test: agrega pruebas de ver sin y ver todos en comandos y servidor

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
pull/1/head
borja 2 months ago
parent 137e0d2d07
commit 6c4e85aa6c

@ -4,6 +4,7 @@ import { WebhookServer } from '../../src/server';
import { ResponseQueue } from '../../src/services/response-queue';
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[] = [];
@ -54,6 +55,9 @@ beforeEach(() => {
// Inject testDb for GroupSyncService to use
GroupSyncService.dbInstance = testDb;
// Inject testDb for TaskService to use
(TaskService as any).dbInstance = testDb;
// Ensure database is initialized (recreates tables if dropped)
initializeDatabase(testDb);
@ -783,4 +787,153 @@ describe('WebhookServer', () => {
}
});
});
describe('Advanced listings via WebhookServer', () => {
test('should process "/t ver sin" in group as DM-only with pagination line', async () => {
// 12 sin dueño en el grupo activo
for (let i = 1; i <= 12; i++) {
TaskService.createTask({
description: `Sin dueño ${i}`,
due_date: '2025-12-31',
group_id: 'group-id@g.us',
created_by: '9999999999',
});
}
// 2 asignadas (no deben aparecer en "sin")
TaskService.createTask({
description: 'Asignada 1',
due_date: '2025-10-10',
group_id: 'group-id@g.us',
created_by: '1111111111',
}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
TaskService.createTask({
description: 'Asignada 2',
due_date: '2025-10-11',
group_id: 'group-id@g.us',
created_by: '1111111111',
}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
const payload = {
event: 'messages.upsert',
instance: 'test-instance',
data: {
key: {
remoteJid: 'group-id@g.us',
participant: '1234567890@s.whatsapp.net'
},
message: { conversation: '/t ver sin' }
}
};
const response = await WebhookServer.handleRequest(createTestRequest(payload));
expect(response.status).toBe(200);
const out = SimulatedResponseQueue.getQueue();
expect(out.length).toBeGreaterThan(0);
for (const r of out) {
expect(r.recipient.endsWith('@g.us')).toBe(false);
}
const msg = out.map(x => x.message).join('\n');
expect(msg).toContain('sin dueño');
expect(msg).toContain('… y 2 más');
});
test('should process "/t ver sin" in DM returning instruction', async () => {
const payload = {
event: 'messages.upsert',
instance: 'test-instance',
data: {
key: {
remoteJid: '1234567890@s.whatsapp.net', // DM
participant: '1234567890@s.whatsapp.net'
},
message: { conversation: '/t ver sin' }
}
};
const response = await WebhookServer.handleRequest(createTestRequest(payload));
expect(response.status).toBe(200);
const out = SimulatedResponseQueue.getQueue();
expect(out.length).toBeGreaterThan(0);
const msg = out.map(x => x.message).join('\n');
expect(msg).toContain('Este comando se usa en grupos');
});
test('should process "/t ver todos" in group showing "Tus tareas" + "Sin dueño (grupo actual)" with pagination in unassigned section', async () => {
// Tus tareas (2 asignadas)
TaskService.createTask({
description: 'Mi Tarea 1',
due_date: '2025-10-10',
group_id: 'group-id@g.us',
created_by: '2222222222',
}, [{ user_id: '1234567890', assigned_by: '2222222222' }]);
TaskService.createTask({
description: 'Mi Tarea 2',
due_date: '2025-10-11',
group_id: 'group-id@g.us',
created_by: '2222222222',
}, [{ user_id: '1234567890', assigned_by: '2222222222' }]);
// 12 sin dueño para provocar paginación
for (let i = 1; i <= 12; i++) {
TaskService.createTask({
description: `Sin dueño ${i}`,
due_date: '2025-12-31',
group_id: 'group-id@g.us',
created_by: '9999999999',
});
}
const payload = {
event: 'messages.upsert',
instance: 'test-instance',
data: {
key: {
remoteJid: 'group-id@g.us',
participant: '1234567890@s.whatsapp.net'
},
message: { conversation: '/t ver todos' }
}
};
const response = await WebhookServer.handleRequest(createTestRequest(payload));
expect(response.status).toBe(200);
const out = SimulatedResponseQueue.getQueue();
expect(out.length).toBeGreaterThan(0);
const msg = out.map(x => x.message).join('\n');
expect(msg).toContain('Tus tareas');
expect(msg).toContain('sin dueño');
expect(msg).toContain('… y 2 más');
});
test('should process "/t ver todos" in DM showing "Tus tareas" + instructive note', async () => {
TaskService.createTask({
description: 'Mi Tarea A',
due_date: '2025-11-20',
group_id: 'group-2@g.us',
created_by: '1111111111',
}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
const payload = {
event: 'messages.upsert',
instance: 'test-instance',
data: {
key: {
remoteJid: '1234567890@s.whatsapp.net', // DM
participant: '1234567890@s.whatsapp.net'
},
message: { conversation: '/t ver todos' }
}
};
const response = await WebhookServer.handleRequest(createTestRequest(payload));
expect(response.status).toBe(200);
const out = SimulatedResponseQueue.getQueue();
expect(out.length).toBeGreaterThan(0);
const msg = out.map(x => x.message).join('\n');
expect(msg).toContain('Tus tareas');
expect(msg).toContain(' Para ver tareas sin dueño');
});
});
});

@ -145,6 +145,149 @@ test('completar tarea: camino feliz, ya completada y no encontrada', async () =>
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 dueño');
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 dueño');
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 dueño');
});
afterEach(() => {
try { memDb.close(); } catch {}
});

@ -17,6 +17,113 @@ afterEach(() => {
} catch {}
});
describe('TaskService.listGroupUnassigned / countGroupUnassigned', () => {
it('devuelve solo tareas sin dueño del grupo, en orden por fecha (NULL al final) y respeta el límite', () => {
// Sembrar grupos para FK
memDb.prepare(`INSERT OR IGNORE INTO groups (id, community_id, name, active) VALUES (?, ?, ?, 1)`)
.run('g1@g.us', 'c1', 'G1', 1);
memDb.prepare(`INSERT OR IGNORE INTO groups (id, community_id, name, active) VALUES (?, ?, ?, 1)`)
.run('g2@g.us', 'c1', 'G2', 1);
// En g1: 3 sin dueño con distintas fechas, 1 asignada (excluir), 1 sin dueño completada (excluir)
const tEarly = TaskService.createTask({
description: 'Sin dueño pronto',
due_date: '2025-08-01',
group_id: 'g1@g.us',
created_by: '1000000000',
});
const tLater = TaskService.createTask({
description: 'Sin dueño tarde',
due_date: '2025-10-01',
group_id: 'g1@g.us',
created_by: '1000000000',
});
const tNull = TaskService.createTask({
description: 'Sin dueño sin fecha',
due_date: null,
group_id: 'g1@g.us',
created_by: '1000000000',
});
TaskService.createTask({
description: 'Asignada (no debe salir)',
due_date: '2025-09-01',
group_id: 'g1@g.us',
created_by: '1000000000',
}, [{ user_id: '2000000000', assigned_by: '1000000000' }]);
// Completar una sin dueño para que no aparezca
memDb.prepare(`UPDATE tasks SET completed = 1, completed_at = CURRENT_TIMESTAMP WHERE id = ?`).run(tEarly);
// En g2: sin dueño (no deben aparecer al listar g1)
for (let i = 1; i <= 3; i++) {
TaskService.createTask({
description: `G2 ${i}`,
due_date: '2025-12-31',
group_id: 'g2@g.us',
created_by: '1000000000',
});
}
const list = TaskService.listGroupUnassigned('g1@g.us', 10);
expect(Array.isArray(list)).toBe(true);
// Deben quedar 2: 'Sin dueño tarde' (fecha) y 'Sin dueño sin fecha' (NULL al final)
expect(list.length).toBe(2);
expect(list[0].description).toBe('Sin dueño tarde');
expect(list[1].description).toBe('Sin dueño sin fecha');
expect(list[0].assignees).toEqual([]);
expect(list[1].assignees).toEqual([]);
expect(list[0].group_id).toBe('g1@g.us');
expect(list[1].group_id).toBe('g1@g.us');
// Límite
// Crear más en g1 para forzar límite 1
TaskService.createTask({
description: 'Otra sin dueño',
due_date: '2025-11-01',
group_id: 'g1@g.us',
created_by: '1000000000',
});
const limited = TaskService.listGroupUnassigned('g1@g.us', 1);
expect(limited.length).toBe(1);
});
it('countGroupUnassigned cuenta solo sin dueño pendientes (excluye completadas y asignadas)', () => {
// Limpiar por si acaso
memDb.exec('DELETE FROM tasks');
memDb.exec('DELETE FROM task_assignments');
// Sembrar grupo
memDb.prepare(`INSERT OR IGNORE INTO groups (id, community_id, name, active) VALUES (?, ?, ?, 1)`)
.run('g1@g.us', 'c1', 'G1', 1);
const a = TaskService.createTask({
description: 'Sin dueño 1',
due_date: '2025-09-10',
group_id: 'g1@g.us',
created_by: '3333333333',
});
const b = TaskService.createTask({
description: 'Sin dueño 2',
due_date: null,
group_id: 'g1@g.us',
created_by: '3333333333',
});
// Asignada (no cuenta como sin dueño)
TaskService.createTask({
description: 'Asignada',
due_date: '2025-09-12',
group_id: 'g1@g.us',
created_by: '3333333333',
}, [{ user_id: '4444444444', assigned_by: '3333333333' }]);
// Completar una sin dueño
memDb.prepare(`UPDATE tasks SET completed = 1, completed_at = CURRENT_TIMESTAMP WHERE id = ?`).run(a);
const cnt = TaskService.countGroupUnassigned('g1@g.us');
expect(cnt).toBe(1); // sólo queda b
});
});
describe('TaskService.createTask', () => {
it('crea una tarea mínima con created_by y sin due_date ni group_id', () => {
const creatorRaw = '555111222@s.whatsapp.net';

Loading…
Cancel
Save