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

Test plan for the async generate → poll → view flow. Baseline: 00-shared/10. Module facts that drive cases: job doc schema (report-job.schema.ts:21-43), service logic (reports.service.ts:28-160), worker (report.worker.ts:18-35), repository (report-job.repository.ts:21-47), queue name report-generate (queue.constants.ts:10).


1. Functional (client)

  • POST generate returns {jobId, status:"queued"} (reports.service.ts:43); navigates to S4.
  • S4 shows all 4 statuses via poll; terminal stops polling (10 §1).
  • Report card requires student+exam client-side (08 §3.1); server async failure path verified too (reports.service.ts:91).
  • Attendance summary with classId/date range filters correctly (reports.service.ts:117-123); result counts per status match source records (:126-129).
  • Fee summary math: collected/pending/overdue per reports.service.ts:142-152 (OVERDUE status wins — :150).
  • Empty dataset → completed with total:0/empty summary renders empty state, not error (reports.service.ts:131-136).
  • 404 on poll → not-found state; cross-tenant jobId → same 404 (no leak, base.repository.ts:21-23).
  • Retry creates a NEW job; failed job stays in history.
  • Local history (S3) survives app restart; cap 50 (13 §3).

2. Large datasets

  • report_card with max subject count (schema-driven) renders table without jank; tabularFigures on marks (11 §4).
  • attendance_summary over a full academic year: server loops all matching records in memory (reports.service.ts:125-129) — verify response time and that the client handles a large summary map / long period render.
  • fee_summary with >10k invoices: loop :147-152 is O(n) in-process — measure p95; QA flag: blueprint requires streaming for large exports (04-Modules/Reports.md:49), code does not stream yet.
  • 50+ jobs in local history list: lazy list builds, no frame drops.

3. Concurrency

  • Double-tap submit → single job (button disabled in flight, 09 §2).
  • Two clients polling same job concurrently — no conflict (read-only GET).
  • Generate N jobs at once (term-end) — queue drains; no DB contention; {tenantId,status} index used (report-job.schema.ts:47).
  • Same user re-generates same report while previous is processing — independent jobs; results not crossed (jobId-keyed).

4. Retry & DLQ

  • Worker failure (e.g. student not found, reports.service.ts:94) → job failed with readable error (:79-82); user sees error + Retry.
  • BullMQ retries: report-generate queue config (retries/DLQ) verified — worker redeploys mid-job don't lose jobs (verify queue options).
  • DLQ behavior: poisoned job (bad payload) lands in DLQ; tenant gets failed job doc; no silent gap between queue state and report_jobs state (13 §2 — two sources of truth must stay consistent).

5. Reliability / failure injection (server)

  • Stuck processing: worker crash between markProcessing and terminal write leaves processing forever (no requeue/timeout in code — report-job.repository.ts:21-47). Decide: watchdog job or timeout → mark failed. Known gap — test and document.
  • Redis down at POST → 5xx; form kept; no orphan job doc (create happens before queue.add — reports.service.ts:31-41; a queue failure leaves an orphaned queued job doc — verify cleanup behavior).
  • Mongo down at GET → 5xx; client banner + paused poll (10 §4).
  • Scheduler enqueues while worker off — jobs stay queued (BullMQ durable); process when worker returns (scheduler.service.ts:32,163,219).
  • Worker tenant isolation: job for tenant A cannot read tenant B data (report.worker.ts:20-29).

6. Scheduled reports

  • Daily/weekly attendance job → report_jobs doc exists with correct type so executeJob switch handles it (reports.service.ts:64-76; attendance-report.job.ts:13-24). ⚠ verify enqueued payload produces a job doc — the queue job payload {reportType} (attendance-report.job.ts:21) differs from ReportsService.generate's shape; confirm end-to-end.
  • Remove-on-complete retention {age:3600,count:100} (:23) doesn't destroy report_jobs docs (different stores — confirm).

7. Permissions & security

  • report.generate / report.read defined (permissions.constants.ts:39-40).
  • Gap: endpoints enforce JWT only (reports.controller.ts:9) — no @Permissions. Plan: server adds permission decorators; client gates meanwhile (12 §0).
  • tenantId never accepted from body (BaseSchema, repository scoping).
  • Error messages don't leak cross-tenant existence (404 path, :48).

8. Performance budgets

MetricBudget
POST → 201< 500 ms
poll GET< 150 ms p95 (indexed)
fee_summary >10k invoices< 5 s p95 (flag if exceeded)
app frame rate during pollno jank at 2 s cadence
poll load≤ 1 req/2 s/device (jittered, 10 §1)

9. Accessibility (00-shared/09)

  • Status changes announced (live region) without full-card re-announce.
  • 48 dp targets; contrast 4.5:1 (11 §6).
  • ReduceMotion: instant swaps, polling unchanged.

10. Release gates

  • All of 1–3 pass; 4–5 documented decisions (stuck-processing & orphan-queued cases); 7 server change ticket opened for RBAC enforcement.