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'; import { Metrics } from '../../../src/services/metrics'; describe('CommandService - /t nueva (A2: fallback menciones)', () => { let memdb: Database; beforeAll(() => { memdb = new Database(':memory:'); initializeDatabase(memdb); TaskService.dbInstance = memdb; CommandService.dbInstance = memdb; }); beforeEach(() => { process.env.NODE_ENV = 'test'; process.env.METRICS_ENABLED = 'true'; process.env.CHATBOT_PHONE_NUMBER = '1234567890'; process.env.ONBOARDING_FALLBACK_MIN_DIGITS = '8'; Metrics.reset(); memdb.exec(` DELETE FROM task_assignments; DELETE FROM tasks; DELETE FROM users; DELETE FROM user_preferences; `); }); function getLastTaskId(): number { const row = memdb.prepare(`SELECT id FROM tasks ORDER BY id DESC LIMIT 1`).get() as any; return row ? Number(row.id) : 0; } function getAssignees(taskId: number): string[] { const rows = memdb.prepare(`SELECT user_id FROM task_assignments WHERE task_id = ? ORDER BY assigned_at ASC`).all(taskId) as any[]; return rows.map(r => String(r.user_id)); } it('asigna por mención de JID real sin alias (fallback a dígitos)', async () => { const res = await CommandService.handle({ sender: '111', groupId: '', // DM message: '/t n Tarea por mención', mentions: ['34600123456@s.whatsapp.net'], }); expect(res.length).toBeGreaterThan(0); const taskId = getLastTaskId(); expect(taskId).toBeGreaterThan(0); const assignees = getAssignees(taskId); expect(assignees).toContain('34600123456'); }); it('asigna por token @ con + (fallback y normalización de +)', async () => { const res = await CommandService.handle({ sender: '222', groupId: '', message: '/t nueva Tarea token @+34600123456', mentions: [], }); expect(res.length).toBeGreaterThan(0); const taskId = getLastTaskId(); const assignees = getAssignees(taskId); expect(assignees).toContain('34600123456'); }); it('mención/tokens irrecuperables: no bloquea, incrementa métrica', async () => { const res = await CommandService.handle({ sender: '333', groupId: '', message: '/t n Mixta @34600123456 @lid-opaque', mentions: [], }); expect(res.length).toBeGreaterThan(0); const taskId = getLastTaskId(); const assignees = getAssignees(taskId); expect(assignees).toContain('34600123456'); const prom = Metrics.render('prom'); expect(prom).toContain('onboarding_assign_failures_total'); expect(prom).toContain('source="tokens"'); }); it('filtra el número del bot entre candidatos', async () => { // CHATBOT_PHONE_NUMBER = '1234567890' (ver beforeEach) const res = await CommandService.handle({ sender: '444', groupId: '', message: '/t n Asignar @1234567890 @34600123456', mentions: [], }); expect(res.length).toBeGreaterThan(0); const taskId = getLastTaskId(); const assignees = getAssignees(taskId); expect(assignees).toContain('34600123456'); expect(assignees).not.toContain('1234567890'); }); it('deduplica cuando el mismo usuario aparece por mención y token', async () => { const res = await CommandService.handle({ sender: '555', groupId: '', message: '/t n Dedupe @34600123456', mentions: ['34600123456@s.whatsapp.net'], }); expect(res.length).toBeGreaterThan(0); const taskId = getLastTaskId(); const assignees = getAssignees(taskId); // Solo un registro para el mismo usuario const count = assignees.filter(v => v === '34600123456').length; expect(count).toBe(1); }); });