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

Module-specific checks on top of 00-shared/10_QA_Baseline.md. Every item is verifiable against the backend behaviors cited. Backend facts exact; expected-client column is this module's contract.


1. Functional (core CRUD)

  • Create user happy path → 201 doc; displayName = "<first> <last>" (users.service.ts:62); list shows row after refresh.
  • Create without displayName vs with explicit displayName — explicit preserved.
  • Duplicate email → 409 with message text (users.service.ts:50-54); duplicate phone → 409 (:56-60).
  • Email stored lowercase (user.schema.ts:28-29) — JOHN@X.com displays lowercase.
  • PATCH partial: only changed fields; version increments (base.repository.ts:57-66).
  • PATCH email to another user's email → 409 (users.service.ts:120-126); same email (no-op) passes (:120 guard).
  • Rename first/last → displayName recomputed (:136-139).
  • GET list: q matches firstName/lastName/email/displayName case-insensitive (:92-99); default sort newest-first (:103).
  • Pagination envelope exact: page/limit/totalItems/totalPages/hasNext/hasPrevious (pagination-query.dto.ts:32-55); limit=101 → 400 (Max 100 :18); page=0 → 400 (Min 1 :10).
  • DELETE → row disappears from list & GET :id → 404; re-create same email allowed (no tombstone).
  • Deleted user cannot log in (users.repository.ts:21-25).
  • Status PATCH: all 4 enum values round-trip (user.schema.ts:7-12); invalid value → 400.

2. GDPR erasure

  • POST /users/:id/erasure → doc anonymized: Erased User / erased-<id>@anonymized.invalid, isDeleted:true (users.service.ts:187-197).
  • Purge job enqueued on tenant-purge queue with attempts:3 + exponential backoff 5000 (:200-209) — verify queue contents after call.
  • Worker hard-deletes user doc (tenant-purge.worker.ts:49-56); job idempotent on replay (:26).
  • Erased user 404s everywhere; erasure twice → 404 (not crash).

3. Bulk import — users inline endpoint

C1 — 1,000-row scenario

  • Upload 1,000-row CSV (valid) → 201 {imported:1000, errors:[]}; no timeout at 120 s client budget (00-shared/11 §5); measure server time — sequential per-row DB writes (users.service.ts:250-279) at ~1,000 round-trips; if > 2 min → flag to product (async (planned), PLAN.md 2.7).
  • Memory: file ≤ ~1 MB parsed in-memory; no client OOM in preview isolate.
  • Progress UI honest: phases 1–2 real progress, phase 3 indeterminate (10_Interaction_Specification.md §6).

C2 — Concurrency & idempotency

  • Same file uploaded twice → second run: imported:0, all rows "already exists" (users.service.ts:260-263) — no duplicates created (sequential check+insert + unique index user.schema.ts:83).
  • Two parallel requests with same email → one succeeds; other: either 409-style row error (if it saw the row) or 500 dup-key (race — OQ-14). Record result; server fix (planned) to map to 409.
  • Retry after network loss mid-request → no partial duplicates (each email checked before insert).

C3 — CSV edge cases

  • Empty file / header only → {imported:0, errors:["CSV must have a header row and at least one data row"]} (users.service.ts:238-243) — not an error envelope.
  • Header casing/whitespace: Email, FirstName → normalized (:244-247).
  • first_name/last_name aliases work (:265-266); missing names → Unknown (:265-266).
  • Missing email → Row N: missing email (:256-259); row number = physical line (client preview must agree).
  • In-file duplicate emails → row 2+ flagged (:260-263).
  • Quoted commas "Doe, Jr." → mis-split (naive parser :251); verify the client preview warns before upload (OQ-7); document limitation.
  • BOM/UTF-8: file.buffer.toString('utf-8') (:236) — BOM in first header cell; client strips BOM in preview and warns if server mis-parses.
  • CRLF line endings handled (split('\n') + trim filter :237).
  • 100+ error rows → UI paginates error list; "Download errors" CSV matches row numbers.

C4 — Bulk module (students adapter)

  • POST /bulk/import/students: valid file → report {entity,totalRows,imported,failed,errors[{rowNumber,errors[]}]} (import-adapter.interface.ts:14-25); rowNumber = index+2 (bulk-import.service.ts:46).
  • Missing required column → row error (not crash) (students-import.adapter.ts:41-45).
  • Unknown grade/section/academicYear → per-row ref errors (:113-125); no class for grade+section → error (:138-141).
  • Duplicate admissionNumber / existing email → row errors (:49-56).
  • Malformed CSV → 400 VALIDATION_ERROR (bulk-import.service.ts:31-33).
  • entity=users → 404 "No import adapter" (bulk-import.service.ts:17-20) — UI must not offer it ((planned) users adapter).
  • GET /bulk/export/studentstext/csv + Content-Disposition: attachment (bulk.controller.ts:50-60).

4. Multi-tenant isolation

  • Tenant A user list never shows tenant B rows (base.repository.ts:20-30).
  • Tenant A GET /users/:id with tenant B id → 404 (no leak, 00-shared/07 §3).
  • Same email in two tenants → both created (unique is per-tenant — user.schema.ts:83).
  • Platform admin bypass scopes (base.repository.ts:21-23) — verify token flag only for platform surfaces.

5. Avatar

  • Upload → 200 {avatarFileId}; filename prefixed ${id}- (users.service.ts:219); doc avatarFileId updated (:223-225).
  • Re-upload replaces: previous file deleted best-effort (:227-229).
  • Missing file / wrong field name → multer 400.
  • Oversized image: no server cap (OQ-10) — client enforces ≤ 2 MB; verify graceful client error.

6. Permissions & guards

  • Unauthenticated → 401 on all users routes (JwtAuthGuardusers.controller.ts:31).
  • Known gap: no RBAC permission enforcement on users routes today (OQ-11) — client hides actions via PermissionScoped; re-test the moment server guards land.
  • rbac routes require org_admin (rbac.controller.ts:21-22): HR-only user → 403 on members API → role chips show "—" (OQ-15).
  • Cross-tenant JWT → 403/404 per PLAN.md 1.6 (00-shared/07 §6).

7. Performance & reliability

  • List load (network) ≤ 2 s at 10k users (00-shared/10 §1); scroll 60 fps with avatars.
  • Indexes present: (tenantId,email) unique, (tenantId,phone) partial unique, (tenantId,status), (tenantId,displayName) (user.schema.ts:83-92).
  • No duplicate parallel requests for same query (00-shared/10 §1).
  • Preferences save serialized (one in-flight PATCH).

8. UX & a11y (per 00-shared/09)

  • Status never color-only (icon+label) — 11 §2.
  • Typed confirms: delete and ERASE work; wrong text disables button; barrier tap cancels.
  • TalkBack/VoiceOver: list rows announce name+status; live regions for search count and import progress; keyboard-only flow (list → filters → dialogs).
  • Text scale 2×: no clipping in list rows, error table, dialogs.
  • Reduced motion: no hero/stagger in wizard transitions.
  • Dark mode full pass (badges, error table, dialog contrast ≥ 4.5:1 text).
  • Offline: cached list + banner; import blocked with guidance; write attempts show guidance (00-shared/10 §2).

9. Analytics (proposed, 00-shared/10 §8)

  • Events fire: users.list.search, users.create.submit|success|failure(409), users.import.start|complete|failure, users.status.change, users.erasure.confirm, users.avatar.upload.

10. Release gates

  • Typecheck/lint/tests green (backend unchanged by this doc).
  • Backend behavioral probes above automated where possible (unit: users.service.spec, bulk bulk-import.service.spec.ts exists — extend for import edge cases).
  • Perf budgets, a11y checklist, offline matrix per 00-shared/10.