Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

14 - QA Checklist (Webhooks Module)

Test plan for the implemented backend surface. Baseline conventions in 00-shared/10; module items below are specific to webhook delivery semantics. Each item cites its source contract. Severity: C = critical, H = high, M = medium.


1. Endpoint contract (C)

#CheckExpectedSource
E-1CRUD happy pathPOST → 201 doc; GET list newest-first; GET :id; PATCH subset; DELETE → { message: 'Webhook deleted' } and doc gone from listwebhooks.controller.ts:21-50
E-2404sGET/PATCH/DELETE unknown id → 404 'Webhook not found'webhooks.service.ts:39-43, 45-48, 50-53
E-3Permission matrixwebhook.create/read/update/delete enforced per endpoint (permissions.constants.ts:89-92)webhooks.controller.ts:22, 28, 34, 40, 46, 53, 59, 66, 73, 79, 86
E-4Validationempty name/url/secret → 400; events: [] → 400 (ArrayMinSize(1)); bad url → 400; enabled non-boolean → 400create-webhook.dto.ts:11-34
E-5LogsGET :id/logs returns ≤ 50, newest first; includes payload, status, responseCode?, responseBody?webhooks.service.ts:88-93, webhook-delivery-log.schema.ts:12-38
E-6Metricscounts reconcile: total = success + failed + pending for same webhookwebhooks.service.ts:149-154
E-7Tenant isolationtenant B cannot read/update/delete/pause tenant A webhooks (scoped filter)base.repository.ts:20-30

2. Delivery pipeline (C)

#CheckExpectedSource
D-1Event fan-outemitting event X creates one deliver job per enabled webhook subscribed to Xwebhooks.service.ts:55-86, webhook.repository.ts:17-29
D-2No fan-outdisabled / soft-deleted / unsubscribed webhooks receive nothingwebhook.repository.ts:23-28
D-3Timeoutendpoint that sleeps > 10 s → attempt failed (AbortSignal.timeout(10000))webhook-delivery.worker.ts:81
D-4Retry countsfailing endpoint observed 3 job attempts with exponential backoff (5 s, 10 s)webhooks.service.ts:80-83
D-5Status recording2xx → success with responseCode+body; non-2xx → failed; network error → failedwebhook-delivery.worker.ts:84-99
D-6Payload fidelityreceiver gets exactly JSON.stringify(payload) of the emitted event payloadwebhook-delivery.worker.ts:70, 80, domain-event.interface.ts:7
D-7Queue presencequeue named webhook-deliver registered; worker attachedqueue.constants.ts:14, webhooks.module.ts:22, webhook-delivery.worker.ts:9

3. Signature verification (C)

#CheckExpectedSource
S-1Signature correctnessreceiver computes HMAC-SHA256(rawBody, secret) hex and matches X-Webhook-Signaturewebhook-delivery.worker.ts:77, 103-105
S-2Header presenceevery POST has Content-Type: application/json, X-Webhook-Signature, X-Webhook-Eventwebhook-delivery.worker.ts:76-79
S-3Secret mismatchwrong stored secret → signature mismatch (receiver rejects) - expected, verifies schemewebhook.schema.ts:19-20
S-4Secret rotationPATCH secret → subsequent deliveries signed with new secretupdate-webhook.dto.ts:4, webhooks.service.ts:45-48
S-5Known gap (H)no X-Webhook-Timestamp / replay window - document; verification must not assume onewebhook-delivery.worker.ts:103-105

4. Retry & test (H)

#CheckExpectedSource
R-1Retry no-failurePOST :id/retry with zero failed logs → 404 'No failed deliveries to retry'webhooks.service.ts:103-108
R-2Retry payloadretry re-sends the latest failed attempt's eventType + payload verbatimwebhooks.service.ts:110-118
R-3Retry correlationretried job carries correlationId: '' - receiver must tolerate empty stringwebhooks.service.ts:117
R-4Test eventPOST :id/test → attempt with eventType: 'WebhookTested', payload: { test: true, webhookId }webhooks.service.ts:130-138
R-5Async repliesboth endpoints reply { message: '… queued' } before any delivery happenswebhooks.controller.ts:62, 69
R-6Double-record bug (H)on non-2xx the worker records failed twice; second call drops responseCode/responseBody - verify final log has code+body or flag as bugwebhook-delivery.worker.ts:84-99

5. Payload size & robustness (M)

#CheckExpectedSource
P-1Large payloade.g. 1 MB homework/results payload delivered intact (no truncation)webhook-delivery.worker.ts:70-82
P-2Non-JSON-safe payloadpayload containing strings/numbers only (Record<string, unknown>); verify serialization edge caseswebhook-delivery-log.schema.ts:15-16
P-3Response body capture5xx with HTML body - stored raw, client must render escapedwebhook-delivery.worker.ts:88
P-4URL edge casesrequire_tld: false allows http://localhost:3000/hook and IPs - keep receiver in same tenant network (SSRF exposure is a security review item, M)create-webhook.dto.ts:17-18

6. Schema/bookkeeping gaps (M)

#CheckExpectedSource
G-1attemptCountcurrently always 0 - assert current behavior, track as bug when worker incrementswebhook-delivery-log.schema.ts:31-32
G-2lastTriggeredAt / failureCountnever written - surfaces can't sort by activity; trackwebhook.schema.ts:25-29
G-3Log-per-attemptretries create new log rows (not updates) - verify UI handles duplicate-ish rowswebhooks.service.ts:62-67, 84-89
G-4Free-form eventstypo in events silently never fires (exact match, webhook.repository.ts:27) - client picker mitigates; no server whitelistcreate-webhook.dto.ts:20-24
G-5Secret exposureplaintext secret in every CRUD response - flag to reviewers; UI must not log itwebhooks.service.ts:31-47

7. Resilience (M)

#CheckExpectedSource
Q-1Redis down at enqueuequeue.add failure surfaces as 500 - webhook creation still persists (fan-out is post-create)webhooks.service.ts:69-84
Q-2Redis down at deliveryjobs stay in BullMQ; worker reconnect replays - verify no duplicate double-record corruptionwebhooks.module.ts:22
Q-3Idempotencyduplicate event emission produces duplicate deliveries (no dedup) - document for receiverswebhooks.service.ts:55-86
Q-4Metrics under loadcounts via 4 countDocuments - fine at tenant scale; revisit with pagination (planned)webhooks.service.ts:149-154

8. Manual QA script (smoke)

  1. Create webhook with 2 events + test receiver (echo endpoint).
  2. Trigger both events → expect 2 deliveries, X-Webhook-Event matching.
  3. Test ping → WebhookTested log row within seconds.
  4. Point URL at a 500 endpoint → watch 3 attempts → failed; metrics reconcile.
  5. POST :id/retry → verify original payload resent.
  6. Pause → trigger event → no delivery. Resume → trigger → delivery.
  7. Delete → absent from list; logs endpoint 404 (webhook deleted).
  8. Cross-tenant: second tenant cannot see or act on the webhook.