From 1ad36ee8989c65e951d720f8fdda5c0f67cc6a7e Mon Sep 17 00:00:00 2001 From: brobert Date: Sun, 9 Nov 2025 21:46:27 +0100 Subject: [PATCH] refactor: centralizar SimulatedResponseQueue y actualizar TaskItem Co-authored-by: aider (openrouter/openai/gpt-5) --- apps/web/src/lib/ui/data/TaskItem.svelte | 7 ++++++- src/services/response-queue.ts | 5 ++--- tests/unit/services/reminders.gating.test.ts | 5 +++-- tests/unit/services/reminders.test.ts | 4 +--- tests/unit/services/response-queue.test.ts | 5 +++-- tests/unit/tasks/service.gating.test.ts | 3 ++- tests/web/ics.aggregate.test.ts | 13 ++----------- tests/web/ics.group.test.ts | 13 ++----------- tests/web/ics.personal.test.ts | 13 ++----------- 9 files changed, 23 insertions(+), 45 deletions(-) diff --git a/apps/web/src/lib/ui/data/TaskItem.svelte b/apps/web/src/lib/ui/data/TaskItem.svelte index 0f8b9ba..ce0d123 100644 --- a/apps/web/src/lib/ui/data/TaskItem.svelte +++ b/apps/web/src/lib/ui/data/TaskItem.svelte @@ -95,6 +95,11 @@ } } + function toIsoSqlLocal(d: Date = new Date()): string { + const iso = d.toISOString(); + return iso.substring(0, 23).replace('T', ' '); + } + async function doComplete() { if (busy || completed) return; busy = true; @@ -103,7 +108,7 @@ const res = await fetch(`/api/tasks/${id}/complete`, { method: "POST" }); if (res.ok) { const data = await res.json().catch(() => null); - const newCompletedAt: string | null = data?.task?.completed_at ? String(data.task.completed_at) : new Date().toISOString().replace('T', ' ').replace('Z', ''); + const newCompletedAt: string | null = data?.task?.completed_at ? String(data.task.completed_at) : toIsoSqlLocal(new Date()); // Si no tenĂ­a responsables, el backend te auto-asigna: reflejarlo localmente if (hadNoAssignees && currentUserId) { const set = new Set(assignees || []); diff --git a/src/services/response-queue.ts b/src/services/response-queue.ts index 0e58a7d..8acbaca 100644 --- a/src/services/response-queue.ts +++ b/src/services/response-queue.ts @@ -572,11 +572,10 @@ export const ResponseQueue = { const startedAt = Date.now(); try { - const toIso = (d: Date) => d.toISOString().replace('T', ' ').replace('Z', ''); const msPerDay = 24 * 60 * 60 * 1000; - const sentThresholdIso = toIso(new Date(now.getTime() - this.RETENTION_DAYS_SENT * msPerDay)); - const failedThresholdIso = toIso(new Date(now.getTime() - this.RETENTION_DAYS_FAILED * msPerDay)); + const sentThresholdIso = toIsoSqlUTC(new Date(now.getTime() - this.RETENTION_DAYS_SENT * msPerDay)); + const failedThresholdIso = toIsoSqlUTC(new Date(now.getTime() - this.RETENTION_DAYS_FAILED * msPerDay)); const cleanStatus = (status: 'sent' | 'failed', thresholdIso: string): number => { let deleted = 0; diff --git a/tests/unit/services/reminders.gating.test.ts b/tests/unit/services/reminders.gating.test.ts index 9281ad3..dbe2c04 100644 --- a/tests/unit/services/reminders.gating.test.ts +++ b/tests/unit/services/reminders.gating.test.ts @@ -5,11 +5,12 @@ import { TaskService } from '../../../src/tasks/service'; import { RemindersService } from '../../../src/services/reminders'; import { AllowedGroups } from '../../../src/services/allowed-groups'; import { ResponseQueue } from '../../../src/services/response-queue'; +import { toIsoSql } from '../../helpers/dates'; function seedGroup(db: Database, groupId: string) { const cols = db.query(`PRAGMA table_info(groups)`).all() as any[]; const values: Record = {}; - const nowIso = new Date().toISOString().replace('T', ' ').replace('Z', ''); + const nowIso = toIsoSql(new Date()); for (const c of cols) { const name = String(c.name); @@ -58,7 +59,7 @@ describe('RemindersService - gating por grupos en modo enforce', () => { sent = []; // Asegurar usuario receptor para satisfacer la FK de user_preferences - const iso = new Date().toISOString().replace('T', ' ').replace('Z', ''); + const iso = toIsoSql(new Date()); memdb.exec(` INSERT INTO users (id, first_seen, last_seen) VALUES ('34600123456', '${iso}', '${iso}') diff --git a/tests/unit/services/reminders.test.ts b/tests/unit/services/reminders.test.ts index 1b6f9a8..0ee028d 100644 --- a/tests/unit/services/reminders.test.ts +++ b/tests/unit/services/reminders.test.ts @@ -5,9 +5,7 @@ import { TaskService } from '../../../src/tasks/service'; import { RemindersService } from '../../../src/services/reminders'; import { ResponseQueue } from '../../../src/services/response-queue'; -function toIso(dt: Date): string { - return dt.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql as toIso } from '../../helpers/dates'; describe('RemindersService', () => { let memdb: Database; diff --git a/tests/unit/services/response-queue.test.ts b/tests/unit/services/response-queue.test.ts index cd2f59f..69f2787 100644 --- a/tests/unit/services/response-queue.test.ts +++ b/tests/unit/services/response-queue.test.ts @@ -2,6 +2,7 @@ import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:tes import { Database } from 'bun:sqlite'; import { initializeDatabase } from '../../../src/db'; import { ResponseQueue } from '../../../src/services/response-queue'; +import { toIsoSql } from '../../helpers/dates'; let testDb: Database; let envBackup: NodeJS.ProcessEnv; @@ -121,10 +122,10 @@ describe('ResponseQueue (retries/backoff)', () => { }); function isoNow(): string { - return new Date().toISOString().replace('T', ' ').replace('Z', ''); + return toIsoSql(new Date()); } function isoFuture(ms: number): string { - return new Date(Date.now() + ms).toISOString().replace('T', ' ').replace('Z', ''); + return toIsoSql(new Date(Date.now() + ms)); } test('claimNextBatch should respect next_attempt_at (only eligible items are claimed)', () => { diff --git a/tests/unit/tasks/service.gating.test.ts b/tests/unit/tasks/service.gating.test.ts index 39a1d39..2a5ebf9 100644 --- a/tests/unit/tasks/service.gating.test.ts +++ b/tests/unit/tasks/service.gating.test.ts @@ -3,12 +3,13 @@ import Database from 'bun:sqlite'; import { initializeDatabase } from '../../../src/db'; import { TaskService } from '../../../src/tasks/service'; import { AllowedGroups } from '../../../src/services/allowed-groups'; +import { toIsoSql } from '../../helpers/dates'; function seedGroup(db: Database, groupId: string) { // Sembrado robusto: cubrir columnas NOT NULL sin valor por defecto const cols = db.query(`PRAGMA table_info(groups)`).all() as any[]; const values: Record = {}; - const nowIso = new Date().toISOString().replace('T', ' ').replace('Z', ''); + const nowIso = toIsoSql(new Date()); for (const c of cols) { const name = String(c.name); diff --git a/tests/web/ics.aggregate.test.ts b/tests/web/ics.aggregate.test.ts index c5a57c4..3016541 100644 --- a/tests/web/ics.aggregate.test.ts +++ b/tests/web/ics.aggregate.test.ts @@ -3,18 +3,9 @@ import Database from 'bun:sqlite'; import { startWebServer } from './helpers/server'; import { createTempDb } from './helpers/db'; -async function sha256Hex(input: string): Promise { - const enc = new TextEncoder().encode(input); - const buf = await crypto.subtle.digest('SHA-256', enc); - const bytes = new Uint8Array(buf); - return Array.from(bytes) - .map((b) => b.toString(16).padStart(2, '0')) - .join(''); -} +import { sha256Hex } from '../helpers/crypto'; -function toIsoSql(d = new Date()): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql } from '../helpers/dates'; function ymdUTC(date = new Date()): string { const yyyy = String(date.getUTCFullYear()).padStart(4, '0'); diff --git a/tests/web/ics.group.test.ts b/tests/web/ics.group.test.ts index 7b33975..0e10c13 100644 --- a/tests/web/ics.group.test.ts +++ b/tests/web/ics.group.test.ts @@ -3,18 +3,9 @@ import Database from 'bun:sqlite'; import { startWebServer } from './helpers/server'; import { createTempDb } from './helpers/db'; -async function sha256Hex(input: string): Promise { - const enc = new TextEncoder().encode(input); - const buf = await crypto.subtle.digest('SHA-256', enc); - const bytes = new Uint8Array(buf); - return Array.from(bytes) - .map((b) => b.toString(16).padStart(2, '0')) - .join(''); -} +import { sha256Hex } from '../helpers/crypto'; -function toIsoSql(d = new Date()): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql } from '../helpers/dates'; function ymdUTC(date = new Date()): string { const yyyy = String(date.getUTCFullYear()).padStart(4, '0'); diff --git a/tests/web/ics.personal.test.ts b/tests/web/ics.personal.test.ts index 10cb466..af693e9 100644 --- a/tests/web/ics.personal.test.ts +++ b/tests/web/ics.personal.test.ts @@ -3,18 +3,9 @@ import Database from 'bun:sqlite'; import { startWebServer } from './helpers/server'; import { createTempDb } from './helpers/db'; -async function sha256Hex(input: string): Promise { - const enc = new TextEncoder().encode(input); - const buf = await crypto.subtle.digest('SHA-256', enc); - const bytes = new Uint8Array(buf); - return Array.from(bytes) - .map((b) => b.toString(16).padStart(2, '0')) - .join(''); -} +import { sha256Hex } from '../helpers/crypto'; -function toIsoSql(d = new Date()): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql } from '../helpers/dates'; function ymdUTC(date = new Date()): string { const yyyy = String(date.getUTCFullYear()).padStart(4, '0');