feat(solo): previewEnqueueAllAction met blocker-detectie
Voeg previewEnqueueAllAction toe aan actions/claude-jobs.ts: haalt taken op in PBI-volgorde, filtert actieve jobs, detecteert eerste blocker (REVIEW taak of BLOCKED PBI). Retourneert tasks[], blockerIndex en blockerReason. Tests: 7 nieuwe cases voor alle blocker-scenario's en demo-blokkering.
This commit is contained in:
parent
41e7654374
commit
8018920cae
2 changed files with 194 additions and 0 deletions
|
|
@ -49,6 +49,7 @@ import {
|
|||
enqueueClaudeJobAction,
|
||||
enqueueAllTodoJobsAction,
|
||||
cancelClaudeJobAction,
|
||||
previewEnqueueAllAction,
|
||||
} from '@/actions/claude-jobs'
|
||||
|
||||
const SESSION_USER = { userId: 'user-1', isDemo: false }
|
||||
|
|
@ -193,6 +194,107 @@ describe('enqueueAllTodoJobsAction', () => {
|
|||
})
|
||||
})
|
||||
|
||||
const makePbiTask = (id: string, status: string, pbiStatus = 'READY') => ({
|
||||
id,
|
||||
title: `Task ${id}`,
|
||||
status,
|
||||
story: { id: 'story-1', title: 'Story 1', code: 'ST-1', pbi: { id: 'pbi-1', status: pbiStatus, priority: 1, sort_order: 1.0 } },
|
||||
})
|
||||
|
||||
describe('previewEnqueueAllAction', () => {
|
||||
it('blocks demo user', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_DEMO)
|
||||
|
||||
const result = await previewEnqueueAllAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toMatchObject({ error: 'Niet beschikbaar in demo-modus' })
|
||||
expect(mockFindManyTask).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns error when product not accessible', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue(null)
|
||||
|
||||
const result = await previewEnqueueAllAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toMatchObject({ error: 'Geen toegang tot dit product' })
|
||||
expect(mockFindManyTask).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns empty tasks when no active sprint', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID })
|
||||
mockFindFirstSprint.mockResolvedValue(null)
|
||||
|
||||
const result = await previewEnqueueAllAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toEqual({ tasks: [], blockerIndex: null, blockerReason: null })
|
||||
expect(mockFindManyTask).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns all tasks with no blocker when only TO_DO tasks', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID })
|
||||
mockFindFirstSprint.mockResolvedValue({ id: 'sprint-1' })
|
||||
mockFindManyTask.mockResolvedValue([
|
||||
makePbiTask('t1', 'TO_DO'),
|
||||
makePbiTask('t2', 'TO_DO'),
|
||||
])
|
||||
|
||||
const result = await previewEnqueueAllAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toMatchObject({ blockerIndex: null, blockerReason: null })
|
||||
if (!('error' in result)) expect(result.tasks).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('detects REVIEW task as blocker at correct index', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID })
|
||||
mockFindFirstSprint.mockResolvedValue({ id: 'sprint-1' })
|
||||
mockFindManyTask.mockResolvedValue([
|
||||
makePbiTask('t1', 'TO_DO'),
|
||||
makePbiTask('t2', 'TO_DO'),
|
||||
makePbiTask('t3', 'REVIEW'),
|
||||
makePbiTask('t4', 'TO_DO'),
|
||||
])
|
||||
|
||||
const result = await previewEnqueueAllAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toMatchObject({ blockerIndex: 2, blockerReason: 'task-review' })
|
||||
if (!('error' in result)) expect(result.tasks).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('detects BLOCKED PBI as blocker at first task of that PBI', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID })
|
||||
mockFindFirstSprint.mockResolvedValue({ id: 'sprint-1' })
|
||||
mockFindManyTask.mockResolvedValue([
|
||||
makePbiTask('t1', 'TO_DO', 'BLOCKED'),
|
||||
makePbiTask('t2', 'TO_DO', 'BLOCKED'),
|
||||
])
|
||||
|
||||
const result = await previewEnqueueAllAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toMatchObject({ blockerIndex: 0, blockerReason: 'pbi-blocked' })
|
||||
if (!('error' in result)) expect(result.tasks).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('queries without TO_DO filter to expose REVIEW tasks', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID })
|
||||
mockFindFirstSprint.mockResolvedValue({ id: 'sprint-1' })
|
||||
mockFindManyTask.mockResolvedValue([])
|
||||
|
||||
await previewEnqueueAllAction(PRODUCT_ID)
|
||||
|
||||
expect(mockFindManyTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: expect.not.objectContaining({ status: 'TO_DO' }),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cancelClaudeJobAction', () => {
|
||||
it('happy path: cancels QUEUED job', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue