import { describe, it, expect, vi, beforeEach } from 'vitest' vi.mock('@/lib/prisma', () => ({ prisma: { sprint: { findFirst: vi.fn(), }, task: { findMany: vi.fn(), }, }, })) vi.mock('@/lib/api-auth', () => ({ authenticateApiRequest: vi.fn(), })) import { prisma } from '@/lib/prisma' import { authenticateApiRequest } from '@/lib/api-auth' import { GET as getSprintTasks } from '@/app/api/sprints/[id]/tasks/route' const mockPrisma = prisma as unknown as { sprint: { findFirst: ReturnType } task: { findMany: ReturnType } } const mockAuth = authenticateApiRequest as ReturnType const SPRINT = { id: 'sprint-1', product_id: 'prod-1', status: 'ACTIVE' } function makeTask(n: number) { return { id: `task-${n}`, title: `Task ${n}`, description: null, implementation_plan: null, story_id: 'story-1', priority: 1, sort_order: n, status: 'TO_DO', story: { code: 'ST-1', tasks: [{ id: `task-${n}` }], }, } } function makeRequest(sprintId = 'sprint-1', limit?: number): [Request, { params: Promise<{ id: string }> }] { const url = limit ? `http://localhost/api/sprints/${sprintId}/tasks?limit=${limit}` : `http://localhost/api/sprints/${sprintId}/tasks` return [ new Request(url, { method: 'GET', headers: { Authorization: 'Bearer test-token' }, }), { params: Promise.resolve({ id: sprintId }) }, ] } describe('GET /api/sprints/:id/tasks', () => { beforeEach(() => { vi.clearAllMocks() mockAuth.mockResolvedValue({ userId: 'user-1', isDemo: false }) mockPrisma.sprint.findFirst.mockResolvedValue(SPRINT) }) // TC-ST-05 it('applies default limit of 10 when no limit param is given', async () => { mockPrisma.task.findMany.mockResolvedValue(Array.from({ length: 10 }, (_, i) => makeTask(i + 1))) const res = await getSprintTasks(...makeRequest()) const data = await res.json() expect(res.status).toBe(200) expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ take: 10 }) ) expect(data).toHaveLength(10) }) // TC-ST-06 it('respects custom limit param', async () => { mockPrisma.task.findMany.mockResolvedValue(Array.from({ length: 3 }, (_, i) => makeTask(i + 1))) const res = await getSprintTasks(...makeRequest('sprint-1', 3)) expect(res.status).toBe(200) expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ take: 3 }) ) }) // TC-ST-07 it('handles limit=1 boundary', async () => { mockPrisma.task.findMany.mockResolvedValue([makeTask(1)]) const res = await getSprintTasks(...makeRequest('sprint-1', 1)) const data = await res.json() expect(res.status).toBe(200) expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ take: 1 }) ) expect(data).toHaveLength(1) }) it('clamps limit to max 50', async () => { mockPrisma.task.findMany.mockResolvedValue([]) await getSprintTasks(...makeRequest('sprint-1', 999)) expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ take: 50 }) ) }) // TC-ST-08 it('returns empty array when sprint has no tasks', async () => { mockPrisma.task.findMany.mockResolvedValue([]) const res = await getSprintTasks(...makeRequest()) const data = await res.json() expect(res.status).toBe(200) expect(data).toEqual([]) }) it('returns tasks with expected fields', async () => { mockPrisma.task.findMany.mockResolvedValue([makeTask(1)]) const res = await getSprintTasks(...makeRequest()) const data = await res.json() expect(data[0]).toMatchObject({ id: 'task-1', title: 'Task 1', story_id: 'story-1', priority: 1, sort_order: 1, status: 'todo', }) }) })