diff --git a/tests/unit/services/command.assignment-defaults.test.ts b/tests/unit/services/command.assignment-defaults.test.ts new file mode 100644 index 0000000..55a330c --- /dev/null +++ b/tests/unit/services/command.assignment-defaults.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect, beforeAll, beforeEach } from 'bun:test'; +import { Database } from 'bun:sqlite'; +import { initializeDatabase } from '../../../src/db'; +import { TaskService } from '../../../src/tasks/service'; +import { CommandService } from '../../../src/services/command'; + +describe('CommandService - asignación por defecto (sin dueño vs creador)', () => { + let memdb: Database; + + beforeAll(() => { + memdb = new Database(':memory:'); + initializeDatabase(memdb); + TaskService.dbInstance = memdb; + CommandService.dbInstance = memdb; + }); + + beforeEach(() => { + process.env.NODE_ENV = 'test'; + process.env.TZ = 'Europe/Madrid'; + memdb.exec('DELETE FROM task_assignments; DELETE FROM tasks;'); + }); + + it('en grupos, si no hay menciones, la tarea queda sin dueño', async () => { + const sender = '600111222'; + await CommandService.handle({ + sender, + groupId: '12345@g.us', // contexto grupo + message: '/t n tarea en grupo', + mentions: [], + }); + + const t = memdb.prepare(`SELECT id FROM tasks ORDER BY id DESC LIMIT 1`).get() as any; + const cnt = memdb.prepare(`SELECT COUNT(*) as c FROM task_assignments WHERE task_id = ?`).get(t.id) as any; + expect(Number(cnt.c)).toBe(0); + }); + + it('en DM, si no hay menciones, se asigna al creador', async () => { + const sender = '600111222'; + await CommandService.handle({ + sender, + groupId: `${sender}@s.whatsapp.net`, // DM + message: '/t n tarea en dm', + mentions: [], + }); + + const t = memdb.prepare(`SELECT id FROM tasks ORDER BY id DESC LIMIT 1`).get() as any; + const row = memdb.prepare(`SELECT user_id FROM task_assignments WHERE task_id = ?`).get(t.id) as any; + expect(row).toBeTruthy(); + expect(String(row.user_id)).toBe(sender); + }); +}); diff --git a/tests/unit/services/command.date-parsing.test.ts b/tests/unit/services/command.date-parsing.test.ts new file mode 100644 index 0000000..a0582b9 --- /dev/null +++ b/tests/unit/services/command.date-parsing.test.ts @@ -0,0 +1,91 @@ +import { describe, it, expect, beforeAll, beforeEach } from 'bun:test'; +import { Database } from 'bun:sqlite'; +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); +} + +describe('CommandService - parser de fechas (hoy/mañana)', () => { + let memdb: Database; + + beforeAll(() => { + memdb = new Database(':memory:'); + initializeDatabase(memdb); + TaskService.dbInstance = memdb; + CommandService.dbInstance = memdb; + }); + + beforeEach(() => { + process.env.NODE_ENV = 'test'; + process.env.TZ = 'Europe/Madrid'; + memdb.exec('DELETE FROM task_assignments; DELETE FROM tasks;'); + }); + + it('interpreta "hoy" como due_date de hoy en la TZ configurada', async () => { + const sender = '600111222'; + const ctx = { + sender, + groupId: `${sender}@s.whatsapp.net`, // DM + message: '/t n prueba hoy', + mentions: [] as string[], + }; + await CommandService.handle(ctx); + + const row = memdb.prepare(`SELECT id, due_date FROM tasks ORDER BY id DESC LIMIT 1`).get() as any; + const todayYMD = ymdInTZ(new Date(), process.env.TZ); + expect(row).toBeTruthy(); + expect(String(row.due_date)).toBe(todayYMD); + }); + + it('interpreta "mañana" (con o sin acento) como due_date de mañana', async () => { + const sender = '600111222'; + const ctx = { + sender, + groupId: `${sender}@s.whatsapp.net`, // DM + message: '/t n prueba mañana', + mentions: [] as string[], + }; + await CommandService.handle(ctx); + + const row = memdb.prepare(`SELECT id, due_date FROM tasks ORDER BY id DESC LIMIT 1`).get() as any; + const todayYMD = ymdInTZ(new Date(), process.env.TZ); + const tomorrowYMD = addDaysToYMD(todayYMD, 1, process.env.TZ); + expect(row).toBeTruthy(); + expect(String(row.due_date)).toBe(tomorrowYMD); + }); + + it('elige la última fecha futura cuando hay varias (mezcla YYYY-MM-DD y tokens)', async () => { + const sender = '600111222'; + const ctx = { + sender, + groupId: `${sender}@s.whatsapp.net`, // DM + // contiene pasado (2020-01-01), hoy (futuro válido) y una futura explícita, y termina con "mañana" + message: '/t n mezcla 2020-01-01 hoy 2099-01-01 mañana', + mentions: [] as string[], + }; + await CommandService.handle(ctx); + + const row = memdb.prepare(`SELECT id, due_date FROM tasks ORDER BY id DESC LIMIT 1`).get() as any; + const todayYMD = ymdInTZ(new Date(), process.env.TZ); + const tomorrowYMD = addDaysToYMD(todayYMD, 1, process.env.TZ); + expect(row).toBeTruthy(); + expect(String(row.due_date)).toBe(tomorrowYMD); + }); +}); diff --git a/tests/unit/services/command.formatting-ddmm.test.ts b/tests/unit/services/command.formatting-ddmm.test.ts new file mode 100644 index 0000000..293d2c4 --- /dev/null +++ b/tests/unit/services/command.formatting-ddmm.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect, beforeAll, beforeEach } from 'bun:test'; +import { Database } from 'bun:sqlite'; +import { initializeDatabase } from '../../../src/db'; +import { TaskService } from '../../../src/tasks/service'; +import { CommandService } from '../../../src/services/command'; + +describe('CommandService - formato dd/MM en ACK de creación', () => { + let memdb: Database; + + beforeAll(() => { + memdb = new Database(':memory:'); + initializeDatabase(memdb); + TaskService.dbInstance = memdb; + CommandService.dbInstance = memdb; + }); + + beforeEach(() => { + process.env.NODE_ENV = 'test'; + process.env.TZ = 'Europe/Madrid'; + memdb.exec('DELETE FROM task_assignments; DELETE FROM tasks;'); + }); + + it('muestra la fecha como dd/MM en el ACK al creador', async () => { + const sender = '600111222'; + const responses = await CommandService.handle({ + sender, + groupId: `${sender}@s.whatsapp.net`, // DM + message: '/t n tarea con fecha 2099-01-05', + mentions: [], + }); + + const ack = responses.find(r => r.recipient === sender); + expect(ack).toBeTruthy(); + expect(ack!.message).toContain('📅 05/01'); + }); +}); diff --git a/tests/unit/services/command.help.test.ts b/tests/unit/services/command.help.test.ts new file mode 100644 index 0000000..e167f62 --- /dev/null +++ b/tests/unit/services/command.help.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect, beforeAll, beforeEach } from 'bun:test'; +import { Database } from 'bun:sqlite'; +import { initializeDatabase } from '../../../src/db'; +import { TaskService } from '../../../src/tasks/service'; +import { CommandService } from '../../../src/services/command'; + +describe('CommandService - ayuda por DM', () => { + let memdb: Database; + + beforeAll(() => { + memdb = new Database(':memory:'); + initializeDatabase(memdb); + TaskService.dbInstance = memdb; + CommandService.dbInstance = memdb; + }); + + beforeEach(() => { + process.env.NODE_ENV = 'test'; + process.env.TZ = 'Europe/Madrid'; + }); + + it('responde con ayuda cuando el usuario escribe "/t"', async () => { + const sender = '600111222'; + const responses = await CommandService.handle({ + sender, + groupId: '12345@g.us', + message: '/t', + mentions: [], + }); + + expect(Array.isArray(responses)).toBe(true); + expect(responses.length).toBe(1); + const r = responses[0]; + expect(r.recipient).toBe(sender); + expect(r.message).toContain('Guía rápida:'); + expect(r.message).toContain('/t ver mis'); + }); + + it('responde con ayuda cuando el usuario escribe "/t ayuda"', async () => { + const sender = '600111222'; + const responses = await CommandService.handle({ + sender, + groupId: `${sender}@s.whatsapp.net`, // DM + message: '/t ayuda', + mentions: [], + }); + + expect(responses.length).toBe(1); + const r = responses[0]; + expect(r.recipient).toBe(sender); + expect(r.message).toContain('Guía rápida:'); + expect(r.message).toContain('/t n'); + }); +}); diff --git a/tests/unit/services/command.listing-ddmm.test.ts b/tests/unit/services/command.listing-ddmm.test.ts new file mode 100644 index 0000000..a2726b2 --- /dev/null +++ b/tests/unit/services/command.listing-ddmm.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, beforeAll, beforeEach } from 'bun:test'; +import { Database } from 'bun:sqlite'; +import { initializeDatabase } from '../../../src/db'; +import { TaskService } from '../../../src/tasks/service'; +import { CommandService } from '../../../src/services/command'; + +describe('CommandService - formato dd/MM en listados', () => { + let memdb: Database; + + beforeAll(() => { + memdb = new Database(':memory:'); + initializeDatabase(memdb); + TaskService.dbInstance = memdb; + CommandService.dbInstance = memdb; + }); + + beforeEach(() => { + process.env.NODE_ENV = 'test'; + process.env.TZ = 'Europe/Madrid'; + memdb.exec('DELETE FROM task_assignments; DELETE FROM tasks;'); + }); + + it('"/t ver mis" muestra fechas en dd/MM', async () => { + const sender = '600111222'; + + // Crear una tarea con due_date conocida y asignada al usuario (DM → asignada al creador) + await CommandService.handle({ + sender, + groupId: `${sender}@s.whatsapp.net`, // DM + message: '/t n tarea listada 2099-01-05', + mentions: [], + }); + + // Listar "mis" y comprobar formato + const responses = await CommandService.handle({ + sender, + groupId: `${sender}@s.whatsapp.net`, + message: '/t ver mis', + mentions: [], + }); + + expect(responses.length).toBe(1); + expect(responses[0].recipient).toBe(sender); + expect(responses[0].message).toContain('📅 05/01'); + }); +});