diff --git a/tests/helpers/crypto.ts b/tests/helpers/crypto.ts new file mode 100644 index 0000000..4c30e35 --- /dev/null +++ b/tests/helpers/crypto.ts @@ -0,0 +1,3 @@ +import { sha256Hex as coreSha256Hex } from '../../src/utils/crypto'; + +export const sha256Hex = coreSha256Hex; diff --git a/tests/helpers/dates.ts b/tests/helpers/dates.ts new file mode 100644 index 0000000..d398469 --- /dev/null +++ b/tests/helpers/dates.ts @@ -0,0 +1,25 @@ +import { toIsoSqlUTC } from '../../src/utils/datetime'; + +export function toIsoSql(d: Date = new Date()): string { + return toIsoSqlUTC(d); +} + +export { toIsoSqlUTC }; + +export function ymdInTZ(d: Date, tz: string = 'Europe/Madrid'): string { + const parts = new Intl.DateTimeFormat('en-GB', { + timeZone: tz, + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).formatToParts(d); + const get = (t: string) => parts.find(p => p.type === t)?.value || ''; + return `${get('year')}-${get('month')}-${get('day')}`; +} + +export function addDaysToYMD(ymd: string, days: number, tz: string = 'Europe/Madrid'): string { + const [Y, M, D] = ymd.split('-').map(n => parseInt(n, 10)); + const base = new Date(Date.UTC(Y, (M || 1) - 1, D || 1)); + base.setUTCDate(base.getUTCDate() + days); + return ymdInTZ(base, tz); +} diff --git a/tests/helpers/queue.ts b/tests/helpers/queue.ts new file mode 100644 index 0000000..319a3ec --- /dev/null +++ b/tests/helpers/queue.ts @@ -0,0 +1,9 @@ +let simulatedQueue: any[] = []; + +export const SimulatedResponseQueue = { + async add(responses: any[]) { + simulatedQueue.push(...responses); + }, + clear() { simulatedQueue = []; }, + get() { return simulatedQueue; } +}; diff --git a/tests/unit/server.test.ts b/tests/unit/server.test.ts index 9585b2a..ea06910 100644 --- a/tests/unit/server.test.ts +++ b/tests/unit/server.test.ts @@ -6,23 +6,9 @@ import { GroupSyncService } from '../../src/services/group-sync'; import { initializeDatabase, ensureUserExists } from '../../src/db'; import { TaskService } from '../../src/tasks/service'; -// Simulated ResponseQueue for testing (in-memory array) -let simulatedQueue: any[] = []; let originalAdd: any; -class SimulatedResponseQueue { - static async add(responses: any[]) { - simulatedQueue.push(...responses); - } - - static getQueue() { - return simulatedQueue; - } - - static clear() { - simulatedQueue = []; - } -} +import { SimulatedResponseQueue } from '../helpers/queue'; // Test database instance let testDb: Database; @@ -158,7 +144,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should ignore empty message content', async () => { @@ -176,7 +162,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBe(0); + expect(SimulatedResponseQueue.get().length).toBe(0); }); test('should handle very long messages', async () => { @@ -195,7 +181,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should handle messages with special characters and emojis', async () => { @@ -214,7 +200,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should ignore non-/tarea commands', async () => { @@ -232,7 +218,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBe(0); + expect(SimulatedResponseQueue.get().length).toBe(0); }); test('should ignore message with mentions but no command', async () => { @@ -255,7 +241,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBe(0); + expect(SimulatedResponseQueue.get().length).toBe(0); }); test('should ignore media attachment messages', async () => { @@ -275,7 +261,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBe(0); + expect(SimulatedResponseQueue.get().length).toBe(0); }); test('should process command from extendedTextMessage', async () => { @@ -295,7 +281,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should process command from image caption when caption starts with a command', async () => { @@ -315,7 +301,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should handle requests on configured port', async () => { @@ -373,7 +359,7 @@ describe('WebhookServer', () => { await WebhookServer.handleRequest(createTestRequest(payload)); // Check that a response was queued (indicating command processing) - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should log command with due date', async () => { @@ -398,7 +384,7 @@ describe('WebhookServer', () => { await WebhookServer.handleRequest(createTestRequest(payload)); // Verify command processing by checking queue - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); }); @@ -418,7 +404,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should handle multiple dates in command (use last one as due date)', async () => { @@ -438,7 +424,7 @@ describe('WebhookServer', () => { await WebhookServer.handleRequest(createTestRequest(payload)); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should ignore past dates as due dates', async () => { @@ -598,7 +584,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); // Verify user was created in real database const user = testDb.query("SELECT * FROM users WHERE id = ?").get('1234567890'); @@ -621,7 +607,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBe(0); + expect(SimulatedResponseQueue.get().length).toBe(0); // Verify no user was created const userCount = testDb.query("SELECT COUNT(*) as count FROM users").get(); @@ -646,7 +632,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBe(0); + expect(SimulatedResponseQueue.get().length).toBe(0); // Reinitialize database for subsequent tests (force full migration) testDb.exec('DROP TABLE IF EXISTS schema_migrations'); @@ -668,7 +654,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); // Verify user was created/updated in database const user = testDb.query("SELECT * FROM users WHERE id = ?").get('1234567890'); @@ -695,7 +681,7 @@ describe('WebhookServer', () => { await WebhookServer.handleRequest(request); // Verify that a response was queued, indicating command processing - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should handle end-to-end flow with valid user and command processing', async () => { @@ -720,7 +706,7 @@ describe('WebhookServer', () => { expect(user).toBeDefined(); // Verify that a response was queued - expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0); + expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); }); @@ -800,7 +786,7 @@ describe('WebhookServer', () => { const request = createTestRequest(payload); await WebhookServer.handleRequest(request); - const out = SimulatedResponseQueue.getQueue(); + const out = SimulatedResponseQueue.get(); expect(out.length).toBeGreaterThan(0); for (const r of out) { expect(r.recipient.endsWith('@g.us')).toBe(false); @@ -848,7 +834,7 @@ describe('WebhookServer', () => { const response = await WebhookServer.handleRequest(createTestRequest(payload)); expect(response.status).toBe(200); - const out = SimulatedResponseQueue.getQueue(); + const out = SimulatedResponseQueue.get(); expect(out.length).toBeGreaterThan(0); for (const r of out) { expect(r.recipient.endsWith('@g.us')).toBe(false); diff --git a/tests/unit/server/admin-approval.test.ts b/tests/unit/server/admin-approval.test.ts index 657ccff..ada09fc 100644 --- a/tests/unit/server/admin-approval.test.ts +++ b/tests/unit/server/admin-approval.test.ts @@ -7,14 +7,7 @@ import { ResponseQueue } from '../../../src/services/response-queue'; let testDb: Database; let originalAdd: any; -let simulatedQueue: any[] = []; -const SimulatedResponseQueue = { - async add(responses: any[]) { - simulatedQueue.push(...responses); - }, - clear() { simulatedQueue = []; }, - get() { return simulatedQueue; } -}; +import { SimulatedResponseQueue } from '../../helpers/queue'; const createTestRequest = (payload: any) => new Request('http://localhost:3007', { diff --git a/tests/unit/server/discovery-label.test.ts b/tests/unit/server/discovery-label.test.ts index 1a1a5bf..43d4425 100644 --- a/tests/unit/server/discovery-label.test.ts +++ b/tests/unit/server/discovery-label.test.ts @@ -8,14 +8,7 @@ import { GroupSyncService } from '../../../src/services/group-sync'; let testDb: Database; let originalAdd: any; -let simulatedQueue: any[] = []; -const SimulatedResponseQueue = { - async add(responses: any[]) { - simulatedQueue.push(...responses); - }, - clear() { simulatedQueue = []; }, - get() { return simulatedQueue; } -}; +import { SimulatedResponseQueue } from '../../helpers/queue'; const createTestRequest = (payload: any) => new Request('http://localhost:3007', { diff --git a/tests/unit/server/discovery-notify-admins.test.ts b/tests/unit/server/discovery-notify-admins.test.ts index bbba8df..bc63caf 100644 --- a/tests/unit/server/discovery-notify-admins.test.ts +++ b/tests/unit/server/discovery-notify-admins.test.ts @@ -7,14 +7,7 @@ import { ResponseQueue } from '../../../src/services/response-queue'; let testDb: Database; let originalAdd: any; -let simulatedQueue: any[] = []; -const SimulatedResponseQueue = { - async add(responses: any[]) { - simulatedQueue.push(...responses); - }, - clear() { simulatedQueue = []; }, - get() { return simulatedQueue; } -}; +import { SimulatedResponseQueue } from '../../helpers/queue'; const createTestRequest = (payload: any) => new Request('http://localhost:3007', { diff --git a/tests/unit/server/enforce-gating.test.ts b/tests/unit/server/enforce-gating.test.ts index d4341cf..28f2a76 100644 --- a/tests/unit/server/enforce-gating.test.ts +++ b/tests/unit/server/enforce-gating.test.ts @@ -9,14 +9,7 @@ import { GroupSyncService } from '../../../src/services/group-sync'; let testDb: Database; let originalAdd: any; -let simulatedQueue: any[] = []; -const SimulatedResponseQueue = { - async add(responses: any[]) { - simulatedQueue.push(...responses); - }, - clear() { simulatedQueue = []; }, - get() { return simulatedQueue; } -}; +import { SimulatedResponseQueue } from '../../helpers/queue'; const createTestRequest = (payload: any) => new Request('http://localhost:3007', { diff --git a/tests/unit/server/unknown-group-discovery.test.ts b/tests/unit/server/unknown-group-discovery.test.ts index 6763b46..f4c6f1e 100644 --- a/tests/unit/server/unknown-group-discovery.test.ts +++ b/tests/unit/server/unknown-group-discovery.test.ts @@ -7,14 +7,7 @@ import { ResponseQueue } from '../../../src/services/response-queue'; let testDb: Database; let originalAdd: any; -let simulatedQueue: any[] = []; -const SimulatedResponseQueue = { - async add(responses: any[]) { - simulatedQueue.push(...responses); - }, - clear() { simulatedQueue = []; }, - get() { return simulatedQueue; } -}; +import { SimulatedResponseQueue } from '../../helpers/queue'; const createTestRequest = (payload: any) => new Request('http://localhost:3007', { diff --git a/tests/unit/services/cleanup-inactive.test.ts b/tests/unit/services/cleanup-inactive.test.ts index 1ab8aca..c395523 100644 --- a/tests/unit/services/cleanup-inactive.test.ts +++ b/tests/unit/services/cleanup-inactive.test.ts @@ -3,9 +3,7 @@ import { Database } from 'bun:sqlite'; import { initializeDatabase } from '../../../src/db'; import { MaintenanceService } from '../../../src/services/maintenance'; -function toIso(d: Date): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql as toIso } from '../../helpers/dates'; const envBackup = { ...process.env }; let memdb: Database; diff --git a/tests/unit/services/command.date-parsing.test.ts b/tests/unit/services/command.date-parsing.test.ts index 2f835cc..d180818 100644 --- a/tests/unit/services/command.date-parsing.test.ts +++ b/tests/unit/services/command.date-parsing.test.ts @@ -4,23 +4,8 @@ import { initializeDatabase } from '../../../src/db'; import { TaskService } from '../../../src/tasks/service'; import { CommandService } from '../../../src/services/command'; -function ymdInTZ(d: Date, tz: string = 'Europe/Madrid'): string { - const parts = new Intl.DateTimeFormat('en-GB', { - timeZone: tz, - year: 'numeric', - month: '2-digit', - day: '2-digit', - }).formatToParts(d); - const get = (t: string) => parts.find(p => p.type === t)?.value || ''; - return `${get('year')}-${get('month')}-${get('day')}`; -} - -function addDaysToYMD(ymd: string, days: number, tz: string = 'Europe/Madrid'): string { - const [Y, M, D] = ymd.split('-').map(n => parseInt(n, 10)); - const base = new Date(Date.UTC(Y, (M || 1) - 1, D || 1)); - base.setUTCDate(base.getUTCDate() + days); - return ymdInTZ(base, tz); -} +import { ymdInTZ, addDaysToYMD } from '../../helpers/dates'; + describe('CommandService - parser de fechas (hoy/mañana y formatos YYYY/YY-MM-DD)', () => { let memdb: Database; diff --git a/tests/unit/services/metrics-health.test.ts b/tests/unit/services/metrics-health.test.ts index f13e169..97425b7 100644 --- a/tests/unit/services/metrics-health.test.ts +++ b/tests/unit/services/metrics-health.test.ts @@ -4,9 +4,7 @@ import { Metrics } from '../../../src/services/metrics'; import { initializeDatabase } from '../../../src/db'; import { Database } from 'bun:sqlite'; -function toIso(d: Date): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql as toIso } from '../../helpers/dates'; const envBackup = { ...process.env }; let memdb: Database; diff --git a/tests/unit/services/response-queue.cleanup.test.ts b/tests/unit/services/response-queue.cleanup.test.ts index 2d778d4..faa088f 100644 --- a/tests/unit/services/response-queue.cleanup.test.ts +++ b/tests/unit/services/response-queue.cleanup.test.ts @@ -6,9 +6,7 @@ import { ResponseQueue } from '../../../src/services/response-queue'; let testDb: Database; let originalDbInstance: Database; -function toIso(dt: Date): string { - return dt.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql as toIso } from '../../helpers/dates'; describe('ResponseQueue cleanup/retention', () => { beforeAll(() => { diff --git a/tests/unit/tasks/complete-reaction.test.ts b/tests/unit/tasks/complete-reaction.test.ts index a891b59..2158dfe 100644 --- a/tests/unit/tasks/complete-reaction.test.ts +++ b/tests/unit/tasks/complete-reaction.test.ts @@ -5,9 +5,7 @@ import { TaskService } from '../../../src/tasks/service'; import { ResponseQueue } from '../../../src/services/response-queue'; import { AllowedGroups } from '../../../src/services/allowed-groups'; -function toIsoSql(d: Date): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql } from '../../helpers/dates'; describe('TaskService - reacción ✅ al completar (Fase 2)', () => { let memdb: Database; diff --git a/tests/web/api.integrations.feeds.test.ts b/tests/web/api.integrations.feeds.test.ts index a3dc156..4422696 100644 --- a/tests/web/api.integrations.feeds.test.ts +++ b/tests/web/api.integrations.feeds.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(''); -} - -function toIsoSql(d = new Date()): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { sha256Hex } from '../helpers/crypto'; + +import { toIsoSql } from '../helpers/dates'; describe('API - /api/integrations/feeds', () => { const PORT = 19123; diff --git a/tests/web/api.me.preferences.test.ts b/tests/web/api.me.preferences.test.ts index 29356d7..d6c39ed 100644 --- a/tests/web/api.me.preferences.test.ts +++ b/tests/web/api.me.preferences.test.ts @@ -6,18 +6,9 @@ import { join } from 'path'; import { startWebServer } from './helpers/server'; import { initializeDatabase, ensureUserExists } from '../../src/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(''); -} - -function toIsoSql(d = new Date()): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { sha256Hex } from '../helpers/crypto'; + +import { toIsoSql } from '../helpers/dates'; describe('Web API - GET /api/me/preferences', () => { const userId = '34600123456'; diff --git a/tests/web/api.me.tasks.test.ts b/tests/web/api.me.tasks.test.ts index a36b405..9301130 100644 --- a/tests/web/api.me.tasks.test.ts +++ b/tests/web/api.me.tasks.test.ts @@ -6,18 +6,9 @@ import { join } from 'path'; import { startWebServer } from './helpers/server'; import { initializeDatabase, ensureUserExists } from '../../src/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(''); -} - -function toIsoSql(d = new Date()): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { sha256Hex } from '../helpers/crypto'; + +import { toIsoSql } from '../helpers/dates'; describe('Web API - GET /api/me/tasks', () => { const USER = '34600123456'; diff --git a/tests/web/api.tasks.complete.reaction.test.ts b/tests/web/api.tasks.complete.reaction.test.ts index 1357aad..19bbba5 100644 --- a/tests/web/api.tasks.complete.reaction.test.ts +++ b/tests/web/api.tasks.complete.reaction.test.ts @@ -2,9 +2,7 @@ import { beforeEach, afterEach, describe, expect, it } from 'bun:test'; import { createTempDb } from './helpers/db'; // Los imports del handler y closeDb se hacen dinámicos dentro de cada test/teardown -function toIsoSql(d: Date): string { - return d.toISOString().replace('T', ' ').replace('Z', ''); -} +import { toIsoSql } from '../helpers/dates'; describe('Web API - completar tarea encola reacción ✅', () => { let cleanup: () => void; diff --git a/tests/web/app.integrations.page.test.ts b/tests/web/app.integrations.page.test.ts index 443663b..cc783c6 100644 --- a/tests/web/app.integrations.page.test.ts +++ b/tests/web/app.integrations.page.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'; describe('Web page - /app/integrations', () => { const PORT = 19124; diff --git a/tests/web/app.preferences.page.test.ts b/tests/web/app.preferences.page.test.ts index 28d8cd4..ede4fb3 100644 --- a/tests/web/app.preferences.page.test.ts +++ b/tests/web/app.preferences.page.test.ts @@ -6,18 +6,9 @@ import { join } from 'path'; import { startWebServer } from './helpers/server'; import { initializeDatabase, ensureUserExists } from '../../src/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'; describe('Web UI - /app/preferences', () => { const userId = '34600123456';