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.
		
		
		
		
		
			
		
			
				
	
	
		
			158 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			158 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			TypeScript
		
	
| import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'bun:test';
 | |
| import { Database } from 'bun:sqlite';
 | |
| import { initializeDatabase } from '../../../src/db';
 | |
| import { TaskService } from '../../../src/tasks/service';
 | |
| import { ResponseQueue } from '../../../src/services/response-queue';
 | |
| import { AllowedGroups } from '../../../src/services/allowed-groups';
 | |
| 
 | |
| function toIsoSql(d: Date): string {
 | |
|   return d.toISOString().replace('T', ' ').replace('Z', '');
 | |
| }
 | |
| 
 | |
| describe('TaskService - reacción ✅ al completar (Fase 2)', () => {
 | |
|   let memdb: Database;
 | |
|   let envBackup: Record<string, string | undefined>;
 | |
| 
 | |
|   beforeAll(() => {
 | |
|     envBackup = { ...process.env };
 | |
|     memdb = new Database(':memory:');
 | |
|     initializeDatabase(memdb);
 | |
|     (TaskService as any).dbInstance = memdb;
 | |
|     (ResponseQueue as any).dbInstance = memdb;
 | |
|     (AllowedGroups as any).dbInstance = memdb;
 | |
|   });
 | |
| 
 | |
|   afterAll(() => {
 | |
|     process.env = envBackup;
 | |
|     try { memdb.close(); } catch {}
 | |
|   });
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     process.env.NODE_ENV = 'test';
 | |
|     process.env.REACTIONS_ENABLED = 'true';
 | |
|     process.env.REACTIONS_SCOPE = 'groups';
 | |
|     process.env.REACTIONS_TTL_DAYS = '14';
 | |
|     process.env.GROUP_GATING_MODE = 'enforce';
 | |
| 
 | |
|     memdb.exec(`
 | |
|       DELETE FROM response_queue;
 | |
|       DELETE FROM task_origins;
 | |
|       DELETE FROM tasks;
 | |
|       DELETE FROM users;
 | |
|       DELETE FROM allowed_groups;
 | |
|     `);
 | |
|   });
 | |
| 
 | |
|   it('enqueuea ✅ al completar una tarea con task_origins dentro de TTL y grupo allowed', async () => {
 | |
|     const groupId = 'grp-1@g.us';
 | |
|     AllowedGroups.setStatus(groupId, 'allowed');
 | |
| 
 | |
|     const taskId = TaskService.createTask({
 | |
|       description: 'Prueba ✅',
 | |
|       due_date: null,
 | |
|       group_id: groupId,
 | |
|       created_by: '600111222'
 | |
|     });
 | |
| 
 | |
|     // Origen reciente (dentro de TTL)
 | |
|     const msgId = 'MSG-OK-1';
 | |
|     memdb.prepare(`
 | |
|       INSERT INTO task_origins (task_id, chat_id, message_id, created_at)
 | |
|       VALUES (?, ?, ?, ?)
 | |
|     `).run(taskId, groupId, msgId, toIsoSql(new Date()));
 | |
| 
 | |
|     const res = TaskService.completeTask(taskId, '600111222');
 | |
|     expect(res.status).toBe('updated');
 | |
| 
 | |
|     const row = memdb.prepare(`SELECT id, recipient, metadata FROM response_queue ORDER BY id DESC LIMIT 1`).get() as any;
 | |
|     expect(row).toBeTruthy();
 | |
|     expect(String(row.recipient)).toBe(groupId);
 | |
| 
 | |
|     const meta = JSON.parse(String(row.metadata || '{}'));
 | |
|     expect(meta.kind).toBe('reaction');
 | |
|     expect(meta.emoji).toBe('✅');
 | |
|     expect(meta.chatId).toBe(groupId);
 | |
|     expect(meta.messageId).toBe(msgId);
 | |
|   });
 | |
| 
 | |
|   it('no encola ✅ si el origen está fuera de TTL', async () => {
 | |
|     const groupId = 'grp-2@g.us';
 | |
|     AllowedGroups.setStatus(groupId, 'allowed');
 | |
| 
 | |
|     // TTL 7 días para forzar expiración
 | |
|     process.env.REACTIONS_TTL_DAYS = '7';
 | |
| 
 | |
|     const taskId = TaskService.createTask({
 | |
|       description: 'Fuera TTL',
 | |
|       due_date: null,
 | |
|       group_id: groupId,
 | |
|       created_by: '600111222'
 | |
|     });
 | |
| 
 | |
|     const msgId = 'MSG-OLD-1';
 | |
|     const old = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000); // 8 días atrás
 | |
|     memdb.prepare(`
 | |
|       INSERT INTO task_origins (task_id, chat_id, message_id, created_at)
 | |
|       VALUES (?, ?, ?, ?)
 | |
|     `).run(taskId, groupId, msgId, toIsoSql(old));
 | |
| 
 | |
|     const res = TaskService.completeTask(taskId, '600111222');
 | |
|     expect(res.status).toBe('updated');
 | |
| 
 | |
|     const cnt = memdb.prepare(`SELECT COUNT(*) AS c FROM response_queue`).get() as any;
 | |
|     expect(Number(cnt.c)).toBe(0);
 | |
|   });
 | |
| 
 | |
|   it('idempotencia: completar dos veces encola solo un ✅', async () => {
 | |
|     const groupId = 'grp-3@g.us';
 | |
|     AllowedGroups.setStatus(groupId, 'allowed');
 | |
| 
 | |
|     const taskId = TaskService.createTask({
 | |
|       description: 'Idempotencia ✅',
 | |
|       due_date: null,
 | |
|       group_id: groupId,
 | |
|       created_by: '600111222'
 | |
|     });
 | |
| 
 | |
|     const msgId = 'MSG-IDEMP-1';
 | |
|     memdb.prepare(`
 | |
|       INSERT INTO task_origins (task_id, chat_id, message_id, created_at)
 | |
|       VALUES (?, ?, ?, ?)
 | |
|     `).run(taskId, groupId, msgId, toIsoSql(new Date()));
 | |
| 
 | |
|     const r1 = TaskService.completeTask(taskId, '600111222');
 | |
|     const r2 = TaskService.completeTask(taskId, '600111222');
 | |
|     expect(r1.status === 'updated' || r1.status === 'already').toBe(true);
 | |
|     expect(r2.status === 'updated' || r2.status === 'already').toBe(true);
 | |
| 
 | |
|     const rows = memdb.query(`SELECT metadata FROM response_queue`).all() as any[];
 | |
|     expect(rows.length).toBe(1);
 | |
|     const meta = JSON.parse(String(rows[0].metadata || '{}'));
 | |
|     expect(meta.emoji).toBe('✅');
 | |
|   });
 | |
| 
 | |
|   it('enforce: grupo no allowed → no encola ✅', async () => {
 | |
|     const groupId = 'grp-4@g.us';
 | |
|     // Estado por defecto 'pending' (no allowed)
 | |
| 
 | |
|     const taskId = TaskService.createTask({
 | |
|       description: 'No allowed',
 | |
|       due_date: null,
 | |
|       group_id: groupId,
 | |
|       created_by: '600111222'
 | |
|     });
 | |
| 
 | |
|     const msgId = 'MSG-NO-ALLOW-1';
 | |
|     memdb.prepare(`
 | |
|       INSERT INTO task_origins (task_id, chat_id, message_id, created_at)
 | |
|       VALUES (?, ?, ?, ?)
 | |
|     `).run(taskId, groupId, msgId, toIsoSql(new Date()));
 | |
| 
 | |
|     const res = TaskService.completeTask(taskId, '600111222');
 | |
|     expect(res.status === 'updated' || res.status === 'already').toBe(true);
 | |
| 
 | |
|     const cnt = memdb.prepare(`SELECT COUNT(*) AS c FROM response_queue`).get() as any;
 | |
|     expect(Number(cnt.c)).toBe(0);
 | |
|   });
 | |
| });
 |