From fdd0447aadf26ce0fc9e3273b4b901849bd1bfea Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Thu, 7 May 2026 12:53:09 +0200 Subject: [PATCH] PBI-50 F5: cross-repo blocker test voor SPRINT_BATCH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - task_cross_repo blocker fires bij task.repo_url ≠ product.repo_url - happy path: tasks zonder repo_url-override of met match → één SPRINT_IMPLEMENTATION-job (niet per-task). Co-Authored-By: Claude Opus 4.7 (1M context) --- __tests__/actions/sprint-runs.test.ts | 100 ++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/__tests__/actions/sprint-runs.test.ts b/__tests__/actions/sprint-runs.test.ts index 8b39061..9defa50 100644 --- a/__tests__/actions/sprint-runs.test.ts +++ b/__tests__/actions/sprint-runs.test.ts @@ -201,6 +201,106 @@ describe('startSprintRunAction — pre-flight blockers', () => { }) }) +describe('startSprintRunAction — SPRINT_BATCH', () => { + const SPRINT_BATCH = { + ...SPRINT_OK, + product: { + id: 'prod-1', + pr_strategy: 'SPRINT_BATCH', + repo_url: 'https://github.com/example/main', + }, + } + + it('blokkeert task met afwijkende repo_url', async () => { + mockPrisma.sprint.findUnique.mockResolvedValue(SPRINT_BATCH) + mockPrisma.sprintRun.findFirst.mockResolvedValue(null) + mockPrisma.story.findMany.mockResolvedValue([ + { + ...STORY_OK, + tasks: [ + { + id: 'task-1', + code: 'T-1', + title: 'In main repo', + priority: 1, + sort_order: 1, + implementation_plan: 'plan', + repo_url: null, + }, + { + id: 'task-2', + code: 'T-2', + title: 'Cross-repo', + priority: 1, + sort_order: 2, + implementation_plan: 'plan', + repo_url: 'https://github.com/example/other', + }, + ], + }, + ]) + mockPrisma.claudeQuestion.findMany.mockResolvedValue([]) + + const result = await startSprintRunAction({ sprint_id: 'sprint-1' }) + + expect(result).toMatchObject({ ok: false, error: 'PRE_FLIGHT_BLOCKED' }) + if (result.ok === false && 'blockers' in result) { + expect(result.blockers).toContainEqual({ + type: 'task_cross_repo', + id: 'task-2', + label: 'T-2: Cross-repo', + }) + } + expect(mockPrisma.sprintRun.create).not.toHaveBeenCalled() + }) + + it('staat tasks toe wanneer repo_url leeg is of gelijk aan product.repo_url', async () => { + mockPrisma.sprint.findUnique.mockResolvedValue(SPRINT_BATCH) + mockPrisma.sprintRun.findFirst.mockResolvedValue(null) + mockPrisma.story.findMany.mockResolvedValue([ + { + ...STORY_OK, + tasks: [ + { + id: 'task-1', + code: 'T-1', + title: 'No override', + priority: 1, + sort_order: 1, + implementation_plan: 'plan', + repo_url: null, + }, + { + id: 'task-2', + code: 'T-2', + title: 'Same repo', + priority: 1, + sort_order: 2, + implementation_plan: 'plan', + repo_url: 'https://github.com/example/main', + }, + ], + }, + ]) + mockPrisma.claudeQuestion.findMany.mockResolvedValue([]) + mockPrisma.sprintRun.create.mockResolvedValue({ id: 'run-batch' }) + mockPrisma.claudeJob.create.mockResolvedValue({ id: 'job-sprint' }) + + const result = await startSprintRunAction({ sprint_id: 'sprint-1' }) + + expect(result).toMatchObject({ ok: true, sprint_run_id: 'run-batch' }) + // Eén SPRINT_IMPLEMENTATION-job, niet per-task + expect(mockPrisma.claudeJob.create).toHaveBeenCalledTimes(1) + expect(mockPrisma.claudeJob.create).toHaveBeenCalledWith({ + data: expect.objectContaining({ + kind: 'SPRINT_IMPLEMENTATION', + sprint_run_id: 'run-batch', + product_id: 'prod-1', + }), + }) + }) +}) + describe('startSprintRunAction — guards', () => { it('weigert wanneer Sprint niet ACTIVE is', async () => { mockPrisma.sprint.findUnique.mockResolvedValue({ ...SPRINT_OK, status: 'COMPLETED' })