Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

14 — QA Checklist (Search Module)

Module-specific quality gates on top of 00-shared/10_QA_Baseline.md (performance budgets, offline matrix, security checklist, device matrix). Each item is traceable to source.


1. Query contract

  • q is trimmed; whitespace-only q is never sent (server returns data: []search.service.ts:28-30).
  • page ≥ 1, limit ∈ 1..50 (defaults 1/20 — search-query.dto.ts:16-29); values outside → server 400 VALIDATION_ERROR.
  • Debounce fires only after 300 ms of keystroke silence; submit skips it (13 §3).
  • Stale responses dropped (guard token) — rapid typing never renders out-of-order results.

2. Minimum characters

  • Server accepts 1-char queries (no @Min on qsearch-query.dto.ts:6-9); verify 1-char results render and the fallback path does not degrade response time (regex full-scan — search-index.repository.ts:36-50).
  • No query → landing screen, zero network calls.

3. Special characters

  • ( ) [ ] { } * + ? ^ $ | . \ inside q must never 500. Risk: the fallback builds { $regex: q, $options: 'i' } from raw input (search-index.repository.ts:40) — invalid regex throws. Gap: mitigation (proposed) is client-side escaping (08 §1.4).
  • Punctuation in $text queries (ADM-20, Mr. John) — verify expected match behavior vs fallback (tokenization differs — 09 §1).
  • %, &, +, =, UTF-8 (Iñigo, Școala) round-trip through URL encoding and match by substring/word.
  • 100+ char queries: server unbounded (08 §1.2) — confirm p95 latency still within budget (00-shared/10 §1).

4. Tenant isolation

  • Cross-tenant rows never leak: every query uses scopedFilter (search-index.repository.ts:24-26) over BaseSchema.tenantId (base.schema.ts:9-11); upserts key on (tenantId, entityType, entityId) (search-index.repository.ts:84-89).
  • Two tenants searching the same string see disjoint results.
  • entityId from tenant A is not resolvable in tenant B (result rows are pointers into owner modules — 12 §8).

5. Result limits & pagination

  • limit respected (max 50) — server slices via .skip().limit() (search-index.repository.ts:32-33).
  • Pagination walk: meta.hasNext true → page+1 appends; final page stops (pagination-query.dto.ts:46-54).
  • Known bug-behavior (flagged): entityType filtering happens on the fetched page only (search.service.ts:44-49); with limit=20 and 30 Students behind 50 mixed rows, page 1 shows 20 Students but "See all" page 2 may show none/partial — verify drill-down UX copy ("N in this view", 07 §3) and consider a (planned) server-side type filter.

6. Stale data

  • Rename student/teacher/staff/parent/book/user → re-search within seconds shows new title (event → upsert — search-indexer.service.ts:48-96).
  • Soft-delete an entity → row disappears from search (removeByEntity sets isDeletedsearch-index.repository.ts:92-98; scoped filter excludes — base.schema.ts:20-21).
  • Known gap: LeadUpdated, LeadDeleted, AnnouncementUpdated, AnnouncementDeleted are unmapped (search-indexer.service.ts:29-31) → edited/deleted leads & announcements persist stale rows. Verify the (planned) reindex job resolves; until then document behavior.
  • Indexer idempotency: duplicate events upsert, never duplicate rows (search-index.repository.ts:85-89).

7. Fallback parity

  • Force $text failure (test hook) → regex fallback returns results, totalItems consistent per path (search.service.ts:35-41).
  • Fallback order differs (no textScore sort — search-index.repository.ts:49): acceptable, verify no UI regression.

8. Scoring

  • Exact-title match ranks above substring ($text textScore sort — search-index.repository.ts:28-33); score never serialized (search.service.ts:51-57).

9. Security & auth

  • 401 unauthenticated → UNAUTHENTICATED; 403 without search perm (permissions.constants.ts:93) → PERMISSION_DENIED (12 §5).
  • No query text in crash logs or analytics payloads beyond the analytics contract (00-shared/10 §3).
  • Rate limits respected; 429 handled with guidance (00-shared/07 §4).

10. UX & a11y

  • 1-char queries, 300 ms debounce → no input lag, no flicker (prior results dim, 06 §2.2).
  • Zero-results copy quotes raw query; clear CTA returns to landing.
  • Live-region announces result count; group headers are headings; tiles labeled "{title}, {type}, {description}" (07 §2).
  • Offline → banner + cached last results + retry on restore (00-shared/10 §2).
  • Keyboard: / focuses, Esc clears, enter submits (desktop — 06 §6).

11. Performance

  • Search p95 < 1 s (target) — 2 queries per search (search + count, search.service.ts:36-37).
  • No duplicate parallel requests for the same (q, type, page) key (00-shared/10 §1).
  • Long lists: lazy tile rendering, no rebuild storm on pagination append.