test(ST-cmovhveh3): add pre-check integration tests for materializeIdeaPlanAction
Three new scenarios in ideas-crud.test.ts: - auto-vervang: old PBI deleted in transaction when no executed tasks - conflict-409: returns PBI_HAS_ACTIVE_TASKS:<code> with active tasks - alongside: skips delete and creates new PBI when allowAlongside=true Also adds task.count, pbi.findUnique, pbi.delete to prisma mock.
This commit is contained in:
parent
33a36d616b
commit
cd26caa22e
1 changed files with 68 additions and 2 deletions
|
|
@ -35,7 +35,9 @@ vi.mock('@/lib/prisma', () => ({
|
|||
pbi: {
|
||||
findFirst: vi.fn(),
|
||||
findMany: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
create: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
story: {
|
||||
findMany: vi.fn(),
|
||||
|
|
@ -44,6 +46,7 @@ vi.mock('@/lib/prisma', () => ({
|
|||
task: {
|
||||
findMany: vi.fn(),
|
||||
create: vi.fn(),
|
||||
count: vi.fn(),
|
||||
},
|
||||
$transaction: vi.fn(),
|
||||
$executeRaw: vi.fn().mockResolvedValue(0),
|
||||
|
|
@ -71,9 +74,9 @@ type MockIdea = {
|
|||
ideaLog: { create: ReturnType<typeof vi.fn> }
|
||||
claudeJob: { findFirst: ReturnType<typeof vi.fn>; create: ReturnType<typeof vi.fn>; update: ReturnType<typeof vi.fn> }
|
||||
claudeWorker: { count: ReturnType<typeof vi.fn> }
|
||||
pbi: { findFirst: ReturnType<typeof vi.fn>; findMany: ReturnType<typeof vi.fn>; create: ReturnType<typeof vi.fn> }
|
||||
pbi: { findFirst: ReturnType<typeof vi.fn>; findMany: ReturnType<typeof vi.fn>; findUnique: ReturnType<typeof vi.fn>; create: ReturnType<typeof vi.fn>; delete: ReturnType<typeof vi.fn> }
|
||||
story: { findMany: ReturnType<typeof vi.fn>; create: ReturnType<typeof vi.fn> }
|
||||
task: { findMany: ReturnType<typeof vi.fn>; create: ReturnType<typeof vi.fn> }
|
||||
task: { findMany: ReturnType<typeof vi.fn>; create: ReturnType<typeof vi.fn>; count: ReturnType<typeof vi.fn> }
|
||||
$transaction: ReturnType<typeof vi.fn>
|
||||
$executeRaw: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
|
@ -476,6 +479,69 @@ body
|
|||
})
|
||||
})
|
||||
|
||||
describe('materializeIdeaPlanAction — existing PBI pre-check', () => {
|
||||
const VALID_PLAN = `---
|
||||
pbi:
|
||||
title: New PBI
|
||||
priority: 2
|
||||
stories:
|
||||
- title: Story A
|
||||
priority: 2
|
||||
tasks:
|
||||
- title: Task A1
|
||||
priority: 2
|
||||
---
|
||||
|
||||
body
|
||||
`
|
||||
|
||||
beforeEach(() => {
|
||||
// Use a distinct userId to avoid sharing the rate-limit bucket with the
|
||||
// materializeIdeaPlanAction describe block above.
|
||||
mockSession.userId = 'user-precheck'
|
||||
m.idea.findFirst.mockResolvedValue({
|
||||
id: 'idea-1',
|
||||
status: 'PLAN_READY',
|
||||
product_id: 'prod-1',
|
||||
plan_md: VALID_PLAN,
|
||||
pbi_id: 'old-pbi',
|
||||
})
|
||||
m.pbi.findMany.mockResolvedValue([])
|
||||
m.story.findMany.mockResolvedValue([])
|
||||
m.task.findMany.mockResolvedValue([])
|
||||
m.pbi.findFirst.mockResolvedValue(null)
|
||||
m.pbi.findUnique.mockResolvedValue({ code: 'PBI-X' })
|
||||
m.pbi.create.mockResolvedValue({ id: 'pbi-new', code: 'PBI-2' })
|
||||
m.pbi.delete.mockResolvedValue({})
|
||||
m.story.create.mockResolvedValue({ id: 's-1' })
|
||||
m.task.create.mockResolvedValue({ id: 't-1' })
|
||||
})
|
||||
|
||||
it('auto-vervang: deletes old PBI in transaction when no tasks executed', async () => {
|
||||
m.task.count.mockResolvedValueOnce(0)
|
||||
const r = await materializeIdeaPlanAction('idea-1')
|
||||
expect(r).toMatchObject({ success: true, data: { pbi_id: 'pbi-new' } })
|
||||
expect(m.pbi.delete).toHaveBeenCalledWith({ where: { id: 'old-pbi' } })
|
||||
expect(m.pbi.create).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('conflict-409: returns PBI_HAS_ACTIVE_TASKS when executed tasks exist', async () => {
|
||||
m.task.count.mockResolvedValueOnce(1)
|
||||
const r = await materializeIdeaPlanAction('idea-1')
|
||||
expect(r).toMatchObject({ code: 409, error: 'PBI_HAS_ACTIVE_TASKS:PBI-X' })
|
||||
expect(m.pbi.create).not.toHaveBeenCalled()
|
||||
expect(m.pbi.delete).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('alongside: skips old PBI delete and creates new PBI when allowAlongside=true', async () => {
|
||||
m.task.count.mockResolvedValueOnce(1)
|
||||
const r = await materializeIdeaPlanAction('idea-1', { allowAlongside: true })
|
||||
expect(r).toMatchObject({ success: true, data: { pbi_id: 'pbi-new' } })
|
||||
expect(m.pbi.delete).not.toHaveBeenCalled()
|
||||
expect(m.pbi.create).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('relinkIdeaPlanAction', () => {
|
||||
it('happy: PLANNED with pbi_id=null → PLAN_READY', async () => {
|
||||
m.idea.findFirst.mockResolvedValueOnce({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue