14 — QA Checklist (Audit Module)
- 1. Functional
- 2. Visual / layout
- 3. Accessibility (00-shared/09)
- 4. Performance
- 5. Offline & network (10 §2)
- 6. Tablet / desktop
- 7. Localization
- 8. Permissions
- 9. Dark mode
- 10. Animations
- 11. Security
- 12. Server-backed edge mirrors
- QA scripts
Module QA specific to framework + shared 00-shared/10. Every check derives from a specific backend behavior so failure analysis is direct.
1. Functional
-
List loads
GET /audit-logswith defaultspage=1&limit=50(audit.controller.ts:20-21). -
Response parsed per actual shape
envelope.data.data[]+envelope.data.total(nometa— OQ-2;response-envelope.interceptor.ts:25-32). -
Entries sorted
occurredAtdesc (never asc/unsorted) (audit-log.repository.ts:23). -
Filters
action/entityType/actorIdsent as query params only when set; AND combination works (audit.controller.ts:26-29). -
Filter change resets to page 1; Load more appends;
hasMore = items.length < total. -
entityTypefilter: when data has no such entries, empty state shows helper note (OQ-1), never a raw error. -
Entry detail renders from in-memory entry (no extra request — no
:idendpoint, OQ-4). -
Diff view: changed/added/removed rows computed correctly;
beforeabsent → "Snapshot after action" label (audit.handler.ts:42-43); both absent → empty state. -
Immutability checks: sending
PATCH/DELETEto/api/v1/audit-logs*returns 404 (no routes —audit.controller.ts:17); no edit/delete UI exists; PLAN 19.3 (PLAN.md:191). -
Masking: response
afterfor aUserCreated-style event never containspasswordHash/password/totpSecret/refreshToken/accessToken/token— masked'***'(audit.service.ts:5-12, 20). -
Realtime: while list open, another session performs an action → entry appears
(≤ ~1 s), dedupe by
_id; banner count correct; WS payloads never rendered verbatim (OQ-8). - WS reconnect → resubscribe + silent refetch (merge without duplicates).
-
Non-numeric
page/limithandled: client never sends them; if server 400 arrives → reset page 1 (http-exception.filter.ts:47-55). -
correlationIdpresent on detail and copyable (traceability,audit-log.schema.ts:51-52).
2. Visual / layout
- Desktop table ≥ 1200 px: sticky header, hover, arrow-key nav, horizontal scroll ≤ 1199 px (min-width 720).
- Phone cards + filter bottom sheet; tablet master-detail ≥ 840 px.
-
Diff colors (added
secondary/ removederror/ changedtertiary) pass contrast in light and dark; never color-only (icon + text) (00-shared/09 §9). -
Long
correlationId/entityIdmono strings truncate with tooltip, wrap at 200% text scale (no clipping). -
Dark parity: sticky header
surfaceContainerHigh, JSON onsurfaceContainerLow.
3. Accessibility (00-shared/09)
- TalkBack: full list→filter→detail flow operable without sight; table column headers announced; diff rows labeled "field X changed".
-
VoiceOver separately; keyboard-only desktop (
/focus, arrows, Enter, Esc ). - Realtime append + result count announced via live regions.
- Reduced motion: no stagger/flash (appends fade only).
- Contrast body 4.5:1, icons 3:1.
4. Performance
- List first frame ≤ 300 ms cached / ≤ 2 s network (00-shared/10 §1); skeleton ≤ 200 ms.
-
ListView.builder/virtualized table — no unbounded widget build on 10 000+ rows. - Deep pages (page 50+) load without jank; no duplicate parallel requests for same page (load-more guard).
- JSON tree: long arrays/objects collapsed by default; expand is lazy.
5. Offline & network (10 §2)
- Offline → cached list + banner; Load more disabled; filter changes blocked (no stale queries) with guidance.
- Reconnect → WS resubscribe + refetch, merged without duplicates.
- Slow network × Load more: bottom spinner, no ANR; retry after failure keeps list.
6. Tablet / desktop
- Master-detail at ≥ 840 px (list selection persists in detail pane).
- Right-click context menu = long-press menu; hover tooltips (UTC time, truncated values, full action).
- Enter submits filter; Tab order filter → table → footer; focus ring visible.
7. Localization
-
All strings via i18n keys; date/time via
Intllocale; UTC in tooltip; no hardcoded date formats. - RTL readiness: table/diff/key order logical; no hardcoded alignment.
-
Long localized action strings (never translated — raw
eventType) don't break row height (ellipsis + tooltip).
8. Permissions
-
Client hides route without
audit.read(permissions.constants.ts:54); direct link → 403 screen. -
Known server gap (OQ-5): today any authenticated user can call
GET /audit-logs(JWT-only guard,audit.controller.ts:9) — verify with security owner whether student/parent tokens must be blocked server-side before Phase-5 (docs/IMPLEMENTATION_PLAN.md:241). -
Cross-tenant: no UI path can read another tenant's entries (server scopes by
requireTenantId(),audit.controller.ts:30).
9. Dark mode
- Full visual pass on list/table/detail/diff/JSON; token-only colors.
10. Animations
-
m-*tokens only; reduced-motion respected; no flash on realtime append.
11. Security
-
Masked
'***'values displayed as-is — UI never hints secrets are recoverable. - WS broadcast payloads not logged/cached in display state (OQ-8).
- Copy JSON on detail never includes raw secrets (server-masked only).
-
Retention conflict flagged (OQ-6):
migrate.ts:23TTL 90 days onaudit_logs.occurredAtvs 7-year policy (DATA_RETENTION.md:29) — product/security decision required; QA should re-verify retention after fix.
12. Server-backed edge mirrors
-
totalshown matches count when filters applied (servercountcall,audit.service.ts:48). -
Entry with
metadatarenders nested JSON tree (audit-log.schema.ts:54-55). -
actorTypeother thanuser(futuresystem/api_keywrites) renders badge correctly (audit-log.schema.ts:18-19).
QA scripts
- Golden:
AuditTable,AuditEntryRow,AuditDiffView(changed/added/removed/empty/ nested),JsonTreeView, dark mode, 3 sizes. - E2E (P0): login as org_admin → open audit → filter by action → open entry → verify diff → realtime append from second session → immutability probe (PATCH/DELETE → 404).
- Security smoke: student-role token calls
GET /audit-logs→ record current behavior (allowed today, OQ-5) and confirm Phase-5 enforcement when landed.