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 (Notifications Module)

Verify against notifications.controller.ts, notifications.service.ts, repositories/notifications.repository.ts, notifications.handler.ts. Shared QA baseline in 00-shared/10.


A. Ordering & Pagination

  • List returns createdAt desc — first page newest first (repository.ts:66).
  • meta math: totalPages = max(1, ceil(total/limit)), hasNext = page < totalPages, hasPrevious = page > 1 (pagination-query.dto.ts:46-54).
  • limit clamp: 1..100 (list-notifications.dto.ts:13-19); page ≥ 1.
  • Empty total → { data: [], meta: { totalItems: 0, totalPages: 1, hasNext: false } } — not an error.
  • Insert-on-top between page fetches: offset pagination can duplicate rows across pages — client dedupes by _id (10 §5); confirm no server crash on skip > collection.
  • unreadOnly=true returns only readAt: null (service.ts:45); false/absent returns all.

B. Mark-Read Semantics

  • PATCH /:id/read sets readAt + increments version (repository.ts:40).
  • Cross-user id → 200 null, never 404/403 and never touches the row (service.ts:57).
  • Already-read id → idempotent re-mark is harmless (findOneAndUpdate).
  • read-all only updates readAt: null docs (repository.ts:46-49).
  • Race: user taps item while read-all is in flight → both succeed; final state read. Client must not double-decrement badge (reconcile via server count, 13 §3).

C. Tenant & Soft-Delete Isolation

  • All queries pass through scopedFilter (tenantId + deletedAt) via BaseRepository — verify a second tenant's notifications are invisible (repository.ts:24-26,38-39,47-48,61).
  • Handler writes under synthetic tenant context from the event (handler.ts:81-99) — tenantId must come from the event, never ambient state.
  • Soft-deleted recipient docs: notifications still queryable by id (hard delete not cascaded) — document intended behaviour.

D. Auth & RBAC

  • All 4 routes reject unauthenticated requests (controller.ts:16).
  • Gap: notification.read / notification.update declared (permissions.constants.ts:32-33) but no RBAC guard attached — confirm intended (JWT-only for v1) and log decision.
  • (planned) notification.send must be added to ALL_PERMISSIONS before any send endpoint ships.

E. Announcement Overlap

  • AnnouncementPublished (announcement.service.ts:80-91) does not create inbox notifications today — confirm product intent (separate read receipts announcement.service.ts:95-102 vs inbox). If inbox fan-out is wanted, map the event in EVENT_TYPE_MAP and define recipient expansion (audience resolution is in announcement.service.ts:109-148).

F. Event Fan-Out & Idempotency

  • Handler is onAny — only mapped types create notifications; unknown events are no-ops (handler.ts:71-74). Adding a new event requires an explicit map entry (feature toggle, 09 §5).
  • Gap: NotificationCreatedEvent interface exists (events/notification-events.ts:1-10) but is never emitted — either emit it (audit/websocket use) or delete it.
  • Gap: event-queue-map.ts:10-40 routes ~18 events to the in-app BullMQ queue with no worker — creating notifications for them is (planned); do not half-wire (queue without worker = silent loss, violates Notifications.md:57).
  • Retry/DLQ for notification creation: handler failures are logged only (handler.ts:62-67) — no retry today. Acceptable for in-process events; revisit with queue-based fan-out.
  • Duplicate events (e.g. double EmailVerified emit) create duplicate notifications — document idempotency key if dedupe is required.

G. Volume & Fatigue (planned-feature gate)

  • Before fanning out homework/exam/fee events, ship per-type preferences (Notifications.md:27-28) — see 09 §5.
  • Coaching push (test reminders, DPP, batch start) is gated on mobile-app decision (IMPLEMENTATION_PLAN.md:859).

H. Client UX Checks

  • Badge ≤ 99+ display; hidden on loaded=false (06 §2).
  • Badge agrees with list unread count on inbox open.
  • Offline first-load → error panel + retry; pagination failure → footer retry, list intact (06 §1.6).
  • a11y: unread suffix semantics, badge label, contrast (00-shared/09).
  • Analytics events {module}.{screen}.{action} wired (proposed) (00-shared/10 §8).