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 (Library Module)

Test matrix for the client + API surface. Server behaviors cited from library.service.ts / book.schema.ts / borrow-record.schema.ts; E2E requires MongoDB + Redis (see AGENTS.md). Focus areas per the module brief: availability race, double-issue, overdue computation, fines.


QA-1. Availability race (concurrent issue of the last copy) — CRITICAL

Server behavior today: issueBook reads book.availableCopies then writes availableCopies - 1 (library.service.ts:108-130) — a read-then-write without atomic conditional update. Two concurrent issues of the last copy can both pass the < 1 check (OQ-7).

CheckExpected
Two parallel POST /books/issue for the same book with availableCopies = 1at least one must fail (or both succeed but availability stays ≥ 0 — today this is a race; assert no negative availableCopies; document as known gap)
Issue when availableCopies = 0 (sequential)409 "No copies available for borrowing." (library.service.ts:109-111)
Client on 409refreshes availability, never auto-retries (13 §4)
Client button during submitdisabled/spinner — prevents client-side double-tap (server has no idempotency, QA-2)
availableCopies never negative after any sequenceproperty test over issue/return mixes

QA-2. Double-issue (same book, same student, twice)

CheckExpected
Issue same (bookId, studentId) while a record is ACTIVEserver allows it today (no active-record check per book+student; only the 5-cap library.service.ts:116-120) — assert & document as gap (planned)
Two quick submits of the same issue formclient blocks (submit lock); if both reach server, cap may still allow → duplicates possible; log for review
Book status after double-issuestatus flips to borrowed when available hits 0 (library.service.ts:125-129)

QA-3. Copy-count integrity

CheckExpected
totalCopies edit by +NavailableCopies +N, floor 0 (library.service.ts:87-90)
Edit totalCopies below currently availableavailable clamps at 0, never negative
Create with totalCopies = 5available = 5 (library.service.ts:41-46); default 1
Delete book with ACTIVE records409 (library.service.ts:97-102); delete succeeds only when no active records; soft-delete (book gone from lists, not physically deleted)

QA-4. Fines — computation and override

CheckExpected
Return on due datefineAmount = 0, fineStatus unset (library.service.ts:159-169)
Return 1 ms after due dateceil(diff/24h) × 5 = 5 for < 24 h late — ceil makes any late return ≥ 1 day (library.service.ts:211-217)
Return 3.5 days lateceil(3.5) × 5 = 20
Return with fineAmount: 0override wins (0, library.service.ts:159) — "waive via zero" path
Return with fineAmount: 999override wins — server trusts client; QA must confirm UI hides override for non-staff (OQ-6)
Fine on ACTIVE record: POST /books/fines/:id/paysucceeds today (no guard) — assert UI gates by fineStatus == pending
Pay twiceidempotent overwrite to PAID (no error) — no double-charge UI implication (units only)
fineStatus: WAIVEDnever produced by any endpoint — don't test as reachable

QA-5. Overdue computation (client-derived)

CheckExpected
OverdueTag shows iff status=='active' && now > dueDatederived rule (13 §5); server never writes OVERDUE (OQ-2)
Days late / fine previewceil((now−dueDate)/day) × 5 matches server rule when the same clock is used; label "estimate"
Clock skew (device vs server)preview differs from server fine at return → UI must show server value after return (authoritative)
Server findOverdue()exists unused (borrow-record.repository.ts:34-43) — plan tests when the endpoint ships (planned)

QA-6. Business-rule 409s (message accuracy)

CheckExpected
Duplicate ISBN409, exact message Book with ISBN "…" already exists. (library.service.ts:37-39)
No copies409 exact copy (:109-111)
5-active cap409 exact copy (:116-120) — boundary: exactly 5 active → block; 4 → allow
Return non-ACTIVE record409 "Book was not actively borrowed." (:155-157)
Unknown book/record404 (:80,153-154,207)
Invalid Mongo id400 VALIDATION_ERROR

QA-7. Envelope, auth, tenant isolation

CheckExpected
Every response wraps {success, message, data, meta?, timestamp, requestId}response-envelope.interceptor.ts:44-52
x-request-id echoedrequestId in body (http-exception.filter.ts:45)
No token → 401JwtAuthGuard (library.controller.ts:22)
Tenant A book invisible to tenant Ball repos tenant-scoped; unique ISBN is per-tenant (book.schema.ts:51)
ISBN duplicate across tenantsallowed (both succeed)

QA-8. Search & pagination

CheckExpected
q matches title/author/ISBN, case-insensitive substringregex $options: 'i' (book.repository.ts:26-30)
No q → all books sorted title asclibrary.service.ts:68-74
page/limit mathmeta matches buildPaginationMeta (library.service.ts:75); last page hasNext = false
Non-numeric page/limitNumber() cast → NaN edge (400 not guaranteed — flag)

QA-9. Client states (offline, rate, permissions)

CheckExpected
Offline with cachecached catalog/borrows render + AppOfflineBanner; writes blocked
429countdown banner, no auto-retry
401 mid-sessiontoken refresh then retry once; fail → sessionExpired
RBAC (planned)with perms enforced: no books.create → FAB hidden; no fines.pay → pay hidden (matrix 02 §6)

QA-10. Events & side effects

CheckExpected
Issue emits BookIssued {bookId, studentId}event bus (library.service.ts:141-148); correlationId/tenantId/actorId populated
Return emits BookReturned incl. fineAmount(:180-191)
issuedBy on the recordnever written (:132-139) — assert field absent in responses; test when fixed (OQ-3)

Severity summary

#AreaSeverityStatus
QA-1availability raceHighserver gap (OQ-7); client: refresh-on-409
QA-2double-issue same book+studentMediumserver gap (planned); client: submit lock
QA-4fine override trustedMediumclient: staff-only UI
QA-5overdue derivedLowserver scan (planned)
QA-6409 messagesHightest verbatim