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.
		
		
		
		
		
			
		
			
				
	
	
		
			113 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			113 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
| import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
 | |
| import { Database } from 'bun:sqlite';
 | |
| import { initializeDatabase } from '../../../src/db';
 | |
| import { ResponseQueue } from '../../../src/services/response-queue';
 | |
| 
 | |
| const ORIGINAL_FETCH = globalThis.fetch;
 | |
| const envBackup = { ...process.env };
 | |
| 
 | |
| describe('ResponseQueue - jobs de reacción (enqueue + sendOne)', () => {
 | |
|   let memdb: Database;
 | |
|   let captured: { url?: string; payload?: any } = {};
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     process.env = {
 | |
|       ...envBackup,
 | |
|       NODE_ENV: 'test',
 | |
|       EVOLUTION_API_URL: 'http://evolution.test',
 | |
|       EVOLUTION_API_INSTANCE: 'instance-1',
 | |
|       EVOLUTION_API_KEY: 'apikey',
 | |
|       RQ_REACTIONS_MAX_ATTEMPTS: '3',
 | |
|     };
 | |
| 
 | |
|     memdb = new Database(':memory:');
 | |
|     memdb.exec('PRAGMA foreign_keys = ON;');
 | |
|     initializeDatabase(memdb);
 | |
| 
 | |
|     (ResponseQueue as any).dbInstance = memdb;
 | |
| 
 | |
|     globalThis.fetch = async (url: RequestInfo | URL, init?: RequestInit) => {
 | |
|       captured.url = String(url);
 | |
|       try {
 | |
|         captured.payload = init?.body ? JSON.parse(String(init.body)) : null;
 | |
|       } catch {
 | |
|         captured.payload = null;
 | |
|       }
 | |
|       return new Response(JSON.stringify({ ok: true }), {
 | |
|         status: 200,
 | |
|         headers: { 'Content-Type': 'application/json' }
 | |
|       });
 | |
|     };
 | |
| 
 | |
|     memdb.exec('DELETE FROM response_queue');
 | |
|     captured = {};
 | |
|   });
 | |
| 
 | |
|   afterEach(() => {
 | |
|     globalThis.fetch = ORIGINAL_FETCH;
 | |
|     process.env = envBackup;
 | |
|     try { memdb.close(); } catch {}
 | |
|   });
 | |
| 
 | |
|   it('enqueueReaction aplica idempotencia por (chatId, messageId, emoji) en ventana 24h', async () => {
 | |
|     await ResponseQueue.enqueueReaction('123@g.us', 'MSG-1', '🤖');
 | |
|     await ResponseQueue.enqueueReaction('123@g.us', 'MSG-1', '🤖'); // duplicado → ignorar
 | |
| 
 | |
|     const cnt = memdb.prepare(`SELECT COUNT(*) AS c FROM response_queue`).get() as any;
 | |
|     expect(Number(cnt.c)).toBe(1);
 | |
| 
 | |
|     // Mismo chat y mensaje, emoji distinto → debe insertar
 | |
|     await ResponseQueue.enqueueReaction('123@g.us', 'MSG-1', '⚠️');
 | |
|     const cnt2 = memdb.prepare(`SELECT COUNT(*) AS c FROM response_queue`).get() as any;
 | |
|     expect(Number(cnt2.c)).toBe(2);
 | |
|   });
 | |
| 
 | |
|   it('sendOne con metadata.kind === "reaction" usa /message/sendReaction y payload esperado', async () => {
 | |
|     const item = {
 | |
|       id: 42,
 | |
|       recipient: '123@g.us',
 | |
|       message: '', // no se usa para reaction
 | |
|       attempts: 0,
 | |
|       metadata: JSON.stringify({ kind: 'reaction', emoji: '🤖', chatId: '123@g.us', messageId: 'MSG-99' }),
 | |
|     };
 | |
| 
 | |
|     const res = await ResponseQueue.sendOne(item as any);
 | |
|     expect(res.ok).toBe(true);
 | |
| 
 | |
|     expect(captured.url?.includes('/message/sendReaction/instance-1')).toBe(true);
 | |
|     expect(captured.payload).toBeDefined();
 | |
|     expect(captured.payload.reaction).toBe('🤖');
 | |
|     expect(captured.payload.key).toEqual({ remoteJid: '123@g.us', fromMe: false, id: 'MSG-99' });
 | |
|   });
 | |
| 
 | |
|   it('sendOne incluye key.participant cuando viene en metadata (grupo, fromMe:false)', async () => {
 | |
|     const item = {
 | |
|       id: 43,
 | |
|       recipient: '120363401791776728@g.us',
 | |
|       message: '',
 | |
|       attempts: 0,
 | |
|       metadata: JSON.stringify({
 | |
|         kind: 'reaction',
 | |
|         emoji: '✅',
 | |
|         chatId: '120363401791776728@g.us',
 | |
|         messageId: 'MSG-100',
 | |
|         participant: '34650861805:32@s.whatsapp.net',
 | |
|         fromMe: false
 | |
|       }),
 | |
|     };
 | |
| 
 | |
|     const res = await ResponseQueue.sendOne(item as any);
 | |
|     expect(res.ok).toBe(true);
 | |
| 
 | |
|     expect(captured.url?.includes('/message/sendReaction/instance-1')).toBe(true);
 | |
|     expect(captured.payload).toBeDefined();
 | |
|     expect(captured.payload.reaction).toBe('✅');
 | |
|     expect(captured.payload.key).toEqual({
 | |
|       remoteJid: '120363401791776728@g.us',
 | |
|       fromMe: false,
 | |
|       id: 'MSG-100',
 | |
|       participant: '34650861805:32@s.whatsapp.net'
 | |
|     });
 | |
|   });
 | |
| });
 |