qwen_2.5_model / src /modules /webhook /webhook.controller.spec.ts
Muhammad Noman
Deploy OpenWA to Hugging Face Spaces
46252cd
Raw
History Blame Contribute Delete
5.54 kB
import { Test, TestingModule } from '@nestjs/testing';
import { Reflector } from '@nestjs/core';
import { WebhookController } from './webhook.controller';
import { WebhooksListController } from './webhooks-list.controller';
import { WebhookService } from './webhook.service';
import { Webhook } from './entities/webhook.entity';
import { Session } from '../session/entities/session.entity';
import { REQUIRED_ROLE_KEY } from '../auth/decorators/auth.decorators';
import { ApiKeyRole } from '../auth/entities/api-key.entity';
/**
* Regression locks for the secret/headers leak and read authorization.
* e2e coverage is deferred (the e2e harness is currently broken),
* so these controller-level unit tests are the regression gate.
*/
function createSecretWebhook(overrides: Partial<Webhook> = {}): Webhook {
return {
id: 'wh-uuid-1',
sessionId: 'sess-1',
url: 'https://example.com/webhook',
events: ['message.received'],
secret: 's3cr3t-hmac-key',
headers: { Authorization: 'Bearer receiver-token' },
filters: null,
active: true,
retryCount: 3,
lastTriggeredAt: null,
createdAt: new Date('2026-01-01T00:00:00.000Z'),
updatedAt: new Date('2026-01-02T00:00:00.000Z'),
session: undefined as unknown as Session,
...overrides,
};
}
describe('Webhook controllers (secret leak + read authz)', () => {
let controller: WebhookController;
let listController: WebhooksListController;
let reflector: Reflector;
let service: jest.Mocked<Partial<WebhookService>>;
beforeEach(async () => {
service = {
create: jest.fn(),
findBySession: jest.fn(),
findOne: jest.fn(),
findAll: jest.fn(),
update: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [WebhookController, WebhooksListController],
providers: [{ provide: WebhookService, useValue: service }],
}).compile();
controller = module.get<WebhookController>(WebhookController);
listController = module.get<WebhooksListController>(WebhooksListController);
reflector = new Reflector();
});
// ── secret/headers must never appear in any response body ──────
it('findOne does not return secret or headers, but keeps safe fields', async () => {
(service.findOne as jest.Mock).mockResolvedValue(createSecretWebhook());
const result = await controller.findOne('sess-1', 'wh-uuid-1');
expect(result).not.toHaveProperty('secret');
expect(result).not.toHaveProperty('headers');
expect(JSON.stringify(result)).not.toContain('s3cr3t');
expect(result.id).toBe('wh-uuid-1');
expect(result.url).toBe('https://example.com/webhook');
expect(result.events).toEqual(['message.received']);
expect(result.active).toBe(true);
});
it('findBySession strips secret/headers from every item', async () => {
(service.findBySession as jest.Mock).mockResolvedValue([
createSecretWebhook(),
createSecretWebhook({ id: 'wh-2' }),
]);
const result = await controller.findBySession('sess-1');
expect(result).toHaveLength(2);
for (const w of result) {
expect(w).not.toHaveProperty('secret');
expect(w).not.toHaveProperty('headers');
}
expect(JSON.stringify(result)).not.toContain('s3cr3t');
});
it('cross-session findAll strips secret/headers', async () => {
(service.findAll as jest.Mock).mockResolvedValue([createSecretWebhook()]);
const result = await listController.findAll();
expect(result[0]).not.toHaveProperty('secret');
expect(result[0]).not.toHaveProperty('headers');
expect(JSON.stringify(result)).not.toContain('Bearer receiver-token');
});
it('create response echoes no secret/headers', async () => {
(service.create as jest.Mock).mockResolvedValue(createSecretWebhook());
const result = await controller.create('sess-1', { url: 'https://example.com/webhook', secret: 's3cr3t-hmac-key' });
expect(result).not.toHaveProperty('secret');
expect(result).not.toHaveProperty('headers');
expect(result.id).toBe('wh-uuid-1');
});
it('update response returns no secret/headers', async () => {
(service.update as jest.Mock).mockResolvedValue(createSecretWebhook({ url: 'https://new.example.com/hook' }));
const result = await controller.update('sess-1', 'wh-uuid-1', { url: 'https://new.example.com/hook' });
expect(result).not.toHaveProperty('secret');
expect(result).not.toHaveProperty('headers');
expect(result.url).toBe('https://new.example.com/hook');
});
// ── read routes require OPERATOR+ (VIEWER β†’ 403 via guard) ─────
it('findBySession requires OPERATOR role', () => {
// eslint-disable-next-line @typescript-eslint/unbound-method -- reading route metadata, not invoking
const role = reflector.get<ApiKeyRole>(REQUIRED_ROLE_KEY, controller.findBySession);
expect(role).toBe(ApiKeyRole.OPERATOR);
});
it('findOne requires OPERATOR role', () => {
// eslint-disable-next-line @typescript-eslint/unbound-method -- reading route metadata, not invoking
const role = reflector.get<ApiKeyRole>(REQUIRED_ROLE_KEY, controller.findOne);
expect(role).toBe(ApiKeyRole.OPERATOR);
});
it('cross-session findAll requires OPERATOR role', () => {
// eslint-disable-next-line @typescript-eslint/unbound-method -- reading route metadata, not invoking
const role = reflector.get<ApiKeyRole>(REQUIRED_ROLE_KEY, listController.findAll);
expect(role).toBe(ApiKeyRole.OPERATOR);
});
});