diff --git a/apps/worker/src/whatsapp/session-pool.test.ts b/apps/worker/src/whatsapp/session-pool.test.ts new file mode 100644 index 0000000..3daeb4d --- /dev/null +++ b/apps/worker/src/whatsapp/session-pool.test.ts @@ -0,0 +1,76 @@ +import { WhatsAppSessionPool } from './session-pool'; + +// Mock createWhatsAppSession so we don't need a real WhatsApp connection +jest.mock('./session', () => ({ + createWhatsAppSession: jest.fn().mockResolvedValue({ + sendMessage: jest.fn().mockResolvedValue({}), + logout: jest.fn().mockResolvedValue({}), + }), +})); + +describe('WhatsAppSessionPool', () => { + let pool: WhatsAppSessionPool; + + beforeEach(() => { + pool = new WhatsAppSessionPool(); + jest.clearAllMocks(); + }); + + it('add() stores a session by accountId', async () => { + await pool.add('acc_1', './sessions/1', jest.fn(), jest.fn(), jest.fn()); + expect(pool.get('acc_1')).toBeDefined(); + }); + + it('get() returns undefined for unknown accountId', () => { + expect(pool.get('acc_unknown')).toBeUndefined(); + }); + + it('getAll() returns the sessions map', async () => { + await pool.add('acc_1', './sessions/1', jest.fn(), jest.fn(), jest.fn()); + expect(pool.getAll().size).toBe(1); + expect(pool.getAll().has('acc_1')).toBe(true); + }); + + it('sendMessage() throws with informative error when no session exists', async () => { + await expect(pool.sendMessage('acc_missing', '1234@g.us', 'hello')).rejects.toThrow( + 'No active session for account acc_missing', + ); + }); + + it('sendMessage() includes available accounts in error when others exist', async () => { + await pool.add('acc_1', './sessions/1', jest.fn(), jest.fn(), jest.fn()); + await expect(pool.sendMessage('acc_missing', '1234@g.us', 'hello')).rejects.toThrow( + 'Active accounts: [acc_1]', + ); + }); + + it('remove() removes session from pool', async () => { + await pool.add('acc_1', './sessions/1', jest.fn(), jest.fn(), jest.fn()); + expect(pool.get('acc_1')).toBeDefined(); + await pool.remove('acc_1'); + expect(pool.get('acc_1')).toBeUndefined(); + }); + + it('remove() is a no-op for unknown accountId', async () => { + await expect(pool.remove('acc_unknown')).resolves.not.toThrow(); + }); + + it('add() injects accountId into onMessage callback', async () => { + const onMessage = jest.fn(); + const { createWhatsAppSession } = require('./session'); + + let capturedOnMessage: any; + (createWhatsAppSession as jest.Mock).mockImplementationOnce( + (_accountId: string, _path: string, cb: any) => { + capturedOnMessage = cb; + return Promise.resolve({ sendMessage: jest.fn(), logout: jest.fn() }); + }, + ); + + await pool.add('acc_1', './sessions/1', onMessage, jest.fn(), jest.fn()); + + const fakeMsg = { platformMsgId: 'abc', content: 'hi', sourceGroupJid: '1@g.us', senderJid: '2@s.whatsapp.net', accountId: 'acc_1' }; + await capturedOnMessage(fakeMsg); + expect(onMessage).toHaveBeenCalledWith(fakeMsg, 'acc_1'); + }); +}); diff --git a/apps/worker/src/whatsapp/session-pool.ts b/apps/worker/src/whatsapp/session-pool.ts index 816991b..0c8c24f 100644 --- a/apps/worker/src/whatsapp/session-pool.ts +++ b/apps/worker/src/whatsapp/session-pool.ts @@ -42,7 +42,10 @@ export class WhatsAppSessionPool { async sendMessage(accountId: string, groupJid: string, text: string): Promise { const sock = this.sessions.get(accountId); - if (!sock) throw new Error(`No active session for account ${accountId}`); + if (!sock) { + const available = Array.from(this.sessions.keys()).join(', ') || 'none'; + throw new Error(`No active session for account ${accountId}. Active accounts: [${available}]`); + } await sock.sendMessage(groupJid, { text }); }