5.7.7 Vitest — `Notification` model + fan-in recipients (actor-excluded, mention-deduped, registry-extensible) + the read/mark API + preference gating
Estimate: 30m · Depends on: 5.7.3, 5.7.4, 5.7.6
The integration verification across the 5.7 model + fan-in + API + preference gate (real Postgres, getSession mock only — the standing rule). The cross-cutting assertions the per-subtask tests don't fully own; the Playwright journey is 5.7.8.
Model (5.7.2): cascade behaviour (recipient/work-item delete removes rows; actor delete nulls), the partial-index unread count ignores read rows, the idempotency @@unique rejects a duplicate (eventId × recipient), cursor paging edges (walk, empty page, count).
Fan-in job (5.7.3): a work-item/mentioned event writes one row per eligible recipient with the right data payload + category: direct; the actor is excluded; a user mentioned twice → ONE row; a non-viewer recipient → no row; a replayed event → no duplicate (idempotent). Extensibility: registering a SYNTHETIC event type produces rows through the same handler with no core change (the 5.4/6.6 seam, asserted WITHOUT depending on 5.4/6.6 code); an unregistered type is a clean no-op.
API (5.7.4): list is per-user-scoped + cursor-paged + category-filterable; reading another user's notification → 404; mark-read is idempotent and returns the fresh count; mark-all-read is one bulk op returning zero.
Preference gate (5.7.6): in_app OFF for an event type → the fan-in writes no row for that user; email OFF → the 5.1.6 email job suppresses the mail (asserted via the [EMAIL]/job harness) while in-app still writes — proving BOTH channels read the same resolver; an unset preference resolves to the documented default.
Acceptance criteria
- The Vitest matrix covers the model (cascade / partial-count / idempotency / paging), the fan-in (recipients, actor-excluded, mention-dedupe, view-recheck, idempotent, the registry-extensible synthetic-event case), the API (scoping / 404 / paging / mark-read / mark-all), and the preference gate (in_app + email both honoured, default resolution);
pnpm test:coveragekeeps all 5.7 files ≥90% branch/fn/line. - The extensibility test registers a synthetic event type and sees rows WITHOUT importing Story 5.4 / 6.6 code (proving the seam carries no forward dep).
- The flaky-isolation rule is respected (no reliance on sibling-session DB state).
Context refs
tests/integration/service-test conventions (real DB,getSessionmock only) + the@inngest/testjob harness (the 5.1.6 / 1.6 pattern for fan-in + DLQ assertions)- 5.7.2 / 5.7.3 / 5.7.4 / 5.7.6 — everything under test; the coverage gate (direct empty-input guards on new repo methods)
- Story 5.7 description — the actor-exclude / mention-dedupe / view-recheck / channel-gate rules being asserted