import { Boom } from '@hapi/boom'; import { createWhatsAppSession } from './session'; import * as fs from 'fs/promises'; // Capture connection.update handler so tests can trigger it let connectionUpdateHandler: (update: any) => Promise; // These are referenced inside jest.mock factories, so they must be declared // before the mock calls. jest.mock is hoisted but the factory runs lazily. const mockGroupFetch = jest.fn(); const mockEnd = jest.fn(); const mockEvOn = jest.fn(); // The baileys default export is a factory function; we need __esModule: true // so that the `import makeWASocket from '...'` interop works correctly. jest.mock('@whiskeysockets/baileys', () => { const mockEvOnInner = jest.fn().mockImplementation((event: string, handler: any) => { if (event === 'connection.update') connectionUpdateHandler = handler; }); return { __esModule: true, default: jest.fn().mockReturnValue({ ev: { on: mockEvOnInner }, end: jest.fn(), groupFetchAllParticipating: jest.fn().mockResolvedValue({}), }), useMultiFileAuthState: jest.fn().mockResolvedValue({ state: {}, saveCreds: jest.fn() }), fetchLatestBaileysVersion: jest.fn().mockResolvedValue({ version: [2, 0, 0] }), DisconnectReason: { loggedOut: 401 }, }; }); jest.mock('qrcode-terminal', () => ({ generate: jest.fn() })); jest.mock('fs/promises', () => ({ rm: jest.fn().mockResolvedValue(undefined), mkdir: jest.fn().mockResolvedValue(undefined), })); // eslint-disable-next-line @typescript-eslint/no-require-imports const baileys = require('@whiskeysockets/baileys'); describe('createWhatsAppSession', () => { beforeEach(() => { // Clear all mock state (calls, instances, results) between tests jest.clearAllMocks(); // Re-apply implementations after clearAllMocks wipes them const freshEvOn = jest.fn().mockImplementation((event: string, handler: any) => { if (event === 'connection.update') connectionUpdateHandler = handler; }); baileys.default.mockReturnValue({ ev: { on: freshEvOn }, end: jest.fn(), groupFetchAllParticipating: jest.fn().mockResolvedValue({}), }); baileys.useMultiFileAuthState.mockResolvedValue({ state: {}, saveCreds: jest.fn() }); baileys.fetchLatestBaileysVersion.mockResolvedValue({ version: [2, 0, 0] }); (fs.rm as jest.Mock).mockResolvedValue(undefined); (fs.mkdir as jest.Mock).mockResolvedValue(undefined); }); it('calls onQr with raw QR string when QR event fires', async () => { const onQr = jest.fn(); await createWhatsAppSession('acc_1', '/sessions/1', jest.fn(), jest.fn(), jest.fn(), undefined, onQr); await connectionUpdateHandler({ qr: 'test-qr-string' }); expect(onQr).toHaveBeenCalledWith('test-qr-string'); }); it('calls onStatus with "connected" when connection opens', async () => { const onStatus = jest.fn(); await createWhatsAppSession('acc_1', '/sessions/1', jest.fn(), jest.fn(), jest.fn(), undefined, undefined, onStatus); await connectionUpdateHandler({ connection: 'open' }); expect(onStatus).toHaveBeenCalledWith('connected', undefined); }); it('calls onStatus with "disconnected" on non-logout close', async () => { const onStatus = jest.fn(); await createWhatsAppSession('acc_1', '/sessions/1', jest.fn(), jest.fn(), jest.fn(), undefined, undefined, onStatus); const error = new Boom('Connection lost', { statusCode: 408 }); await connectionUpdateHandler({ connection: 'close', lastDisconnect: { error } }); expect(onStatus).toHaveBeenCalledWith('disconnected'); }); it('calls onStatus with "logged_out" on loggedOut close', async () => { const onStatus = jest.fn(); await createWhatsAppSession('acc_1', '/sessions/1', jest.fn(), jest.fn(), jest.fn(), undefined, undefined, onStatus); const error = new Boom('Logged out', { statusCode: 401 }); await connectionUpdateHandler({ connection: 'close', lastDisconnect: { error } }); expect(onStatus).toHaveBeenCalledWith('logged_out'); }); it('deletes session directory on loggedOut', async () => { await createWhatsAppSession('acc_1', '/sessions/1', jest.fn(), jest.fn(), jest.fn()); const error = new Boom('Logged out', { statusCode: 401 }); await connectionUpdateHandler({ connection: 'close', lastDisconnect: { error } }); expect(fs.rm).toHaveBeenCalledWith('/sessions/1', { recursive: true, force: true }); expect(fs.mkdir).toHaveBeenCalledWith('/sessions/1', { recursive: true }); }); it('does not delete session directory on non-logout close', async () => { await createWhatsAppSession('acc_1', '/sessions/1', jest.fn(), jest.fn(), jest.fn()); const error = new Boom('Timeout', { statusCode: 408 }); await connectionUpdateHandler({ connection: 'close', lastDisconnect: { error } }); expect(fs.rm).not.toHaveBeenCalled(); }); });