feat: añadir TaskService con dbInstance y tests unitarios
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>pull/1/head
							parent
							
								
									94f6813cb2
								
							
						
					
					
						commit
						7b9928937b
					
				| @ -0,0 +1,170 @@ | |||||||
|  | import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; | ||||||
|  | import Database from 'better-sqlite3'; | ||||||
|  | import { initializeDatabase, ensureUserExists } from '../../../src/db'; | ||||||
|  | import { TaskService } from '../../../src/tasks/service'; | ||||||
|  | 
 | ||||||
|  | let memDb: Database; | ||||||
|  | 
 | ||||||
|  | beforeEach(() => { | ||||||
|  |   memDb = new Database(':memory:'); | ||||||
|  |   initializeDatabase(memDb); | ||||||
|  |   TaskService.dbInstance = memDb; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | afterEach(() => { | ||||||
|  |   try { | ||||||
|  |     memDb.close(); | ||||||
|  |   } catch {} | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 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'; | ||||||
|  |     const createdBy = ensureUserExists(creatorRaw, memDb); | ||||||
|  |     expect(createdBy).toBeTruthy(); | ||||||
|  | 
 | ||||||
|  |     const id = TaskService.createTask( | ||||||
|  |       { | ||||||
|  |         description: 'Comprar agua', | ||||||
|  |         created_by: createdBy!, | ||||||
|  |         due_date: null, | ||||||
|  |         group_id: null, | ||||||
|  |       }, | ||||||
|  |       [] // sin asignaciones
 | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     expect(typeof id).toBe('number'); | ||||||
|  | 
 | ||||||
|  |     const task = memDb | ||||||
|  |       .prepare( | ||||||
|  |         `SELECT id, description, due_date, group_id, created_by, completed 
 | ||||||
|  |          FROM tasks WHERE id = ?` | ||||||
|  |       ) | ||||||
|  |       .get(id) as any; | ||||||
|  | 
 | ||||||
|  |     expect(task).toBeTruthy(); | ||||||
|  |     expect(task.description).toBe('Comprar agua'); | ||||||
|  |     expect(task.due_date).toBeNull(); | ||||||
|  |     expect(task.group_id).toBeNull(); | ||||||
|  |     expect(task.created_by).toBe(createdBy); | ||||||
|  |     expect(task.completed).toBe(0); // BOOLEAN en SQLite como 0/1
 | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('guarda due_date como cadena YYYY-MM-DD y group_id como JID completo', () => { | ||||||
|  |     const creatorRaw = '555333444@s.whatsapp.net'; | ||||||
|  |     const createdBy = ensureUserExists(creatorRaw, memDb)!; | ||||||
|  | 
 | ||||||
|  |     const due = '2025-09-10'; | ||||||
|  |     const groupId = '12345@g.us'; | ||||||
|  | 
 | ||||||
|  |     const id = TaskService.createTask( | ||||||
|  |       { | ||||||
|  |         description: 'Pagar servicios', | ||||||
|  |         created_by: createdBy, | ||||||
|  |         due_date: due, | ||||||
|  |         group_id: groupId, | ||||||
|  |       }, | ||||||
|  |       [] | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const row = memDb | ||||||
|  |       .prepare( | ||||||
|  |         `SELECT description, due_date, group_id, created_by 
 | ||||||
|  |          FROM tasks WHERE id = ?` | ||||||
|  |       ) | ||||||
|  |       .get(id) as any; | ||||||
|  | 
 | ||||||
|  |     expect(row.due_date).toBe(due); | ||||||
|  |     expect(row.group_id).toBe(groupId); | ||||||
|  |     expect(row.created_by).toBe(createdBy); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('inserta asignaciones y evita duplicados por usuario', () => { | ||||||
|  |     const creator = ensureUserExists('555000001@s.whatsapp.net', memDb)!; | ||||||
|  |     const assigneeA = ensureUserExists('555000002@s.whatsapp.net', memDb)!; | ||||||
|  |     const assigneeB = ensureUserExists('555000003@s.whatsapp.net', memDb)!; | ||||||
|  | 
 | ||||||
|  |     const id = TaskService.createTask( | ||||||
|  |       { | ||||||
|  |         description: 'Organizar reunión', | ||||||
|  |         created_by: creator, | ||||||
|  |         due_date: null, | ||||||
|  |         group_id: null, | ||||||
|  |       }, | ||||||
|  |       [ | ||||||
|  |         { user_id: assigneeA, assigned_by: creator }, | ||||||
|  |         { user_id: assigneeA, assigned_by: creator }, // duplicado
 | ||||||
|  |         { user_id: assigneeB, assigned_by: creator }, | ||||||
|  |       ] | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const count = memDb | ||||||
|  |       .prepare( | ||||||
|  |         `SELECT COUNT(*) AS c FROM task_assignments WHERE task_id = ?` | ||||||
|  |       ) | ||||||
|  |       .get(id) as any; | ||||||
|  | 
 | ||||||
|  |     expect(count.c).toBe(2); | ||||||
|  | 
 | ||||||
|  |     const users = memDb | ||||||
|  |       .prepare( | ||||||
|  |         `SELECT user_id FROM task_assignments WHERE task_id = ? ORDER BY user_id` | ||||||
|  |       ) | ||||||
|  |       .all(id) as any[]; | ||||||
|  | 
 | ||||||
|  |     expect(users.map(u => u.user_id)).toEqual([assigneeA, assigneeB].sort()); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('hace rollback si una asignación viola FK (usuario inexistente)', () => { | ||||||
|  |     const creator = ensureUserExists('555010101@s.whatsapp.net', memDb)!; | ||||||
|  | 
 | ||||||
|  |     expect(() => | ||||||
|  |       TaskService.createTask( | ||||||
|  |         { | ||||||
|  |           description: 'Tarea con fallo', | ||||||
|  |           created_by: creator, | ||||||
|  |           due_date: null, | ||||||
|  |           group_id: null, | ||||||
|  |         }, | ||||||
|  |         [ | ||||||
|  |           // Usuario no existente: no llamamos ensureUserExists
 | ||||||
|  |           { user_id: '555099999', assigned_by: creator }, | ||||||
|  |         ] | ||||||
|  |       ) | ||||||
|  |     ).toThrow(); | ||||||
|  | 
 | ||||||
|  |     const counts = memDb | ||||||
|  |       .prepare( | ||||||
|  |         `SELECT 
 | ||||||
|  |            (SELECT COUNT(*) FROM tasks) AS tasks_count, | ||||||
|  |            (SELECT COUNT(*) FROM task_assignments) AS assigns_count` | ||||||
|  |       ) | ||||||
|  |       .get() as any; | ||||||
|  | 
 | ||||||
|  |     expect(counts.tasks_count).toBe(0); | ||||||
|  |     expect(counts.assigns_count).toBe(0); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('lanza error si created_by no existe (FK) y no inserta la tarea', () => { | ||||||
|  |     // No llamamos a ensureUserExists para este created_by
 | ||||||
|  |     const nonExisting = '555123123'; | ||||||
|  | 
 | ||||||
|  |     expect(() => | ||||||
|  |       TaskService.createTask( | ||||||
|  |         { | ||||||
|  |           description: 'No debería insertarse', | ||||||
|  |           created_by: nonExisting, | ||||||
|  |           due_date: null, | ||||||
|  |           group_id: null, | ||||||
|  |         }, | ||||||
|  |         [] | ||||||
|  |       ) | ||||||
|  |     ).toThrow(); | ||||||
|  | 
 | ||||||
|  |     const count = memDb | ||||||
|  |       .prepare(`SELECT COUNT(*) AS c FROM tasks`) | ||||||
|  |       .get() as any; | ||||||
|  | 
 | ||||||
|  |     expect(count.c).toBe(0); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
					Loading…
					
					
				
		Reference in New Issue