14 — QA Checklist (Feature Flags 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 the framework + shared 00-shared/10. Every check derives from a specific backend behavior so failure analysis is direct.
1. Functional
-
GET /feature-flagsreturns all tenant flags sorted bykeyasc (feature-flags.service.ts:11), plain array, nometa. -
GET /feature-flags?module=channelsreturns only matchingmodule(feature-flag.repository.ts:24-26). -
GET /feature-flags/enabledreturns onlyenabled:truedocs (feature-flag.repository.ts:28-30). -
GET /feature-flags/:keyreturns the doc; unknown/soft-deleted key → 404RESOURCE_NOT_FOUND("Feature flag "" not found." — feature-flags.service.ts:29). -
PUT /feature-flagswith new key creates (upsert:true,feature-flag.repository.ts:38-42); same key updates (merge). -
Upsert persists exactly
enabled+label— description/module silently dropped (feature-flag.repository.ts:40): verify the editor warning banner exists and no "saved" claim is made for those fields. -
Upsert with missing
keyor non-booleanenabled→ 400VALIDATION_ERRORwithdetails[](update-feature-flag.dto.ts:5-11). -
PUT /feature-flags/bulk(array body) applies sequentially (feature-flags.service.ts:40-44); client shows per-row results; a 5xx mid-list leaves earlier items applied (no rollback) → "retry failed" works. -
Bulk body with one invalid item → whole-body 400 with array-item errors in
details[]. -
DELETE /feature-flags/:keysoft-deletes (flag disappears from all three list endpoints); second delete of same key → 404 (feature-flags.service.ts:47-52). - Delete → re-create same key → expect E11000 → 500 today (OQ-4); QA must verify the current error copy is shown, not a raw crash.
-
Org overlay:
PATCH /organizations/:id/feature-flagsreplaces the whole map (organizations.service.ts:149-151) — QA: PATCH{a:true}then{b:true}→ais gone. Merge-first client behavior verified. -
Flag written via
PUT /feature-flagsdoes not appear inorg.metadata.featureFlagsand vice versa (two disconnected surfaces — OQ-2). -
Cross-tenant: org A's flag list never contains org B's keys (scoped reads,
base.repository.ts:20-30). -
JWT-only gate: flag endpoints work with a valid JWT of any role (no
@Permissions— OQ-5) — QA documents current behavior; UI affordances still followfeature-flags.*(target contract). -
Rate limit: > 100 req/min on flags → 429 (
rate-limit.constants.ts:6).
2. Visual / layout
-
List groups by
module; null-module flags land in an "Ungrouped" bucket (schema allows absentmodule,feature-flag.schema.ts:21-22). - Dark/light parity: switches, selected rows, bulk results (no hardcoded colors).
-
Long keys (
a.very.long.module.namespace.key) wrap/ellipsize at 200% text scale without clipping. - Bulk results row = icon + reason text (never color-only, 09 §9 shared).
3. Accessibility (00-shared/09)
- TalkBack: browse → toggle → detail → editor save → bulk apply, fully operable without sight.
- VoiceOver separately.
-
Switch rows announce key + state (
Semantics(toggled:)); live region announces "Saving…" and bulk progress/results. - Editor: labels linked, first-invalid focus jump, error announced.
- Gated (hidden) features are removed from semantics — screen readers never read a disabled feature.
- Keyboard-only desktop: chips → rows → FAB order, visible focus ring.
-
Contrast: body 4.5:1, chrome 3:1;
monokeys ≥ 4.5:1 onsurfaceVariant. - Reduced motion: no stagger/hero/shake.
4. Performance
- List load (cold) skeleton ≤ 200 ms, content ≤ 2 s (00-shared/10 §1).
-
Toggle round-trip p95 < 1 s (single
findOneAndUpdate,feature-flag.repository.ts:38-42). -
Bulk ≤ 20 flags < 3 s (sequential loop,
feature-flags.service.ts:40-44). -
FeatureFlagsCubitTTL refresh does not rebuild gated screens wholesale (selector-based rebuilds). - No duplicate parallel flag requests on screen open + TTL tick.
5. Offline & network (10 §2)
- Offline → list renders last-good + banner; switches disabled (no silent writes).
- Gating offline → last-good set; no "disabled" flash from error states.
- 5xx on toggle → rollback + snackbar with requestId.
- 429 → countdown, no auto-retry.
-
Reconnect → immediate
FeatureFlagsCubit.refresh().
6. Tablet / desktop
- Master-detail list↔detail at ≥ 840 px.
- Hover row tint, tooltips (switch disabled reason), right-click menu.
- Keyboard: Space toggles switch; Ctrl/Cmd+N new; Esc closes sheet/dialog.
7. Localization
- All strings via i18n keys; module names/labels are tenant data (rendered as-is, not translated).
- Date/time locale for createdAt/updatedAt.
- Long localized strings in editor warning banner reflow at 200% scale.
8. Permissions
- Server: flag endpoints accept any JWT today (OQ-5) — QA confirms and files the gap.
-
Client: routes/affordances hide without
feature-flags.read; toggle/edit/bulk/delete withoutfeature-flags.update/.delete(target contract — UI works either way). -
Org overlay uses
organization.update-adjacent gating (org settings context).
9. Dark mode
-
Full visual pass; selected rows, bulk ✓/✕, warning banner (
tertiaryContainer) contrast.
10. Animations
- Tokens only; reduced-motion respected; toggle rollback never animates a false state.
11. Security
-
Keys/values never logged; no tenant data in analytics events (only key names where agreed
(proposed)). - Flag values not cached in plain-text prefs — in-memory only (13 §5).
-
No tenantId sent in bodies (server derives from JWT; upsert uses
requireTenantId(),feature-flag.repository.ts:37).
12. Server-backed edge mirrors
- 404 detail for deleted flag → "Flag not found — it may have been deleted".
- E11000 re-create → friendly error (OQ-4).
- Full-replace org overlay preserved client-side (merge-before-PATCH).
-
isEnabled('anything')with empty catalog → false everywhere (fail-closed,feature-flags.service.ts:24) — gated screens all hidden on fresh tenant.
QA scripts
- Golden per component:
AppFlagRow(on/off/pending/selected/disabled-perm), editor (create/edit/error-banner), bulk sheet (idle/applying/results), light/dark × 3 sizes. - E2E (device-cloud P0): login admin → browse flags → toggle → bulk 3 → delete with warning → re-create attempt (expect graceful error today) → verify gated screen hides.
- Gating journey: teacher logs in on tenant with
channels.smsoff → SMS entry absent; admin enables → within TTL it appears (or on pull-to-refresh immediately).