You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

343 lines
11 KiB
TypeScript

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db';
import { CommandService } from '../../../src/services/command';
import { TaskService } from '../../../src/tasks/service';
import { GroupSyncService } from '../../../src/services/group-sync';
let memDb: Database;
const testContextBase = {
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [] as string[],
};
beforeEach(() => {
memDb = new Database(':memory:');
initializeDatabase(memDb);
(CommandService as any).dbInstance = memDb;
(TaskService as any).dbInstance = memDb;
(GroupSyncService as any).dbInstance = memDb;
GroupSyncService.activeGroupsCache.clear();
});
test('listar grupo por defecto con /t ver en grupo e incluir “… y X más”', async () => {
// Insert group and cache it as active
memDb.exec(`
INSERT OR IGNORE INTO groups (id, community_id, name, active)
VALUES ('test-group@g.us', 'test-community', 'Test Group', 1)
`);
GroupSyncService.activeGroupsCache.clear();
GroupSyncService.activeGroupsCache.set('test-group@g.us', 'Test Group');
// Crear 12 tareas sin asignados en el grupo
for (let i = 1; i <= 12; i++) {
TaskService.createTask({
description: `Task ${i}`,
due_date: '2025-12-31',
group_id: 'test-group@g.us',
created_by: '1234567890',
});
}
const responses = await CommandService.handle({
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [],
message: '/t ver'
});
expect(responses.length).toBe(1);
expect(responses[0].recipient).toBe('1234567890');
expect(responses[0].message).toContain('Test Group');
// Debe indicar que hay 2 más (límite 10)
expect(responses[0].message).toContain('… y 2 más');
// Debe mostrar “sin responsable”
expect(responses[0].message).toContain('sin responsable');
});
test('listar “mis” por defecto en DM con /t ver', async () => {
// Insert groups and cache them
memDb.exec(`
INSERT OR REPLACE INTO groups (id, community_id, name, active) VALUES
('test-group@g.us', 'test-community', 'Test Group', 1),
('group-2@g.us', 'test-community', 'Group 2', 1)
`);
GroupSyncService.activeGroupsCache.clear();
GroupSyncService.activeGroupsCache.set('test-group@g.us', 'Test Group');
GroupSyncService.activeGroupsCache.set('group-2@g.us', 'Group 2');
// Crear 2 tareas asignadas al usuario en distintos grupos
const t1 = TaskService.createTask({
description: 'G1 Task',
due_date: '2025-11-20',
group_id: 'test-group@g.us',
created_by: '1111111111',
}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
const t2 = TaskService.createTask({
description: 'G2 Task',
due_date: '2025-11-25',
group_id: 'group-2@g.us',
created_by: '2222222222',
}, [{ user_id: '1234567890', assigned_by: '2222222222' }]);
const responses = await CommandService.handle({
sender: '1234567890',
// Contexto de DM: usar un JID que NO sea de grupo
groupId: '1234567890@s.whatsapp.net',
mentions: [],
message: '/t ver'
});
expect(responses.length).toBe(1);
expect(responses[0].recipient).toBe('1234567890');
const msg = responses[0].message;
expect(msg).toContain('Test Group');
expect(msg).toContain('Group 2');
expect(msg).toMatch(/- `\d{4}` G1 Task/);
expect(msg).toMatch(/- `\d{4}` G2 Task/);
});
test('completar tarea: camino feliz, ya completada y no encontrada', async () => {
// Insertar grupo y cache
memDb.exec(`
INSERT OR IGNORE INTO groups (id, community_id, name, active)
VALUES ('test-group@g.us', 'test-community', 'Test Group', 1)
`);
GroupSyncService.activeGroupsCache.clear();
GroupSyncService.activeGroupsCache.set('test-group@g.us', 'Test Group');
const taskId = TaskService.createTask({
description: 'Completar yo',
due_date: '2025-10-10',
group_id: 'test-group@g.us',
created_by: '1111111111',
});
const dc = Number((memDb.prepare(`SELECT display_code FROM tasks WHERE id = ?`).get(taskId) as any)?.display_code || 0);
// 1) Camino feliz
let responses = await CommandService.handle({
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [],
message: `/t x ${dc}`
});
expect(responses.length).toBe(1);
expect(responses[0].recipient).toBe('1234567890');
expect(responses[0].message).toMatch(/^✅ `\d{4}` _completada_/);
// 2) Ya completada (ahora no debe resolverse por display_code → no encontrada)
responses = await CommandService.handle({
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [],
message: `/t x ${dc}`
});
expect(responses.length).toBe(1);
expect(responses[0].message).toContain('no encontrada');
// 3) No encontrada
responses = await CommandService.handle({
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [],
message: `/t x 999999`
});
expect(responses.length).toBe(1);
expect(responses[0].message).toContain('no encontrada');
});
test('ver sin en grupo activo: solo sin dueño y paginación', async () => {
// Insertar grupo y cachearlo como activo
memDb.exec(`
INSERT OR IGNORE INTO groups (id, community_id, name, active)
VALUES ('test-group@g.us', 'test-community', 'Test Group', 1)
`);
GroupSyncService.activeGroupsCache.clear();
GroupSyncService.activeGroupsCache.set('test-group@g.us', 'Test Group');
// 12 tareas sin dueño (para provocar “… y 2 más” con límite 10)
for (let i = 1; i <= 12; i++) {
TaskService.createTask({
description: `Unassigned ${i}`,
due_date: '2025-12-31',
group_id: 'test-group@g.us',
created_by: '9999999999',
});
}
// 2 tareas asignadas (no deben aparecer en "ver sin")
TaskService.createTask({
description: 'Asignada 1',
due_date: '2025-11-01',
group_id: 'test-group@g.us',
created_by: '1111111111',
}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
TaskService.createTask({
description: 'Asignada 2',
due_date: '2025-11-02',
group_id: 'test-group@g.us',
created_by: '1111111111',
}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
const responses = await CommandService.handle({
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [],
message: '/t ver sin'
});
expect(responses.length).toBe(1);
expect(responses[0].recipient).toBe('1234567890');
const msg = responses[0].message;
expect(msg).toContain('Test Group');
expect(msg).toContain('sin responsable');
expect(msg).toContain('… y 2 más');
expect(msg).not.toContain('Asignada 1');
expect(msg).not.toContain('Asignada 2');
});
test('ver sin por DM devuelve instrucción', async () => {
const responses = await CommandService.handle({
sender: '1234567890',
// DM: no es un JID de grupo
groupId: '1234567890@s.whatsapp.net',
mentions: [],
message: '/t ver sin'
});
expect(responses.length).toBe(1);
expect(responses[0].recipient).toBe('1234567890');
expect(responses[0].message).toContain('Este comando se usa en grupos');
});
test('ver todos en grupo: “Tus tareas” + “Sin dueño (grupo actual)” con paginación en la sección sin dueño', async () => {
// Insertar grupo y cachearlo como activo
memDb.exec(`
INSERT OR IGNORE INTO groups (id, community_id, name, active)
VALUES ('test-group@g.us', 'test-community', 'Test Group', 1)
`);
GroupSyncService.activeGroupsCache.clear();
GroupSyncService.activeGroupsCache.set('test-group@g.us', 'Test Group');
// Tus tareas (2 asignadas al usuario)
TaskService.createTask({
description: 'Mi Tarea 1',
due_date: '2025-10-10',
group_id: 'test-group@g.us',
created_by: '2222222222',
}, [{ user_id: '1234567890', assigned_by: '2222222222' }]);
TaskService.createTask({
description: 'Mi Tarea 2',
due_date: '2025-10-11',
group_id: 'test-group@g.us',
created_by: '2222222222',
}, [{ user_id: '1234567890', assigned_by: '2222222222' }]);
// 12 sin dueño en el grupo (provoca “… y 2 más” en esa sección)
for (let i = 1; i <= 12; i++) {
TaskService.createTask({
description: `Sin dueño ${i}`,
due_date: '2025-12-31',
group_id: 'test-group@g.us',
created_by: '9999999999',
});
}
const responses = await CommandService.handle({
sender: '1234567890',
groupId: 'test-group@g.us',
mentions: [],
message: '/t ver todos'
});
expect(responses.length).toBe(1);
const msg = responses[0].message;
expect(msg).toContain('Tus tareas');
expect(msg).toContain('Test Group');
expect(msg).toContain('sin responsable');
expect(msg).toContain('… y 2 más'); // paginación en la sección “sin dueño”
});
test('ver todos por DM: “Tus tareas” + nota instructiva para ver sin dueño desde el grupo', async () => {
// 2 tareas asignadas al usuario en cualquier grupo (no importa para este test)
TaskService.createTask({
description: 'Mi Tarea A',
due_date: '2025-11-20',
group_id: 'group-1@g.us',
created_by: '1111111111',
}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
TaskService.createTask({
description: 'Mi Tarea B',
due_date: '2025-11-21',
group_id: 'group-2@g.us',
created_by: '1111111111',
}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
const responses = await CommandService.handle({
sender: '1234567890',
groupId: '1234567890@s.whatsapp.net', // DM
mentions: [],
message: '/t ver todos'
});
expect(responses.length).toBe(1);
const msg = responses[0].message;
expect(msg).toContain('Tus tareas');
expect(msg).toContain(' Para ver tareas sin responsable');
});
afterEach(() => {
try { memDb.close(); } catch {}
});
describe('CommandService', () => {
test('should ignore non-tarea commands', async () => {
const responses = await CommandService.handle({
...testContextBase,
message: '/othercommand'
});
expect(responses).toEqual([]);
});
test('acepta alias /t y responde con formato compacto', async () => {
const responses = await CommandService.handle({
...testContextBase,
message: '/t n Test task'
});
expect(responses.length).toBe(1);
expect(responses[0].recipient).toBe('1234567890');
// Debe empezar con "📝 `0001` "
expect(responses[0].message).toMatch(/^📝 `\d{4}` /);
// Debe mostrar la descripción en texto plano (sin cursiva)
expect(responses[0].message).toContain(' Test task');
expect(responses[0].message).not.toContain('_Test task_');
// No debe usar el texto antiguo "Tarea <id> creada"
expect(responses[0].message).not.toMatch(/Tarea \d+ creada/);
});
test('should return error response on failure', async () => {
// Forzar error temporalmente
const original = TaskService.createTask;
(TaskService as any).createTask = () => { throw new Error('forced'); };
const responses = await CommandService.handle({
...testContextBase,
message: '/tarea nueva Test task'
});
expect(responses.length).toBe(1);
expect(responses[0].recipient).toBe('1234567890');
expect(responses[0].message).toBe('Error processing command');
// Restaurar
(TaskService as any).createTask = original;
});
});