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 .
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).
Check Expected
Two parallel POST /books/issue for the same book with availableCopies = 1 at 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 409 refreshes availability, never auto-retries (13 §4 )
Client button during submit disabled/spinner — prevents client-side double-tap (server has no idempotency, QA-2)
availableCopies never negative after any sequenceproperty test over issue/return mixes
Check Expected
Issue same (bookId, studentId) while a record is ACTIVE server 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 form client blocks (submit lock); if both reach server, cap may still allow → duplicates possible; log for review
Book status after double-issue status flips to borrowed when available hits 0 (library.service.ts:125-129)
Check Expected
totalCopies edit by +NavailableCopies +N, floor 0 (library.service.ts:87-90)
Edit totalCopies below currently available available clamps at 0, never negative
Create with totalCopies = 5 available = 5 (library.service.ts:41-46); default 1
Delete book with ACTIVE records 409 (library.service.ts:97-102); delete succeeds only when no active records; soft-delete (book gone from lists, not physically deleted)
Check Expected
Return on due date fineAmount = 0, fineStatus unset (library.service.ts:159-169)
Return 1 ms after due date ceil(diff/24h) × 5 = 5 for < 24 h late — ceil makes any late return ≥ 1 day (library.service.ts:211-217)
Return 3.5 days late ceil(3.5) × 5 = 20
Return with fineAmount: 0 override wins (0, library.service.ts:159) — "waive via zero" path
Return with fineAmount: 999 override wins — server trusts client ; QA must confirm UI hides override for non-staff (OQ-6)
Fine on ACTIVE record: POST /books/fines/:id/pay succeeds today (no guard) — assert UI gates by fineStatus == pending
Pay twice idempotent overwrite to PAID (no error) — no double-charge UI implication (units only)
fineStatus: WAIVEDnever produced by any endpoint — don't test as reachable
Check Expected
OverdueTag shows iff status=='active' && now > dueDate derived rule (13 §5 ); server never writes OVERDUE (OQ-2)
Days late / fine preview ceil((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)
Check Expected
Duplicate ISBN 409, exact message Book with ISBN "…" already exists. (library.service.ts:37-39)
No copies 409 exact copy (:109-111)
5-active cap 409 exact copy (:116-120) — boundary: exactly 5 active → block; 4 → allow
Return non-ACTIVE record 409 "Book was not actively borrowed." (:155-157)
Unknown book/record 404 (:80,153-154,207)
Invalid Mongo id 400 VALIDATION_ERROR
Check Expected
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 → 401 JwtAuthGuard (library.controller.ts:22)
Tenant A book invisible to tenant B all repos tenant-scoped; unique ISBN is per-tenant (book.schema.ts:51)
ISBN duplicate across tenants allowed (both succeed)
Check Expected
q matches title/author/ISBN, case-insensitive substringregex $options: 'i' (book.repository.ts:26-30)
No q → all books sorted title asc library.service.ts:68-74
page/limit mathmeta matches buildPaginationMeta (library.service.ts:75); last page hasNext = false
Non-numeric page/limit Number() cast → NaN edge (400 not guaranteed — flag)
Check Expected
Offline with cache cached catalog/borrows render + AppOfflineBanner; writes blocked
429 countdown banner, no auto-retry
401 mid-session token refresh then retry once; fail → sessionExpired
RBAC (planned) with perms enforced: no books.create → FAB hidden; no fines.pay → pay hidden (matrix 02 §6 )
Check Expected
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)
# Area Severity Status
QA-1 availability race High server gap (OQ-7); client: refresh-on-409
QA-2 double-issue same book+student Medium server gap (planned); client: submit lock
QA-4 fine override trusted Medium client: staff-only UI
QA-5 overdue derived Low server scan (planned)
QA-6 409 messages High test verbatim