feat(ST-1112): wire story-promotion into saveTask and PATCH /api/tasks/:id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8b779ccc0b
commit
eb27079ba7
5 changed files with 188 additions and 28 deletions
|
|
@ -18,10 +18,14 @@ vi.mock('@/lib/prisma', () => ({
|
|||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
findMany: vi.fn(),
|
||||
},
|
||||
story: {
|
||||
findFirst: vi.fn(),
|
||||
findUniqueOrThrow: vi.fn(),
|
||||
update: vi.fn(),
|
||||
},
|
||||
$transaction: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
|
|
@ -35,8 +39,14 @@ const mockPrisma = prisma as unknown as {
|
|||
create: ReturnType<typeof vi.fn>
|
||||
update: ReturnType<typeof vi.fn>
|
||||
delete: ReturnType<typeof vi.fn>
|
||||
findMany: ReturnType<typeof vi.fn>
|
||||
}
|
||||
story: { findFirst: ReturnType<typeof vi.fn> }
|
||||
story: {
|
||||
findFirst: ReturnType<typeof vi.fn>
|
||||
findUniqueOrThrow: ReturnType<typeof vi.fn>
|
||||
update: ReturnType<typeof vi.fn>
|
||||
}
|
||||
$transaction: ReturnType<typeof vi.fn>
|
||||
}
|
||||
const mockSession = getIronSession as ReturnType<typeof vi.fn>
|
||||
|
||||
|
|
@ -58,6 +68,10 @@ const STORY = { sprint_id: 'sprint-1' }
|
|||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockSession.mockResolvedValue({ userId: 'user-1', isDemo: false })
|
||||
// Pass-through transaction so saveTask's $transaction wrapper executes its callback inline.
|
||||
mockPrisma.$transaction.mockImplementation(async (run: (tx: typeof prisma) => Promise<unknown>) => {
|
||||
return run(prisma)
|
||||
})
|
||||
})
|
||||
|
||||
// ─── saveTask ────────────────────────────────────────────────────────────────
|
||||
|
|
@ -114,6 +128,47 @@ describe('saveTask — edit (cross-tenant scope)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('saveTask — edit met status-promotie', () => {
|
||||
it('promotes story naar DONE wanneer status flip naar DONE alle siblings DONE maakt', async () => {
|
||||
mockPrisma.task.findFirst.mockResolvedValue({ id: 'task-1', status: 'IN_PROGRESS' })
|
||||
mockPrisma.task.update.mockResolvedValue({
|
||||
id: 'task-1',
|
||||
title: 'Test taak',
|
||||
status: 'IN_PROGRESS',
|
||||
story_id: 'story-1',
|
||||
implementation_plan: null,
|
||||
})
|
||||
// Wanneer de helper draait, gebruikt-ie tx.task.update voor de status-flip.
|
||||
// Dezelfde mock vangt beide updates op; tweede return-value voor de status-update.
|
||||
mockPrisma.task.update.mockResolvedValueOnce({
|
||||
id: 'task-1',
|
||||
title: 'Test taak',
|
||||
status: 'IN_PROGRESS',
|
||||
story_id: 'story-1',
|
||||
implementation_plan: null,
|
||||
}).mockResolvedValueOnce({
|
||||
id: 'task-1',
|
||||
title: 'Test taak',
|
||||
status: 'DONE',
|
||||
story_id: 'story-1',
|
||||
implementation_plan: null,
|
||||
})
|
||||
mockPrisma.task.findMany.mockResolvedValue([{ status: 'DONE' }, { status: 'DONE' }])
|
||||
mockPrisma.story.findUniqueOrThrow.mockResolvedValue({ status: 'IN_SPRINT' })
|
||||
|
||||
const result = await saveTask(
|
||||
{ ...VALID_INPUT, status: 'DONE' },
|
||||
{ taskId: 'task-1', productId: 'p-1' },
|
||||
)
|
||||
|
||||
expect(result).toMatchObject({ ok: true })
|
||||
expect(mockPrisma.story.update).toHaveBeenCalledWith({
|
||||
where: { id: 'story-1' },
|
||||
data: { status: 'DONE' },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('saveTask — create (cross-tenant scope)', () => {
|
||||
it('retourneert forbidden als story buiten scope valt', async () => {
|
||||
mockPrisma.story.findFirst.mockResolvedValue(null)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue