import { describe, it, expect, vi, beforeEach } from 'vitest' vi.mock('@/lib/prisma', () => ({ prisma: { product: { findFirst: vi.fn() }, story: { findMany: 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]/cross-sprint-blocks/route' const mockPrisma = prisma as unknown as { product: { findFirst: ReturnType } story: { findMany: ReturnType } } const mockAuth = authenticateApiRequest as unknown as ReturnType function makeRequest(url: string) { return new Request(url) } describe('GET /api/products/[id]/cross-sprint-blocks', () => { beforeEach(() => { vi.clearAllMocks() mockPrisma.product.findFirst.mockReset() mockPrisma.story.findMany.mockReset() mockAuth.mockReset().mockResolvedValue({ userId: 'user-1' }) }) it('returns blocking sprint info per story for happy path', async () => { mockPrisma.product.findFirst.mockResolvedValue({ id: 'p1' }) mockPrisma.story.findMany.mockResolvedValue([ { id: 'story-1', sprint: { id: 'sprint-x', code: 'SP-X' }, }, { id: 'story-2', sprint: { id: 'sprint-y', code: 'SP-Y' }, }, ]) const req = makeRequest( 'http://localhost/api/products/p1/cross-sprint-blocks?excludeSprintId=sp-1&pbiIds=pbiA', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) expect(res.status).toBe(200) const body = await res.json() expect(body).toEqual({ 'story-1': { sprintId: 'sprint-x', sprintName: 'SP-X' }, 'story-2': { sprintId: 'sprint-y', sprintName: 'SP-Y' }, }) }) it('rejects when pbiIds is missing', async () => { const req = makeRequest( 'http://localhost/api/products/p1/cross-sprint-blocks?excludeSprintId=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/cross-sprint-blocks?pbiIds=', ) 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/cross-sprint-blocks?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/cross-sprint-blocks?pbiIds=pbiA', ) const res = await GET(req, { params: Promise.resolve({ id: 'p1' }) }) expect(res.status).toBe(401) }) it('passes NOT excludeSprintId to prisma when provided', async () => { mockPrisma.product.findFirst.mockResolvedValue({ id: 'p1' }) mockPrisma.story.findMany.mockResolvedValue([]) const req = makeRequest( 'http://localhost/api/products/p1/cross-sprint-blocks?excludeSprintId=sp-active&pbiIds=pbiA', ) await GET(req, { params: Promise.resolve({ id: 'p1' }) }) const callArg = mockPrisma.story.findMany.mock.calls[0][0] as { where: Record } expect(callArg.where).toMatchObject({ pbi_id: { in: ['pbiA'] }, product_id: 'p1', sprint_id: { not: null }, NOT: { sprint_id: 'sp-active' }, sprint: { status: 'OPEN' }, }) }) })