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>
51 lines
1.6 KiB
TypeScript
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.
|