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'
|
|
});
|
|
});
|
|
});
|