test: añade pruebas de claim/unassign para TaskService y CommandService
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>pull/1/head
parent
57f5dd04e6
commit
9fb4a0d03a
@ -0,0 +1,110 @@
|
|||||||
|
import { describe, it, expect, beforeAll, beforeEach } from 'bun:test';
|
||||||
|
import { Database } from 'bun:sqlite';
|
||||||
|
import { initializeDatabase, ensureUserExists } from '../../../src/db';
|
||||||
|
import { TaskService } from '../../../src/tasks/service';
|
||||||
|
import { CommandService } from '../../../src/services/command';
|
||||||
|
|
||||||
|
describe('CommandService - /t tomar y /t soltar', () => {
|
||||||
|
let memdb: Database;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
memdb = new Database(':memory:');
|
||||||
|
initializeDatabase(memdb);
|
||||||
|
TaskService.dbInstance = memdb;
|
||||||
|
CommandService.dbInstance = memdb;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
memdb.exec(`
|
||||||
|
DELETE FROM task_assignments;
|
||||||
|
DELETE FROM tasks;
|
||||||
|
DELETE FROM users;
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
function createTask(description: string, creator: string, due?: string | null, assignees: string[] = []): number {
|
||||||
|
const createdBy = ensureUserExists(creator, memdb)!;
|
||||||
|
const taskId = TaskService.createTask(
|
||||||
|
{
|
||||||
|
description,
|
||||||
|
due_date: due ?? null,
|
||||||
|
group_id: null,
|
||||||
|
created_by: createdBy,
|
||||||
|
},
|
||||||
|
assignees.map(uid => ({ user_id: ensureUserExists(uid, memdb)!, assigned_by: createdBy }))
|
||||||
|
);
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = (sender: string, message: string) => ({
|
||||||
|
sender,
|
||||||
|
groupId: '', // DM o vacío; sin relevancia para tomar/soltar
|
||||||
|
message,
|
||||||
|
mentions: [] as string[],
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tomar: uso inválido (sin id)', async () => {
|
||||||
|
const res = await CommandService.handle(ctx('111', '/t tomar'));
|
||||||
|
expect(res).toHaveLength(1);
|
||||||
|
expect(res[0].recipient).toBe('111');
|
||||||
|
expect(res[0].message).toContain('Uso: /t tomar <id>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tomar: not_found', async () => {
|
||||||
|
const res = await CommandService.handle(ctx('111', '/t tomar 99999'));
|
||||||
|
expect(res[0].message).toContain('no encontrada');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tomar: happy y luego already', async () => {
|
||||||
|
const taskId = createTask('Desc tomar', '999', '2025-09-12');
|
||||||
|
const r1 = await CommandService.handle(ctx('111', `/t tomar ${taskId}`));
|
||||||
|
expect(r1[0].message).toContain('Has tomado');
|
||||||
|
expect(r1[0].message).toContain(String(taskId));
|
||||||
|
expect(r1[0].message).toContain('Desc tomar');
|
||||||
|
expect(r1[0].message).toContain('📅'); // formato dd/MM
|
||||||
|
|
||||||
|
const r2 = await CommandService.handle(ctx('111', `/t tomar ${taskId}`));
|
||||||
|
expect(r2[0].message).toContain('ya la tenías');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tomar: completed', async () => {
|
||||||
|
const taskId = createTask('Tarea completa', '111', '2025-10-10');
|
||||||
|
const comp = TaskService.completeTask(taskId, '111');
|
||||||
|
expect(comp.status).toBe('updated');
|
||||||
|
|
||||||
|
const res = await CommandService.handle(ctx('222', `/t tomar ${taskId}`));
|
||||||
|
expect(res[0].message).toContain('ya estaba completada');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('soltar: uso inválido (sin id)', async () => {
|
||||||
|
const res = await CommandService.handle(ctx('111', '/t soltar'));
|
||||||
|
expect(res[0].message).toContain('Uso: /t soltar <id>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('soltar: not_found', async () => {
|
||||||
|
const res = await CommandService.handle(ctx('111', '/t soltar 123456'));
|
||||||
|
expect(res[0].message).toContain('no encontrada');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('soltar: si queda sin dueño, mensaje adecuado', async () => {
|
||||||
|
const taskId = createTask('Desc soltar', '999', '2025-09-12', ['111']);
|
||||||
|
const res = await CommandService.handle(ctx('111', `/t soltar ${taskId}`));
|
||||||
|
expect(res[0].message).toContain('queda sin dueño');
|
||||||
|
expect(res[0].message).toContain(String(taskId));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('soltar: not_assigned muestra mensaje informativo', async () => {
|
||||||
|
const taskId = createTask('Nunca asignada a 111', '999', null, ['222']);
|
||||||
|
const res = await CommandService.handle(ctx('111', `/t soltar ${taskId}`));
|
||||||
|
expect(res[0].message).toContain('no la tenías asignada');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('soltar: completed', async () => {
|
||||||
|
const taskId = createTask('Completa y soltar', '111', null, ['111']);
|
||||||
|
const comp = TaskService.completeTask(taskId, '111');
|
||||||
|
expect(comp.status).toBe('updated');
|
||||||
|
|
||||||
|
const res = await CommandService.handle(ctx('111', `/t soltar ${taskId}`));
|
||||||
|
expect(res[0].message).toContain('ya estaba completada');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
import { describe, it, expect, beforeAll, beforeEach } from 'bun:test';
|
||||||
|
import { Database } from 'bun:sqlite';
|
||||||
|
import { initializeDatabase, ensureUserExists } from '../../../src/db';
|
||||||
|
import { TaskService } from '../../../src/tasks/service';
|
||||||
|
|
||||||
|
describe('TaskService - claim/unassign', () => {
|
||||||
|
let memdb: Database;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
memdb = new Database(':memory:');
|
||||||
|
initializeDatabase(memdb);
|
||||||
|
TaskService.dbInstance = memdb;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
memdb.exec(`
|
||||||
|
DELETE FROM task_assignments;
|
||||||
|
DELETE FROM tasks;
|
||||||
|
DELETE FROM users;
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
function createTask(description: string, createdBy: string, due?: string | null, assignees: string[] = []): number {
|
||||||
|
const creator = ensureUserExists(createdBy, memdb)!;
|
||||||
|
const taskId = TaskService.createTask(
|
||||||
|
{
|
||||||
|
description,
|
||||||
|
due_date: due ?? null,
|
||||||
|
group_id: null,
|
||||||
|
created_by: creator,
|
||||||
|
},
|
||||||
|
assignees.map(uid => ({ user_id: ensureUserExists(uid, memdb)!, assigned_by: creator }))
|
||||||
|
);
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('claim: happy path then idempotent', () => {
|
||||||
|
const taskId = createTask('Probar claim', '111', '2025-09-12');
|
||||||
|
const res1 = TaskService.claimTask(taskId, '222');
|
||||||
|
expect(res1.status).toBe('claimed');
|
||||||
|
expect(res1.task?.id).toBe(taskId);
|
||||||
|
|
||||||
|
const res2 = TaskService.claimTask(taskId, '222');
|
||||||
|
expect(res2.status).toBe('already');
|
||||||
|
expect(res2.task?.id).toBe(taskId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('claim: not_found', () => {
|
||||||
|
const res = TaskService.claimTask(999999, '111');
|
||||||
|
expect(res.status).toBe('not_found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('claim: completed', () => {
|
||||||
|
const taskId = createTask('Tarea ya completada', '111', '2025-10-10');
|
||||||
|
// marcar como completada
|
||||||
|
const comp = TaskService.completeTask(taskId, '111');
|
||||||
|
expect(comp.status).toBe('updated');
|
||||||
|
|
||||||
|
const res = TaskService.claimTask(taskId, '222');
|
||||||
|
expect(res.status).toBe('completed');
|
||||||
|
expect(res.task?.id).toBe(taskId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unassign: happy path; luego not_assigned; now_unassigned=true', () => {
|
||||||
|
const taskId = createTask('Soltar luego de asignar', '111', '2025-09-20', ['222']);
|
||||||
|
// soltar por el mismo usuario
|
||||||
|
const res1 = TaskService.unassignTask(taskId, '222');
|
||||||
|
expect(res1.status).toBe('unassigned');
|
||||||
|
expect(res1.task?.id).toBe(taskId);
|
||||||
|
expect(res1.now_unassigned).toBe(true);
|
||||||
|
|
||||||
|
// idempotente si no estaba asignado
|
||||||
|
const res2 = TaskService.unassignTask(taskId, '222');
|
||||||
|
expect(res2.status).toBe('not_assigned');
|
||||||
|
expect(res2.now_unassigned).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unassign: not_found', () => {
|
||||||
|
const res = TaskService.unassignTask(424242, '111');
|
||||||
|
expect(res.status).toBe('not_found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unassign: completed', () => {
|
||||||
|
const taskId = createTask('Unassign bloqueada por completada', '111', null, ['222']);
|
||||||
|
const comp = TaskService.completeTask(taskId, '111');
|
||||||
|
expect(comp.status).toBe('updated');
|
||||||
|
|
||||||
|
const res = TaskService.unassignTask(taskId, '222');
|
||||||
|
expect(res.status).toBe('completed');
|
||||||
|
expect(res.task?.id).toBe(taskId);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue