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() }, idea: { findMany: vi.fn().mockResolvedValue([]) }, }, })) 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 } claudeQuestion: { findMany: ReturnType } } 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() }) import { productAccessFilter } from '@/lib/product-access' const mockProductAccessFilter = productAccessFilter as ReturnType 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() }) it('access-isolation: productAccessFilter wordt met de juiste userId aangeroepen', async () => { // ST-1106: cross-product-isolatie zit in de productAccessFilter-Set die we // bij connect opbouwen. We mocken de filter zodat product.findMany wel // aangeroepen wordt maar geen producten retourneert; daarna stoppen we // vóór de pg-stream-setup (DIRECT_URL ontbreekt → 500). mockGetSession.mockResolvedValue({ userId: 'user-A', isDemo: false }) mockProductAccessFilter.mockReturnValue({ user_id: 'user-A' }) mockPrisma.product.findMany.mockResolvedValue([]) // We laten de stream zelf falen door DIRECT_URL/DATABASE_URL niet te zetten. const before = { ...process.env } delete process.env.DIRECT_URL delete process.env.DATABASE_URL try { const res = await GET(makeReq()) expect(res.status).toBe(500) } finally { process.env.DIRECT_URL = before.DIRECT_URL process.env.DATABASE_URL = before.DATABASE_URL } // De filter is met de echte userId aangeroepen (cross-user lekt niet) expect(mockProductAccessFilter).toHaveBeenCalledWith('user-A') expect(mockPrisma.product.findMany).toHaveBeenCalledWith({ where: { archived: false, user_id: 'user-A' }, select: { id: true }, }) }) }) // 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.