Files
tower/apps/worker/src/whatsapp/session.test.ts
T

107 lines
4.8 KiB
TypeScript

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<void>;
// 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();
});
});