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

Acceptance + regression checklist for the bulk import/export surface. Every item is traceable to source. Run against a seeded tenant (grades/sections/ academic years/classes exist) per 00-shared/10_QA_Baseline.md.


1. Functional core

  • POST /api/v1/bulk/import/students with a valid file returns 200 and a report where imported + failed === totalRows (import-adapter.interface.ts:14-25).
  • rowNumber of the first data row is 2 (header = row 1, bulk-import.service.ts:46) — matches the physical file line.
  • Valid row creates: user (name/email), student (admissionNumber, rollNumber), class enrollment, status ACTIVE, admissionDate now (students-import.adapter.ts:62-85; student.service.ts:63-78).
  • Valid row fans out UserCreated → in-app user-created-notification and StudentCreated → in-app student-enrolled (event-queue-map.ts:10,28).
  • One bad row does not abort the batch (bulk-import.service.spec.ts:53-65).

2. File-level robustness (quoted commas, BOM, encoding, whitespace)

  • Quoted commas / quotes: email,"last, Name Jr.",…csv-parse handles RFC-4180 quoting (bulk-import.service.ts:26-30); verify a field containing a comma inside quotes imports intact; unclosed quote → 400 Malformed CSV: could not parse file. (:31-33).
  • BOM: UTF-8 BOM (EF BB BF) — client strips it in the isolate (15 §4); verify direct API call with BOM still parses (csv-parse tolerates it) and that preview == server result.
  • Encoding: UTF-8 only (bulk.controller.ts:47). Windows-1252 accents → client warning "re-encode as UTF-8"; never send mojibake.
  • CRLF vs LF: both accepted (parser handles); verify mixed line endings.
  • Header whitespace: header " firstName" fails required check with Missing required column "firstName". (students-import.adapter.ts:43) — client must block pre-upload (06 §2.2); server behaviour is "row errors", not a crash.
  • Empty lines: skipped (skip_empty_lines:true, :28); header-only file → 400 CSV must include a header row and data. (:34-35).
  • Trailing empty cells / stray columns: unknown columns ignored; blank optional rollNumber → omitted (:79).

3. Validation matrix (per-row, exact strings in 08 §2)

  • Missing each required column → Missing required column "X".
  • Bad email → Invalid email format.
  • Existing admissionNumber → Admission number "X" already exists.
  • Existing email → Email "X" already registered.
  • Unknown academicYear / grade / section → … not found.
  • Grade matched by code too (name OR code, :118-121).
  • Known grade+section but no class → No class found for grade "X" section "Y".
  • No row with an unresolved ref is ever created.

4. Duplicates & idempotent re-upload

  • Same file uploaded twice: second run reports every row as duplicate; zero new users/students; counts consistent.
  • Race fallback: concurrent create of same email → second row reuses the existing user id and still creates the student (students-import.adapter.ts:72-75).
  • Duplicate email + different admissionNumber: email row rejected, student not created.
  • Duplicate admissionNumber + different email: same rejection semantics.

5. Row limits & large files (1000 rows)

  • 1000-row file: all rows processed; report complete; no partial response; response time is the long-pole (sequential awaits, bulk-import.service.ts:45-63).
  • Client caps pick at 1000 rows / ~2 MB (10 §6) — verify warning copy and hard block.
  • Mid-request network drop: client shows partial-upload guidance; a follow-up import reports completed rows as duplicates (06 §4.1a).
  • Rate budget: a large import against the 100/min api tier (00-shared/07 §4) — verify 429 handling on rapid successive uploads.

6. Concurrency

  • Two simultaneous imports from different users: no cross-tenant bleed (all repos tenant-scoped via BaseRepository); duplicates handled per row by validation + race fallback.
  • Import while grades/classes are being edited: worst case = "not found" row errors, never corruption.
  • Import is not transactional (no rollback): partial success is the designed behaviour — report reflects exactly what was created (bulk-import.service.spec.ts:53-65).

7. Entity & error envelope

  • POST /bulk/import/fees → 404 No import adapter for entity "fees". (bulk-import.service.ts:19); client never offers it (only students, students-import.adapter.ts:15); others (planned) (IMPLEMENTATION_PLAN.md:172).
  • Missing multipart file → 400 exact string (bulk.controller.ts:43-46).
  • Unauthenticated → 401; no tenantId from body ever accepted (00-shared/07 §6).

8. Export

  • GET /bulk/export/studentsContent-Type: text/csv, Content-Disposition: attachment; filename="students.csv" (bulk.controller.ts:50-60).
  • Columns admissionNumber, rollNumber, status, admissionDate in order; sorted by admissionNumber asc (students-import.adapter.ts:87-98); dates ISO; empty roster → header-only CSV (bulk-import.service.ts:67-71).
  • Export round-trip sanity: importing the exported roster fails as duplicates (export ≠ template — 04 §6) — expected, documented.

9. Documentation drift (flagged)

  • Shared ledger glossary claims imports are "processed asynchronously by workers" (00-shared/01 §10) — code is synchronous; this doc set is the corrected reference until async lands (planned) (IMPLEMENTATION_PLAN.md:172). Add a red-line note when the async work ships.

10. Client-side (forward-looking)

  • Preview and server agree: same parser + options in the isolate (15 §4) — test one file through both paths, results identical.
  • Error strings displayed verbatim; row numbers match file lines.
  • 429 countdown, offline banner, permission gate, a11y live regions (10 §8, 11 §6).
  • Analytics events fire (proposed).