952a0e9b49
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
107 lines
4.8 KiB
TypeScript
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();
|
|
});
|
|
});
|