feat: enqueueAllTodoJobsAction voor batch-queueing van TO_DO-taken
Nieuwe Server Action die alle TO_DO-taken van een product zonder
actieve ClaudeJob in één $transaction als QUEUED jobs aanmaakt en
voor elk een pg_notify('claude_job_enqueued') stuurt zodat de SSE-
stream de UI live bijwerkt.
- Auth + demo-blokkade + product-access via productAccessFilter
- Idempotent: tasks met status QUEUED/CLAIMED/RUNNING worden overgeslagen
- 4 nieuwe tests (happy path, count=0, demo-blokkade, geen toegang)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1e48eed459
commit
7d76c3baee
2 changed files with 118 additions and 2 deletions
|
|
@ -3,17 +3,23 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|||
const {
|
||||
mockGetSession,
|
||||
mockFindFirstTask,
|
||||
mockFindManyTask,
|
||||
mockFindFirstProduct,
|
||||
mockFindFirstJob,
|
||||
mockCreateJob,
|
||||
mockUpdateJob,
|
||||
mockExecuteRaw,
|
||||
mockTransaction,
|
||||
} = vi.hoisted(() => ({
|
||||
mockGetSession: vi.fn(),
|
||||
mockFindFirstTask: vi.fn(),
|
||||
mockFindManyTask: vi.fn(),
|
||||
mockFindFirstProduct: vi.fn(),
|
||||
mockFindFirstJob: vi.fn(),
|
||||
mockCreateJob: vi.fn(),
|
||||
mockUpdateJob: vi.fn(),
|
||||
mockExecuteRaw: vi.fn().mockResolvedValue(undefined),
|
||||
mockTransaction: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
|
||||
|
|
@ -24,17 +30,23 @@ vi.mock('@/lib/auth', () => ({
|
|||
|
||||
vi.mock('@/lib/prisma', () => ({
|
||||
prisma: {
|
||||
task: { findFirst: mockFindFirstTask },
|
||||
task: { findFirst: mockFindFirstTask, findMany: mockFindManyTask },
|
||||
product: { findFirst: mockFindFirstProduct },
|
||||
claudeJob: {
|
||||
findFirst: mockFindFirstJob,
|
||||
create: mockCreateJob,
|
||||
update: mockUpdateJob,
|
||||
},
|
||||
$executeRaw: mockExecuteRaw,
|
||||
$transaction: mockTransaction,
|
||||
},
|
||||
}))
|
||||
|
||||
import { enqueueClaudeJobAction, cancelClaudeJobAction } from '@/actions/claude-jobs'
|
||||
import {
|
||||
enqueueClaudeJobAction,
|
||||
enqueueAllTodoJobsAction,
|
||||
cancelClaudeJobAction,
|
||||
} from '@/actions/claude-jobs'
|
||||
|
||||
const SESSION_USER = { userId: 'user-1', isDemo: false }
|
||||
|
||||
|
|
@ -108,6 +120,54 @@ describe('enqueueClaudeJobAction', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('enqueueAllTodoJobsAction', () => {
|
||||
it('happy path: queues a job for every TO_DO task without active job', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID })
|
||||
mockFindManyTask.mockResolvedValue([{ id: 'task-a' }, { id: 'task-b' }])
|
||||
mockTransaction.mockResolvedValue([
|
||||
{ id: 'job-a', task_id: 'task-a' },
|
||||
{ id: 'job-b', task_id: 'task-b' },
|
||||
])
|
||||
|
||||
const result = await enqueueAllTodoJobsAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toEqual({ success: true, count: 2 })
|
||||
expect(mockExecuteRaw).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('returns count=0 when no queueable tasks', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue({ id: PRODUCT_ID })
|
||||
mockFindManyTask.mockResolvedValue([])
|
||||
|
||||
const result = await enqueueAllTodoJobsAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toEqual({ success: true, count: 0 })
|
||||
expect(mockTransaction).not.toHaveBeenCalled()
|
||||
expect(mockExecuteRaw).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('blocks demo user', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_DEMO)
|
||||
|
||||
const result = await enqueueAllTodoJobsAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toMatchObject({ error: 'Niet beschikbaar in demo-modus' })
|
||||
expect(mockTransaction).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns error when product not accessible', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
mockFindFirstProduct.mockResolvedValue(null)
|
||||
|
||||
const result = await enqueueAllTodoJobsAction(PRODUCT_ID)
|
||||
|
||||
expect(result).toMatchObject({ error: 'Geen toegang tot dit product' })
|
||||
expect(mockTransaction).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('cancelClaudeJobAction', () => {
|
||||
it('happy path: cancels QUEUED job', async () => {
|
||||
mockGetSession.mockResolvedValue(SESSION_USER)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue