14 — QA Checklist (Communication Module)
- 1. Audience targeting
- 2. Draft / publish lifecycle
- 3. Read receipts
- 4. Delivery via events / queues
- 5. Expiry & priority
(planned) - 6. List / query behavior
- 7. Client states (per 06)
- 8. API contract conformance
- 9. Visual & a11y
- 10. Data integrity
Test scenarios across both backend (module spec) and client (this design). Every check maps to a source line. Severity: 🔴 blocker, 🟡 major, 🟢 minor.
1. Audience targeting
| # | Severity | Check | Expected / source |
|---|---|---|---|
| 1.1 | 🔴 | ROLE teacher resolves to active members only | org members roles: 'teacher', status: 'active' → userIds (announcement.service.ts:114-119); inactive excluded |
| 1.2 | 🔴 | GRADE by name resolves students of that grade | gradeRepo.findOne({name}) → studentRepo.find({gradeId}) (:120-125); unknown grade ⇒ [] |
| 1.3 | 🔴 | SECTION by name resolves students of that section | sectionRepo.findOne({name}) → students (:126-135) |
| 1.4 | 🟡 | CUSTOM with single string vs array | both handled (:136-143); non-array string wrapped to [value] |
| 1.5 | 🟡 | CUSTOM with invalid ObjectId | new Types.ObjectId(v) throws — unhandled ⇒ 500; gap (:142) — verify error UX |
| 1.6 | 🔴 | ALL type ⇒ targetUserIds = [] | :144-146 — UI must not render "0 recipients"; shows "Everyone" |
| 1.7 | 🔴 | Tenant isolation of audience resolution | grade/section lookups scoped by tenantId (repos) — no cross-tenant leakage |
| 1.8 | 🟢 | Typo'd grade/section name silently broadcasts to nobody | :122,129 — composer preview (planned); backend returns no warning |
| 1.9 | 🟢 | "For me" client filter matches server truth | all ⇒ always; else meId ∈ targetUserIds (announcement.schema.ts:42-43) |
2. Draft / publish lifecycle
| # | Severity | Check | Expected / source |
|---|---|---|---|
| 2.1 | 🔴 | Create ⇒ published: false, createdBy = token user | announcement.service.ts:31-37 |
| 2.2 | 🔴 | Draft never appears in recipient "For me" | client filter published (schema :45-46) |
| 2.3 | 🔴 | Publish idempotent — double publish no-op | early return when published (:68-70) |
| 2.4 | 🔴 | Publish sets publishedAt + resolved targetUserIds | :73-78 |
| 2.5 | 🟡 | Publish 404 for unknown id | NotFoundException (:62-66) |
| 2.6 | 🟡 | Draft cannot be edited (no update route) | recreate only — client dirty-guard, no false "edit" affordance |
| 2.7 | 🟢 | AnnouncementCreated emitted on draft save (not only publish) | :39-51 — downstream must tolerate draft events |
3. Read receipts
| # | Severity | Check | Expected / source |
|---|---|---|---|
| 3.1 | 🔴 | Read adds exactly one receipt per user | $addToSet (announcement.repository.ts:27-31) — idempotent |
| 3.2 | 🔴 | Receipt has userId + readAt | {userId, readAt: now} (:28; announcement.schema.ts:23-29) |
| 3.3 | 🔴 | Mark-read 404 | announcement.service.ts:100 |
| 3.4 | 🟡 | Duplicate read tap (double POST) no-ops | $addToSet + UI disabled while in flight |
| 3.5 | 🔴 | Reads endpoint returns receipts without targets | :104-107 — client must join targetUserIds from list payload |
| 3.6 | 🟡 | Unread count = targetUserIds − readBy.userId | derived client-side; test with partial reads |
| 3.7 | 🟢 | ALL-audience counts hidden | targetUserIds.length === 0 (:144-146) |
4. Delivery via events / queues
| # | Severity | Check | Expected / source |
|---|---|---|---|
| 4.1 | 🔴 | AnnouncementPublished emitted with {announcementId, title, audienceType} | announcement.service.ts:80-91; communication-events.ts:9-13 |
| 4.2 | 🔴 | Gap: event-queue map has no Announcement routes | event-queue-map.ts:6-43 — verify a consumer exists before claiming delivery; otherwise delivery is (planned) |
| 4.3 | 🟡 | correlationId, tenantId, actorId on events | :82-85 — audit continuity |
| 4.4 | 🟢 | Draft event (AnnouncementCreated) does not trigger recipient delivery | payload audienceType only; consumers must key on AnnouncementPublished |
5. Expiry & priority (planned)
| # | Severity | Check | Expected / source |
|---|---|---|---|
| 5.1 | 🟡 | Expiry field absent today — UI must not render it | schema has none (announcement.schema.ts:31-53) |
| 5.2 | 🟢 | When shipped: expired items hidden from feed, flagged on detail | design intent only |
| 5.3 | 🟢 | Priority/pinned ordering affects feed sort | today sort is createdAt: -1 only (announcement.service.ts:59) |
6. List / query behavior
| # | Severity | Check | Expected / source |
|---|---|---|---|
| 6.1 | 🟡 | ?audience= filter equals audience.type | filter['audience.type'] = audience (:58) |
| 6.2 | 🟡 | Sorted createdAt desc, tenant-scoped | :59 + BaseRepository scope (announcement.repository.ts:12-18) |
| 6.3 | 🟢 | Unbounded list (no pagination) — perf test with 1k+ docs | gap, (planned) |
| 6.4 | 🟢 | Index supports feed query | {tenantId, published, createdAt: -1} (announcement.schema.ts:57) |
7. Client states (per 06)
| # | Severity | Check |
|---|---|---|
| 7.1 | 🔴 | Feed: loading / empty / error-offline / loaded each render correctly |
| 7.2 | 🔴 | Offline: banner + last-fetch cache; mark-read queued and flushed (forward-looking) |
| 7.3 | 🟡 | Composer dirty-guard + double-tap publish protection |
| 7.4 | 🟡 | Publish failure keeps draft id; retry doesn't duplicate |
| 7.5 | 🟢 | Receipts sheet auto-refresh (30 s, proposed) doesn't reset scroll |
8. API contract conformance
| # | Severity | Check |
|---|---|---|
| 8.1 | 🔴 | Requests match CreateAnnouncementDto exactly (create-announcement.dto.ts:29-49) — no extra fields |
| 8.2 | 🟡 | Validation errors from class-validator rendered inline per field (00-shared/07) |
| 8.3 | 🟡 | 401 (JWT guard, announcements.controller.ts:11) → global login flow |
| 8.4 | 🟢 | Envelope (success/error) parsed per 00-shared/07 |
9. Visual & a11y
| # | Severity | Check |
|---|---|---|
| 9.1 | 🟡 | Unread dot + badge contrast in light and dark theme (11 §6) |
| 9.2 | 🟡 | No color-only semantics: draft/published/unread all have text labels |
| 9.3 | 🟢 | Composer errors announced via live region; focus moves to first invalid |
| 9.4 | 🟢 | Tablet two-pane feed/detail syncs selection correctly |
10. Data integrity
| # | Severity | Check |
|---|---|---|
| 10.1 | 🔴 | No cross-tenant reads (receipts of tenant A invisible to tenant B) |
| 10.2 | 🟡 | Soft-delete filter (BaseRepository) hides deleted announcements from all lists |
| 10.3 | 🟢 | title/body trimmed on save (announcement.schema.ts:33-37) |