/opt/mawid/apps/api/src/whatsapp
Edit: /opt/mawid/apps/api/src/whatsapp/wa-webhook.controller.spec.ts (2684B)
import { BadRequestException, ForbiddenException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createHmac } from 'node:crypto';
import { WaWebhookController } from './wa-webhook.controller';
import type { WaInboundService } from './wa-inbound.service';
const APP_SECRET = 'test-app-secret';
const VERIFY_TOKEN = 'test-verify-token';
function makeController(process = jest.fn()) {
const config = {
get: (key: string) =>
({ WA_APP_SECRET: APP_SECRET, WA_WEBHOOK_VERIFY_TOKEN: VERIFY_TOKEN })[key],
} as unknown as ConfigService;
const inbound = { process } as unknown as WaInboundService;
return { controller: new WaWebhookController(config, inbound), process };
}
function makeRequest(payload: object, rawBodyOverride?: Buffer) {
const rawBody = Buffer.from(JSON.stringify(payload));
return {
rawBody: rawBodyOverride ?? rawBody,
body: payload,
headers: {
'x-hub-signature-256': `sha256=${createHmac('sha256', APP_SECRET).update(rawBody).digest('hex')}`,
},
} as never;
}
describe('WaWebhookController', () => {
describe('GET verify handshake', () => {
it('returns the challenge for the correct token', () => {
const { controller } = makeController();
expect(controller.verify('subscribe', VERIFY_TOKEN, '12345')).toBe('12345');
});
it('rejects a wrong token', () => {
const { controller } = makeController();
expect(() => controller.verify('subscribe', 'wrong', '12345')).toThrow(ForbiddenException);
});
});
describe('POST receive', () => {
const payload = { object: 'whatsapp_business_account', entry: [] };
it('accepts a correctly signed payload', async () => {
const { controller, process } = makeController();
await expect(controller.receive(makeRequest(payload))).resolves.toEqual({ received: true });
expect(process).toHaveBeenCalledWith(payload);
});
it('rejects a tampered payload', async () => {
const { controller, process } = makeController();
// Signature is computed over the original payload, but the delivered raw
// body was altered in transit.
const tampered = makeRequest(
payload,
Buffer.from(JSON.stringify({ ...payload, entry: [{ id: 'evil' }] })),
);
await expect(controller.receive(tampered)).rejects.toThrow(ForbiddenException);
expect(process).not.toHaveBeenCalled();
});
it('rejects an unexpected webhook object', async () => {
const { controller } = makeController();
await expect(controller.receive(makeRequest({ object: 'page' }))).rejects.toThrow(
BadRequestException,
);
});
});
});