14 — QA Checklist (Results Module)
- 1. Envelope & contract conformance
- 2. Publish behaviour & immutability
- 3. Missing-marks handling
- 4. Grade bands (exact boundaries)
- 5. Marks entry validation (404-vs-422 quirk)
- 6. Concurrency & idempotency
- 7. Data quirks (client workarounds)
- 8. Accessibility & responsive
- 9. Performance
- 10. Regression baseline
Acceptance criteria against real server behaviour. Every item cites the code it guards. Reference 00-shared/10_QA_Baseline.md for general baseline + envelope conformance.
1. Envelope & contract conformance
-
Every result endpoint returns the v1 envelope (00-shared/07 §2-3): success
{success,message:"OK",data,meta?,timestamp,requestId}, error witherror.codefrom the allowed set. -
GET /api/v1/results/student/:idandexam-subject/:idreturn arrays indatawith nometa— pagination does not exist on result routes (examination-result.repository.ts:20-28); the client must not page. -
401 without a Bearer token on all result + exam routes (
JwtAuthGuard,result.controller.ts:10).
2. Publish behaviour & immutability
-
POST /api/v1/examinations/:id/publishstampspublishedAton every result row of every subject (examination.service.ts:203-207) and sets examstatus: 'published'(:209-211). -
Publish with zero subjects still succeeds (
subjectIds.lengthguard,:206) — UI must handle "published with no subjects". -
Repeat publish is idempotent — re-stamping
publishedAtmust not error or duplicate rows (examination-result.repository.ts:42-51). - No unpublish endpoint exists — QA confirms client never offers revert.
-
(planned)immutability: after publish,enterMarksstill updates rows (examination.service.ts:139-195has nopublishedAtcheck). Client lock is UI-only; flag as known gap, revisit when server enforces. -
ExamResultsPublishedfires exactly once per publish (:212-219) →in-app/results-published(event-queue-map.ts:27); in-app job is idempotent (worker test).
3. Missing-marks handling
-
Report card with a student missing some subjects:
marksObtained: 0for missing rows (result.service.ts:70), totals still include that subject's maximum (:72),percentageandoverallGradecomputed on the 0-fill (:83-95). -
Data limitation: a real "0" and "not entered" are indistinguishable
(
result.service.ts:70) — UI distinguishes via presence of a row in E1/E2; QA documents this in release notes. -
Student with zero result rows: E1 returns
[](no error); E4 returns 404 only when the exam has no subjects (result.service.ts:51-52).
4. Grade bands (exact boundaries)
-
computeGradeboundaries verified againstresult.service.ts:130-138:90→A+,89.99→A,80→A,79.99→B+,70→B+,69.99→B,60→B,59.99→C,50→C,49.99→D,40→D,39.99→F,0→F. -
Percentage is rounded to 2 decimals (
result.service.ts:83-86) before banding — test 89.995% (rounds to 90 → A+). -
Per-subject
gradeis client-supplied and stored verbatim (examination-subject.dto.ts:57-60,examination.service.ts:157) — server does not compute per-subject grades; QA asserts UI never claims server-computed per-subject grades.
5. Marks entry validation (404-vs-422 quirk)
-
marksObtained > maximumMarks→ server returns 404RESOURCE_NOT_FOUND(examination.service.ts:145-146), not 422BUSINESS_RULE_VIOLATION. Client normalises to "marks cannot exceed maximum" (08/09); QA asserts the user-facing message is correct despite the misleading code. -
Negative marks → 400
VALIDATION_ERROR(Min(0),examination-subject.dto.ts:54). -
Unknown
examSubjectId→ 404 (examination.service.ts:143-144). - Malformed Mongo id → 400.
6. Concurrency & idempotency
-
Two clients upsert the same
(studentId, examinationSubjectId)— service-level check + unique index{tenantId, studentId, examinationSubjectId}(examination-result.schema.ts:31-33) → last-write-wins; noDUPLICATE_RESOURCE(409) is expected on sequential writes. -
True race (both find nothing, both create): one may hit Mongo dup-key
(11000) → surfaces as 5xx; QA verifies the client treats it as "retry — row now exists" and re-reads. Backend fix
(planned): catch 11000 → read existing. - Re-submitting the same payload (offline replay) → identical end state, one row.
-
Cross-tenant id on any path → 404/
[](scoped repo), never another tenant's data.
7. Data quirks (client workarounds)
-
subjectName= raw subject ID (result.service.ts:75) — UI shows mapped names; QA checks no raw IDs leak to screens. - S3 orphan rows (exam soft-deleted → E5 404) are dropped without crash.
-
createdAt/updatedAt/versionpresent on result rows (base.schema.ts); UI may useupdatedAtfor "last saved" display.
8. Accessibility & responsive
- Per-screen a11y checklist (06_Screen_Specifications.md §8 each): table semantics, grade announced as letters ("A plus"), contrast ≥ 4.5:1 on chips (09_Accessibility_Baseline.md §2), charts have text equivalents.
- Responsive breakpoints per 11_Design_System_Mapping.md §6: <600 / 600-719 / ≥720; save bar safe-area on portrait phones.
9. Performance
- S1 grid stays interactive at 200+ students (virtualised — 15_Flutter §Table).
- S5 aggregation of N subjects: ≤ 4 parallel fetches, skeleton progress (13 §6).
- Offline draft replay: 100 rows → ordered, per-row success/failure, no deadlock on partial failure.
10. Regression baseline
- All reads unchanged after publish (publishedAt stamp must not alter E1/E2/E4 shapes).
-
Soft-deleted exam subject → E2
[], marks POST 404, no crash.