14 — QA Checklist (Rooms Module)
- 1. CRUD — create (E1)
- 2. CRUD — list (E2)
- 3. CRUD — get (E3)
- 4. CRUD — update (E4)
- 5. CRUD — delete (E5)
- 6. Duplicates (create-path contract)
- 7. Capacity validation
- 8. Permission & isolation
- 9. UX / offline / edge
- 10. Accessibility (spot; full baseline 00-shared/09)
- 11. Performance (budgets 00-shared/10 §1)
- 12. Regression suite (minimum)
Feature-level QA for the Rooms module, derived from the implemented backend (
src/modules/rooms/**) and the design docs 01–13. Server gaps are marked(planned); tests that would fail today are marked KNOWN-GAP. Baseline: 00-shared/10 (QA Baseline), 00-shared/09 (Accessibility).
1. CRUD — create (E1)
| # | Check | Expected | Source |
|---|---|---|---|
| 1.1 | POST with valid full payload | 201, doc with injected tenantId, defaults applied (type=classroom, isDeleted=false, version=0) | rooms.service.ts:18-23; room.schema.ts:27-28; base.repository.ts:32-36 |
| 1.2 | POST missing name | 400 VALIDATION_ERROR | create-room.dto.ts:12-14 |
| 1.3 | POST missing code | 400 | create-room.dto.ts:16-18 |
| 1.4 | POST duplicate code in same tenant | 409 "Room code "X" already exists." | rooms.service.ts:19-21 |
| 1.5 | POST same code in different tenant | 201 (per-tenant uniqueness) | room.schema.ts:38 ({tenantId, code} index) |
| 1.6 | POST invalid type | 400 (enum) | create-room.dto.ts:25-28 |
| 1.7 | POST capacity: 0 / -5 / 2.5 | KNOWN-GAP: accepted (no bounds — OQ-7); client blocks <1 (proposed); server @Min(1) (planned) | create-room.dto.ts:20-23 |
| 1.8 | POST body includes tenantId/isDeleted | ignored — overridden by context/scope (never trust body) | base.repository.ts:32-36 |
| 1.9 | POST without JWT | 401 | rooms.controller.ts:19 |
2. CRUD — list (E2)
| # | Check | Expected | Source |
|---|---|---|---|
| 2.1 | GET with defaults | 200, data ≤ 20, meta {page:1,limit:20,totalItems,totalPages,hasNext,hasPrevious} | rooms.controller.ts:32; rooms.service.ts:29-33; pagination-query.dto.ts:32-39 |
| 2.2 | page=2, limit=10 | correct slice; hasNext reflects remainder | rooms.service.ts:29-32 |
| 2.3 | page=0 / negative | KNOWN-GAP: no DTO validation on this route — raw Number() cast (rooms.controller.ts:32-33); page=0 → skip=-10 (Mongoose clamps, but contract unguarded). Shared DTO caps 1–100, not applied here (OQ-3). Server validation (planned) | rooms.controller.ts:32-33; pagination-query.dto.ts:5-19 |
| 2.4 | sort / q params | KNOWN-GAP: ignored — list is Mongo natural order (OQ-3) | rooms.controller.ts:32 |
| 2.5 | Deleted rooms in list | excluded (isDeleted:false scope) | base.repository.ts:20-30 |
| 2.6 | Cross-tenant rows | never visible (tenant scope) | base.repository.ts:24-29 |
| 2.7 | Empty tenant | 200, data: [], totalItems: 0 | rooms.service.ts:29-33 |
3. CRUD — get (E3)
| # | Check | Expected | Source |
|---|---|---|---|
| 3.1 | Existing id | 200 full doc | rooms.service.ts:36-40 |
| 3.2 | Unknown / soft-deleted id | 404 "Room not found." | rooms.service.ts:38; base.repository.ts:20-30 |
| 3.3 | Other tenant's id | 404 (no leak) | base.repository.ts:24-29 |
| 3.4 | Malformed id (not ObjectId) | 400 VALIDATION_ERROR | shared CastError filter |
4. CRUD — update (E4)
| # | Check | Expected | Source |
|---|---|---|---|
| 4.1 | PATCH full valid body | 200 updated doc, version +1 | base.repository.ts:57-66 |
| 4.2 | PATCH partial body (only building) | KNOWN-GAP: 400 — CreateRoomDto requires name+code on PATCH (OQ-6, rooms.controller.ts:44); true partial DTO (planned) | rooms.controller.ts:44 |
| 4.3 | PATCH code → existing code | KNOWN-GAP: 500 (E11000) — no service pre-check (OQ-1); 409 (planned) | rooms.service.ts:42-46; room.schema.ts:38 |
| 4.4 | PATCH unknown id | 404 | rooms.service.ts:44 |
| 4.5 | PATCH type invalid | 400 | create-room.dto.ts:25-28 |
| 4.6 | Update reflection in list/detail | fresh GET shows change (no stale cache server-side) | base.repository.ts:57-66 |
5. CRUD — delete (E5)
| # | Check | Expected | Source |
|---|---|---|---|
| 5.1 | DELETE existing | 200, no payload; doc isDeleted=true, deletedAt set, version +1 | rooms.service.ts:48-51; base.repository.ts:68-74 |
| 5.2 | GET after delete | 404 (soft-deleted hidden) | base.repository.ts:20-30 |
| 5.3 | DELETE twice | second → 404 | rooms.service.ts:50 |
| 5.4 | DELETE unknown / other-tenant id | 404 | rooms.service.ts:50; base.repository.ts:24-29 |
| 5.5 | DELETE room referenced by timetable/booking | KNOWN-GAP: succeeds — no in-use guard (OQ-4); guard (planned) | rooms.service.ts:48-51 |
| 5.6 | Re-create same code after delete | 201 (unique index only blocks live docs; isDeleted doc remains) | room.schema.ts:38; base.repository.ts:20-30 |
6. Duplicates (create-path contract)
| # | Check | Expected |
|---|---|---|
| 6.1 | Same code, same tenant, different case (LAB-02 vs lab-02) | KNOWN-GAP: both persist — index is case-sensitive; client uppercases (proposed); normalized index (planned) (OQ-5/1) |
| 6.2 | Trimmed equality (" A-1 " vs "A-1") | both persist (trim happens at write: room.schema.ts:21-22 — DB stores trimmed; two docs only if created pre-trim) — verify via API |
7. Capacity validation
| # | Check | Expected |
|---|---|---|
| 7.1 | Client input < 1 | blocked with hint (proposed) (OQ-7) |
| 7.2 | Client non-numeric | blocked via numeric keyboard + regex (proposed) |
| 7.3 | Server accepts negative today | KNOWN-GAP (documented; @Min(1) (planned)) |
| 7.4 | Absent capacity | no badge shown; never "Cap 0" |
8. Permission & isolation
| # | Check | Expected |
|---|---|---|
| 8.1 | rooms.read/create/update/delete present in permission catalog | yes — permissions.constants.ts:50-53 |
| 8.2 | Server enforces them | KNOWN-GAP: only JwtAuthGuard (OQ-2, rooms.controller.ts:19); RBAC guard (planned) |
| 8.3 | Client hides create/edit/delete without perms | yes (proposed) — per 12's client contract |
| 8.4 | Tenant isolation on all 5 endpoints | structural (scope in base.repository.ts:20-30) — test with two tenants |
| 8.5 | Platform admin scope bypass | intentional (base.repository.ts:21-23) — platform-only tooling |
9. UX / offline / edge
| # | Check | Expected |
|---|---|---|
| 9.1 | Infinite scroll dedupe | no duplicate rows across pages (dedupe by _id) |
| 9.2 | Pull-to-refresh bypasses cache | fresh page=1 |
| 9.3 | Offline list | cached rows + banner; load-more blocked |
| 9.4 | Offline submit | blocked + draft kept |
| 9.5 | 404 on detail | "Room not found" state, not generic error |
| 9.6 | Empty list | "No rooms yet" + create CTA (gated) |
| 9.7 | Filter no-match | "No rooms match your filters" + clear |
| 9.8 | Delete typed-confirm mismatch | button disabled until exact name match |
| 9.9 | Discard dirty form | confirm dialog; no silent data loss |
10. Accessibility (spot; full baseline 00-shared/09)
| # | Check |
|---|---|
| 10.1 | RoomCard single semantics label; chips labeled |
| 10.2 | Field errors in live regions; focus first invalid |
| 10.3 | Contrast on chips/badges ≥ 4.5:1; touch targets ≥ 44 dp |
| 10.4 | disableAnimations honored (no essential motion) |
11. Performance (budgets 00-shared/10 §1)
| # | Check | Target |
|---|---|---|
| 11.1 | List p95 (server) | < 300 ms (find + count on indexed scope) |
| 11.2 | Detail p95 | < 250 ms |
| 11.3 | Infinite scroll jank | no layout jank; ListView.builder |
| 11.4 | Debounces | search + code-hint 300 ms |
12. Regression suite (minimum)
- Unit:
RoomsService— dup create (409), 404s (findById/update/remove), pagination math (buildPaginationMeta—pagination-query.dto.ts:41-54). - E2E: create → list → get → update → delete → get(404) → list excludes; two-tenant isolation; duplicate code 409.
- Widget
(proposed): list load/empty/error, editor 409 inline, delete typed-confirm, permission-gated FAB.