14 — QA Checklist (Timetable Module)
- 1. Conflict & overlap matrix (the core rule)
- 2. Time / day validation
- 3. Read paths & sorting
- 4. Mutation lifecycle (or lack of it)
- 5. Tenant isolation & security
- 6. Event/side-effect correctness [backend]
- 7. Client QA (per 00-shared/10)
- 8. Release gates (module)
Module-specific additions to 00-shared/10. Items marked [backend] are bugs or gaps derivable from source and should be raised with the API team, not papered over in the UI.
1. Conflict & overlap matrix (the core rule)
| # | Scenario | Expected | Server reality |
|---|---|---|---|
| QA-1 | Same teacher, same day, overlapping time | 409 "Schedule conflict detected" | ✅ $or clause {teacherId, dayOfWeek} + timeOverlaps (timetable.service.ts:17-22,62-69) |
| QA-2 | Same room, same day, overlapping time (both entries have roomId) | 409 | ✅ {roomId, dayOfWeek} clause (timetable.service.ts:20) |
| QA-3 | Same room, same day, overlapping — one entry has no roomId | No conflict (different rooms) | ✅ roomId: undefined is stripped from the query → clause no-ops (timetable.service.ts:20) — verify with E2E |
| QA-4 | Same class, same day, overlapping time (two subjects) | Client highlights only; server accepts | ❌ No classId check (timetable.service.ts:17-22) — [backend] confirm intent (OQ-1); UI must not block |
| QA-5 | Back-to-back: entry A ends 09:00, entry B starts 09:00 | Allowed | ✅ start1 < end2 && start2 < end1 → false (timetable.service.ts:68) |
| QA-6 | Same teacher, different day, same time | Allowed | ✅ day filter (timetable.service.ts:19-20) |
| QA-7 | Boundary: start1 == start2 | Conflict | ✅ overlap true |
| QA-8 | Contained: A 08:00–10:00 vs B 08:30–09:00 | Conflict | ✅ overlap true |
| QA-9 | Two admins race the same slot | Second gets 409 (no corruption) | ✅ create-time check; client pre-flight is advisory |
| QA-10 | Non-zero-padded time ("8:00") vs "09:00" | Broken math (lexical: "8:00" > "09:00") | ❌ @IsString only (create-timetable-entry.dto.ts:27-33) — [backend] add HH:MM pattern; client picker-only input (proposed) |
2. Time / day validation
| # | Scenario | Expected |
|---|---|---|
| QA-11 | dayOfWeek: "sunday" | 400 — @IsEnum(DayOfWeek) (create-timetable-entry.dto.ts:23-25); saturday legal, sunday not (timetable.schema.ts:7-14) |
| QA-12 | endTime <= startTime | Server accepts (no order check) — client blocks pre-submit (proposed); [backend] consider validation |
| QA-13 | Malformed time "9:00 am" | Server accepts (@IsString) — client picker prevents; grid math would be wrong if sent |
| QA-14 | Unknown body key (e.g. tenantId) | 400 — forbidNonWhitelisted (main.ts:50-57) — client never sends tenantId |
3. Read paths & sorting
| # | Scenario | Expected |
|---|---|---|
| QA-15 | GET /timetable?classId= | [] or entries sorted dayOfWeek, startTime (timetable.service.ts:48-53) — client renders as-is, no re-sort |
| QA-16 | GET /timetable?teacherId= | Same sort contract (timetable.service.ts:55-60) |
| QA-17 | Both classId + teacherId | classId wins (timetable.controller.ts:26-27) — client never sends both |
| QA-18 | Bare GET /timetable | [] (timetable.controller.ts:28) — never present as "all entries" |
| QA-19 | ?roomId= filter | 404-ish/ignored — param doesn't exist (timetable.controller.ts:22-25) — room view is client composition; native filter (planned); [backend] |
| QA-20 | Sort with equal dayOfWeek + startTime (duplicate slots) | Insertion order — verify grid handles ties (no flicker) |
4. Mutation lifecycle (or lack of it)
| # | Scenario | Expected |
|---|---|---|
| QA-21 | PATCH /timetable/:id | No endpoint (timetable.controller.ts:10) — editor offers Duplicate+create only; edit UI (planned) — [backend] (OQ-2) |
| QA-22 | DELETE /timetable/:id | No endpoint — soft-delete columns exist on BaseSchema but are unused by the module; delete (planned) |
| QA-23 | Teacher soft-deleted with live timetable entries | Entries remain (no cascade — no delete path; teacher ref dangles) — grid shows "—" name; [backend] consider cascade/substitution (IMPLEMENTATION_PLAN.md:226) |
| QA-24 | Room deleted with live entries | Entries remain; room chip drops (roomId ref unresolved → "—") |
| QA-25 | Entry created for soft-deleted teacher/class/room | Server performs no existence checks on create — only the conflict query runs (timetable.service.ts:16-30) — pickers must only offer live records; [backend] |
| QA-26 | Duplicate identical entry (same everything, same class) | Accepted (no unique index, no dup check) — by design for classes (OQ-1); verify UX intent |
5. Tenant isolation & security
| # | Scenario | Expected |
|---|---|---|
| QA-27 | Cross-tenant entry id in create/read | 404/scope-excluded (base.repository.ts:20-30) — never leak existence (00-shared/07 §3) |
| QA-28 | No tenantId in bodies | Client never sends it (07 §6) |
| QA-29 | Unauthenticated call | 401 → refresh flow |
| QA-30 | RBAC: endpoints carry no timetable.read/create metadata despite perms existing | permissions.constants.ts:44-45 vs timetable.controller.ts:9 — client-side gate only; [backend] add metadata |
6. Event/side-effect correctness [backend]
| # | Item | Evidence |
|---|---|---|
| QA-31 | TimetableEntryCreated emitted but no queue mapping found (event-queue-map.ts) | timetable.service.ts:33-44 — confirm intended consumers (notification/audit/search) or drop emission |
7. Client QA (per 00-shared/10)
| # | Item |
|---|---|
| QA-32 | Grid perf: 6×~10 grid with ≥ 40 slot cards ≤ 60 fps on low-end; lazy row build + RepaintBoundary (15 §9) |
| QA-33 | A11y: slot labels complete (subject/teacher/room/day/time), conflict announced live, Sunday column absent |
| QA-34 | Text scale 2×: slot cards don't clip (subject shortName truncation) |
| QA-35 | Dark mode parity: conflict tokens verified both modes |
| QA-36 | Offline: grid from cache + banner; editor blocked |
| QA-37 | Week nav: offset only re-filters (no server date) — Today resets without refetch |
| QA-38 | Desktop drag: valid/invalid drop targets, no optimistic persistence (10 §2) |
| QA-39 | Analytics (proposed): timetable.entry.{submit,conflict,success}, timetable.class.view (05) |
| QA-40 | Golden tests: TimetableSlotCard ×2×2, ConflictBanner, WeekNavigator |
8. Release gates (module)
-
Backend gaps QA-4/10/12/19/21/22/23/25/30/31 triaged (none block v1 read UI;
editor ships with client-side guards +
(planned)markers) - All envelope error codes map to i18n keys (no raw 5xx text)
- E2E: create entry → conflict 409 → fix → grid shows slot; cross-tenant 404
-
E2E: bare
GET /timetable→[]path never triggered by UI