Scrum4Me/__tests__/api/notifications-stream.test.ts
Madhura68 009375a131 feat(ST-1104): add user-scoped /api/realtime/notifications + filter solo-route
Twee delen:

1. Solo-route filter (1-regel-fix in app/api/realtime/solo/route.ts):
   - NotifyPayload uitgebreid met entity:'question'
   - shouldEmit returnt direct false bij entity='question'
   Voorkomt dat solo-clients M11 question-events ontvangen (geen lekkage naar
   het Solo-bord; geen onnodig netwerk-verkeer; loose coupling tussen features).

2. Nieuwe SSE-route app/api/realtime/notifications/route.ts:
   - User-scoped (geen ?product_id=); query alle accessible product-IDs één keer
     bij connect via productAccessFilter
   - LISTEN scrum4me_changes; filter entity='question' && product_id ∈ accessible
   - Initial-state-event NA LISTEN actief (race-fix conform M10 ST-1004):
     query open vragen voor deze user's accessible products, stuur als event:state
     met summary (id, story_code/title, assignee_id, question, options, expires_at)
   - Hergebruikt het pg.Client + ReadableStream + heartbeat 25s + hard-close 240s +
     abort-cleanup-pattern uit solo-route

Tests __tests__/api/notifications-stream.test.ts:
- 401 zonder iron-session cookie (en geen DB-call)
- Solo-route filter wordt visueel/E2E gedekt in ST-1108-acceptatie

Quality gates: lint 0 errors, tsc clean, vitest 146/146 (18 files).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 01:15:37 +02:00

51 lines
1.6 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
const { mockGetSession } = vi.hoisted(() => ({ mockGetSession: vi.fn() }))
vi.mock('@/lib/auth', () => ({
getSession: mockGetSession,
}))
vi.mock('@/lib/prisma', () => ({
prisma: {
product: { findMany: vi.fn() },
claudeQuestion: { findMany: vi.fn() },
},
}))
vi.mock('@/lib/product-access', () => ({
productAccessFilter: vi.fn().mockReturnValue({}),
getAccessibleProduct: vi.fn(),
}))
import { prisma } from '@/lib/prisma'
import type { NextRequest } from 'next/server'
import { GET } from '@/app/api/realtime/notifications/route'
const mockPrisma = prisma as unknown as {
product: { findMany: ReturnType<typeof vi.fn> }
claudeQuestion: { findMany: ReturnType<typeof vi.fn> }
}
function makeReq(): NextRequest {
// Minimaal NextRequest-shape voor de auth-pad — we komen niet bij de
// pg-stream-setup omdat de auth-fail vóór dat punt gebeurt.
return { signal: new AbortController().signal } as unknown as NextRequest
}
beforeEach(() => {
vi.clearAllMocks()
})
describe('GET /api/realtime/notifications', () => {
it('401 zonder iron-session cookie, geen DB-call', async () => {
mockGetSession.mockResolvedValue({ userId: undefined, isDemo: false })
const res = await GET(makeReq())
expect(res.status).toBe(401)
expect(mockPrisma.product.findMany).not.toHaveBeenCalled()
})
})
// Solo-route filter (entity='question' uitgesloten) is een 1-regel-fix in
// app/api/realtime/solo/route.ts. Visueel reviewbaar in de diff; full-stream-
// regressie wordt handmatig gedekt in ST-1108-acceptatie.