test(05-review): add CR-01/WR-01 reminder pruning tests and IN-01 actor-name tests

This commit is contained in:
Lucas Berger
2026-06-09 22:30:03 -04:00
parent 50da9b3bca
commit 1044de57ae
2 changed files with 157 additions and 23 deletions
@@ -14,7 +14,7 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
// Mock DB
// Mock DB — the factory is hoisted; per-test configuration uses mockReturnValueOnce
vi.mock('../../src/db/client.js', () => ({
db: {
select: vi.fn(),
@@ -27,20 +27,22 @@ vi.mock('../../src/lib/pushDispatcher.js', () => ({
}))
// ---------------------------------------------------------------------------
// Helper: build a mock db.select chain.
// dispatchEventChange now calls Promise.all([actorQuery, subsQuery]).
// The first select is for the actor name (users table, returns [{displayName}]),
// the second is for push subscriptions (returns sub rows).
// Helper: configure db.select for two sequential calls used by dispatchEventChange.
//
// dispatchEventChange calls Promise.all([actorQuery, subsQuery]):
// 1st select → actor name query: .from().where().limit() → [{ displayName }] | []
// 2nd select → subscriptions query: .from().where() → sub rows
//
// We configure the mock BEFORE importing eventChangeDispatcher.js (since each
// test does vi.resetModules + fresh import).
// ---------------------------------------------------------------------------
function mockDbSelect(
function setupDbMock(
db: { select: ReturnType<typeof vi.fn> },
actorDisplayName: string | null,
subRows: Array<{ id: number; userId: number; endpoint: string; p256dh: string; auth: string }>,
) {
// Two sequential .select() calls via Promise.all:
// First call → actor name query (select.from.where.limit → [{ displayName }])
// Second call → subscriptions query (select.from.where → subRows)
db.select
// 1st call: actor name query (select.from.where.limit)
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
@@ -50,6 +52,7 @@ function mockDbSelect(
}),
}),
})
// 2nd call: subscriptions query (select.from.where)
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue(subRows),
@@ -59,7 +62,9 @@ function mockDbSelect(
describe('eventChangeDispatcher — trigger conditions (D-04)', () => {
beforeEach(() => {
vi.clearAllMocks()
// resetAllMocks clears call history AND resets mockReturnValueOnce queues,
// preventing leaked once-queues from test N polluting test N+1.
vi.resetAllMocks()
vi.resetModules()
})
@@ -69,7 +74,7 @@ describe('eventChangeDispatcher — trigger conditions (D-04)', () => {
const { dispatchEventChange } = await import('../../src/lib/eventChangeDispatcher.js')
const otherUserSub = { id: 2, userId: 2, endpoint: 'https://push.example.com/sub/2', p256dh: 'x', auth: 'y' }
mockDbSelect(vi.mocked(db), 'Lucas', [otherUserSub])
setupDbMock(vi.mocked(db), 'Lucas', [otherUserSub])
await dispatchEventChange(
{ uid: 'new-event-uid', title: 'Doctor appointment', operation: 'create' },
@@ -85,7 +90,7 @@ describe('eventChangeDispatcher — trigger conditions (D-04)', () => {
const { dispatchEventChange } = await import('../../src/lib/eventChangeDispatcher.js')
const otherUserSub = { id: 2, userId: 2, endpoint: 'https://push.example.com/sub/2', p256dh: 'x', auth: 'y' }
mockDbSelect(vi.mocked(db), 'Lucas', [otherUserSub])
setupDbMock(vi.mocked(db), 'Lucas', [otherUserSub])
await dispatchEventChange(
{
@@ -105,9 +110,9 @@ describe('eventChangeDispatcher — trigger conditions (D-04)', () => {
const { dispatchPush } = await import('../../src/lib/pushDispatcher.js')
const { dispatchEventChange } = await import('../../src/lib/eventChangeDispatcher.js')
// isMeaningfulChange returns false before DB is queried — no select needed.
// But set up a mock just in case (won't be called).
mockDbSelect(vi.mocked(db), 'Lucas', [
// isMeaningfulChange returns false before DB is queried — select won't be called.
// Set up mock anyway as a no-op guard.
setupDbMock(vi.mocked(db), 'Lucas', [
{ id: 2, userId: 2, endpoint: 'https://push.example.com/sub/2', p256dh: 'x', auth: 'y' },
])
@@ -129,10 +134,9 @@ describe('eventChangeDispatcher — trigger conditions (D-04)', () => {
const { dispatchPush } = await import('../../src/lib/pushDispatcher.js')
const { dispatchEventChange } = await import('../../src/lib/eventChangeDispatcher.js')
// DB returns only the actor's own subscription (userId 1).
// The ne() filter in the SQL should exclude it, but here we simulate that
// the application-level filter also catches it.
mockDbSelect(vi.mocked(db), 'Lucas', [
// DB's ne() filter already excludes the actor; simulate that the DB returns
// only the actor's own sub (userId=1) to test the application-level guard.
setupDbMock(vi.mocked(db), 'Lucas', [
{ id: 1, userId: 1, endpoint: 'https://push.example.com/sub/1', p256dh: 'x', auth: 'y' },
])
@@ -151,7 +155,7 @@ describe('eventChangeDispatcher — trigger conditions (D-04)', () => {
const { dispatchEventChange } = await import('../../src/lib/eventChangeDispatcher.js')
const otherUserSub = { id: 2, userId: 2, endpoint: 'https://push.example.com/sub/2', p256dh: 'x', auth: 'y' }
mockDbSelect(vi.mocked(db), 'Lucas', [otherUserSub])
setupDbMock(vi.mocked(db), 'Lucas', [otherUserSub])
await dispatchEventChange(
{ uid: 'event-uid-4', title: 'Dentist', operation: 'create' },
@@ -169,7 +173,7 @@ describe('eventChangeDispatcher — trigger conditions (D-04)', () => {
const { dispatchEventChange } = await import('../../src/lib/eventChangeDispatcher.js')
const otherUserSub = { id: 2, userId: 2, endpoint: 'https://push.example.com/sub/2', p256dh: 'x', auth: 'y' }
mockDbSelect(vi.mocked(db), null, [otherUserSub]) // actor row absent → null
setupDbMock(vi.mocked(db), null, [otherUserSub]) // actor row absent → empty array
await dispatchEventChange(
{ uid: 'event-uid-5', title: 'Soccer', operation: 'create' },