test(tasks): add unit tests for PATCH /api/tasks/:id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
89f74f3dca
commit
a6ae9f3ed1
1 changed files with 94 additions and 6 deletions
|
|
@ -22,6 +22,19 @@ const mockPrisma = prisma as unknown as {
|
|||
}
|
||||
const mockAuth = authenticateApiRequest as ReturnType<typeof vi.fn>
|
||||
|
||||
function makeTask(overrides: { userId?: string; membersLength?: number } = {}) {
|
||||
const { userId = 'user-1', membersLength = 0 } = overrides
|
||||
return {
|
||||
id: 'task-1',
|
||||
story: {
|
||||
product: {
|
||||
user_id: userId,
|
||||
members: Array.from({ length: membersLength }, (_, i) => ({ id: `member-${i}` })),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function makeRequest(body: unknown, taskId = 'task-1'): [Request, { params: Promise<{ id: string }> }] {
|
||||
return [
|
||||
new Request(`http://localhost/api/tasks/${taskId}`, {
|
||||
|
|
@ -36,23 +49,98 @@ function makeRequest(body: unknown, taskId = 'task-1'): [Request, { params: Prom
|
|||
describe('PATCH /api/tasks/:id', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockAuth.mockResolvedValue({ userId: 'user-1', isDemo: false })
|
||||
mockPrisma.task.findFirst.mockResolvedValue(makeTask())
|
||||
mockPrisma.task.update.mockResolvedValue({
|
||||
id: 'task-1',
|
||||
status: 'DONE',
|
||||
implementation_plan: null,
|
||||
})
|
||||
})
|
||||
|
||||
// TC-T-06
|
||||
it.todo('returns 400 for invalid status value')
|
||||
it('returns 400 for invalid status value', async () => {
|
||||
const res = await patchTask(...makeRequest({ status: 'INVALID' }))
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
// TC-T-07
|
||||
it.todo('returns 400 when body has no recognized fields')
|
||||
it('returns 400 when body has no recognized fields', async () => {
|
||||
const res = await patchTask(...makeRequest({}))
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('returns 400 when body has only unrecognized fields', async () => {
|
||||
const res = await patchTask(...makeRequest({ unknown_field: 'value' }))
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
// TC-T-08
|
||||
it.todo('updates status only and returns 200')
|
||||
it('updates status only and returns 200 with updated task', async () => {
|
||||
mockPrisma.task.update.mockResolvedValue({ id: 'task-1', status: 'IN_PROGRESS', implementation_plan: null })
|
||||
|
||||
const res = await patchTask(...makeRequest({ status: 'IN_PROGRESS' }))
|
||||
const data = await res.json()
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(data).toMatchObject({ id: 'task-1', status: 'IN_PROGRESS' })
|
||||
expect(mockPrisma.task.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: { status: 'IN_PROGRESS' },
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// TC-T-09
|
||||
it.todo('updates implementation_plan only and returns 200')
|
||||
it('updates implementation_plan only and returns 200', async () => {
|
||||
const plan = 'Step 1: implement. Step 2: test.'
|
||||
mockPrisma.task.update.mockResolvedValue({ id: 'task-1', status: 'TO_DO', implementation_plan: plan })
|
||||
|
||||
const res = await patchTask(...makeRequest({ implementation_plan: plan }))
|
||||
const data = await res.json()
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(data.implementation_plan).toBe(plan)
|
||||
expect(mockPrisma.task.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: { implementation_plan: plan },
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// TC-T-10
|
||||
it.todo('updates both status and implementation_plan and returns 200')
|
||||
it('updates both status and implementation_plan and returns 200', async () => {
|
||||
const plan = 'Full plan here.'
|
||||
mockPrisma.task.update.mockResolvedValue({ id: 'task-1', status: 'DONE', implementation_plan: plan })
|
||||
|
||||
const res = await patchTask(...makeRequest({ status: 'DONE', implementation_plan: plan }))
|
||||
const data = await res.json()
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(data).toMatchObject({ status: 'DONE', implementation_plan: plan })
|
||||
expect(mockPrisma.task.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: { status: 'DONE', implementation_plan: plan },
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// TC-T-11
|
||||
it.todo('allows update when user is a team member of the product')
|
||||
it('allows update when user is a team member (not product owner)', async () => {
|
||||
// task belongs to user-2's product, but user-1 is a member
|
||||
mockAuth.mockResolvedValue({ userId: 'user-1', isDemo: false })
|
||||
mockPrisma.task.findFirst.mockResolvedValue(makeTask({ userId: 'user-2', membersLength: 1 }))
|
||||
mockPrisma.task.update.mockResolvedValue({ id: 'task-1', status: 'DONE', implementation_plan: null })
|
||||
|
||||
const res = await patchTask(...makeRequest({ status: 'DONE' }))
|
||||
expect(res.status).toBe(200)
|
||||
})
|
||||
|
||||
it('all three valid status values are accepted', async () => {
|
||||
for (const status of ['TO_DO', 'IN_PROGRESS', 'DONE'] as const) {
|
||||
mockPrisma.task.update.mockResolvedValue({ id: 'task-1', status, implementation_plan: null })
|
||||
const res = await patchTask(...makeRequest({ status }))
|
||||
expect(res.status).toBe(200)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue