From 0605838b99296c2dca9e0530376f25041b2d39eb Mon Sep 17 00:00:00 2001 From: janpeter visser Date: Fri, 1 May 2026 11:04:45 +0200 Subject: [PATCH] fix: scope enqueueAllTodoJobsAction op actieve sprint + assignee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De action queue'de eerder ALLE TO_DO-taken van een product, ongeacht sprint of assignee — terwijl de 'Start agents (n)'-knop in de UI alleen de taken telt die de gebruiker ziet (actieve sprint, eigen stories). Daardoor kreeg een klik op de knop veel meer jobs aangemaakt dan de count suggereerde (62 i.p.v. de getoonde n). Server-filter komt nu overeen met page.tsx solo-query: story: { sprint_id: , assignee_id: userId } Edge case: geen actieve sprint → success met count=0 (geen error). Tests aangepast + nieuwe test voor 'geen actieve sprint'-pad. Co-Authored-By: Claude Opus 4.7 (1M context) --- __tests__/actions/claude-jobs.test.ts | 29 +++++++++++++++++++++++++-- actions/claude-jobs.ts | 12 ++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/__tests__/actions/claude-jobs.test.ts b/__tests__/actions/claude-jobs.test.ts index 28b8783..1da99ef 100644 --- a/__tests__/actions/claude-jobs.test.ts +++ b/__tests__/actions/claude-jobs.test.ts @@ -5,6 +5,7 @@ const { mockFindFirstTask, mockFindManyTask, mockFindFirstProduct, + mockFindFirstSprint, mockFindFirstJob, mockCreateJob, mockUpdateJob, @@ -15,6 +16,7 @@ const { mockFindFirstTask: vi.fn(), mockFindManyTask: vi.fn(), mockFindFirstProduct: vi.fn(), + mockFindFirstSprint: vi.fn(), mockFindFirstJob: vi.fn(), mockCreateJob: vi.fn(), mockUpdateJob: vi.fn(), @@ -32,6 +34,7 @@ vi.mock('@/lib/prisma', () => ({ prisma: { task: { findFirst: mockFindFirstTask, findMany: mockFindManyTask }, product: { findFirst: mockFindFirstProduct }, + sprint: { findFirst: mockFindFirstSprint }, claudeJob: { findFirst: mockFindFirstJob, create: mockCreateJob, @@ -121,9 +124,10 @@ describe('enqueueClaudeJobAction', () => { }) describe('enqueueAllTodoJobsAction', () => { - it('happy path: queues a job for every TO_DO task without active job', async () => { + it('happy path: scopes to active sprint + assignee, queues all queueable tasks', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID }) + mockFindFirstSprint.mockResolvedValue({ id: 'sprint-1' }) mockFindManyTask.mockResolvedValue([{ id: 'task-a' }, { id: 'task-b' }]) mockTransaction.mockResolvedValue([ { id: 'job-a', task_id: 'task-a' }, @@ -133,12 +137,33 @@ describe('enqueueAllTodoJobsAction', () => { const result = await enqueueAllTodoJobsAction(PRODUCT_ID) expect(result).toEqual({ success: true, count: 2 }) + expect(mockFindManyTask).toHaveBeenCalledWith( + expect.objectContaining({ + where: expect.objectContaining({ + status: 'TO_DO', + story: { sprint_id: 'sprint-1', assignee_id: SESSION_USER.userId }, + }), + }) + ) expect(mockExecuteRaw).toHaveBeenCalledTimes(2) }) - it('returns count=0 when no queueable tasks', async () => { + it('returns count=0 when product has no active sprint', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID }) + mockFindFirstSprint.mockResolvedValue(null) + + const result = await enqueueAllTodoJobsAction(PRODUCT_ID) + + expect(result).toEqual({ success: true, count: 0 }) + expect(mockFindManyTask).not.toHaveBeenCalled() + expect(mockTransaction).not.toHaveBeenCalled() + }) + + it('returns count=0 when no queueable tasks in sprint+assignee scope', async () => { + mockGetSession.mockResolvedValue(SESSION_USER) + mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID }) + mockFindFirstSprint.mockResolvedValue({ id: 'sprint-1' }) mockFindManyTask.mockResolvedValue([]) const result = await enqueueAllTodoJobsAction(PRODUCT_ID) diff --git a/actions/claude-jobs.ts b/actions/claude-jobs.ts index 1ed1fef..17c55d3 100644 --- a/actions/claude-jobs.ts +++ b/actions/claude-jobs.ts @@ -78,10 +78,20 @@ export async function enqueueAllTodoJobsAction(productId: string): Promise