import { describe, it, expect, vi, beforeEach } from 'vitest' vi.mock('@/lib/prisma', () => ({ prisma: { product: { findFirst: vi.fn() }, story: { groupBy: vi.fn() }, }, })) vi.mock('@/lib/api-auth', () => ({ authenticateApiRequest: vi.fn(), })) vi.mock('@/lib/product-access', () => ({ productAccessFilter: vi.fn().mockReturnValue({}), })) import { prisma } from '@/lib/prisma' import { authenticateApiRequest } from '@/lib/api-auth' import { GET } from '@/app/api/products/[id]/sprint-membership-summary/route' const mockPrisma = prisma as unknown as { product: { findFirst: ReturnType } story: { groupBy: ReturnType } } const mockAuth = authenticateApiRequest as unknown as ReturnType function makeRequest(url: string) { return new Request(url) } describe('GET /api/products/[id]/sprint-membership-summary', () => { beforeEach(() => { vi.clearAllMocks() mockPrisma.product.findFirst.mockReset() mockPrisma.story.groupBy.mockReset() mockAuth.mockReset().mockResolvedValue({ userId: 'user-1' }) }) it('returns counts per PBI for happy path', async () => { mockPrisma.product.findFirst.mockResolvedValue({ id: 'p1' }) mockPrisma.story.groupBy .mockResolvedValueOnce([ { pbi_id: 'pbiA', _count: { _all: 5 } }, { pbi_id: 'pbiB', _count: { _all: 3 } }, ]) .mockResolvedValueOnce([{ pbi_id: 'pbiA', _count: { _all: 2 } }]) const req = makeRequest( 'http://localhost/api/products/p1/sprint-membership-summary?sprintId=sp-1&pbiIds=pbiA,pbiB', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) expect(res.status).toBe(200) const body = await res.json() expect(body).toEqual({ pbiA: { total: 5, inSprint: 2 }, pbiB: { total: 3, inSprint: 0 }, }) }) it('rejects when pbiIds is missing', async () => { const req = makeRequest( 'http://localhost/api/products/p1/sprint-membership-summary?sprintId=sp-1', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) expect(res.status).toBe(400) }) it('rejects when pbiIds is empty', async () => { const req = makeRequest( 'http://localhost/api/products/p1/sprint-membership-summary?sprintId=sp-1&pbiIds=', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) expect(res.status).toBe(400) }) it('rejects when sprintId is missing', async () => { const req = makeRequest( 'http://localhost/api/products/p1/sprint-membership-summary?pbiIds=pbiA', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) expect(res.status).toBe(400) }) it('returns 404 when product is not accessible', async () => { mockPrisma.product.findFirst.mockResolvedValue(null) const req = makeRequest( 'http://localhost/api/products/p1/sprint-membership-summary?sprintId=sp-1&pbiIds=pbiA', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) expect(res.status).toBe(404) }) it('returns auth error when authenticate fails', async () => { mockAuth.mockResolvedValue({ error: 'Niet ingelogd', status: 401 }) const req = makeRequest( 'http://localhost/api/products/p1/sprint-membership-summary?sprintId=sp-1&pbiIds=pbiA', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) expect(res.status).toBe(401) }) it('returns zero counts for PBIs without stories', async () => { mockPrisma.product.findFirst.mockResolvedValue({ id: 'p1' }) mockPrisma.story.groupBy .mockResolvedValueOnce([]) .mockResolvedValueOnce([]) const req = makeRequest( 'http://localhost/api/products/p1/sprint-membership-summary?sprintId=sp-1&pbiIds=pbiA,pbiB', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) const body = await res.json() expect(body).toEqual({ pbiA: { total: 0, inSprint: 0 }, pbiB: { total: 0, inSprint: 0 }, }) }) })