diff --git a/__tests__/api/sprint-tasks.test.ts b/__tests__/api/sprint-tasks.test.ts index 3e895c7..591a159 100644 --- a/__tests__/api/sprint-tasks.test.ts +++ b/__tests__/api/sprint-tasks.test.ts @@ -5,6 +5,9 @@ vi.mock('@/lib/prisma', () => ({ sprint: { findFirst: vi.fn(), }, + task: { + findMany: vi.fn(), + }, }, })) @@ -18,9 +21,16 @@ 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}`, story_id: 'story-1', priority: 1, sort_order: n, status: 'TO_DO' } +} + function makeRequest(sprintId = 'sprint-1', limit?: number): [Request, { params: Promise<{ id: string }> }] { const url = limit ? `http://localhost/api/sprints/${sprintId}/tasks?limit=${limit}` @@ -37,26 +47,84 @@ function makeRequest(sprintId = 'sprint-1', limit?: number): [Request, { params: describe('GET /api/sprints/:id/tasks', () => { beforeEach(() => { vi.clearAllMocks() + mockAuth.mockResolvedValue({ userId: 'user-1', isDemo: false }) + mockPrisma.sprint.findFirst.mockResolvedValue(SPRINT) }) - // TC-ST-01 - it.todo('returns 401 when no token provided') - - // TC-ST-03 - it.todo('returns 404 when sprint is not accessible') - - // TC-ST-04 - it.todo('returns 404 for another user\'s sprint') - // TC-ST-05 - it.todo('applies default limit of 10 when no limit param given') + 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.todo('respects custom limit param') + 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.todo('handles limit=1 boundary') + 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.todo('returns empty array when sprint has no tasks') + 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: 'TO_DO', + }) + }) })