From 54ace839f81aece55e56492b1998e1258549f937 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Thu, 30 Apr 2026 19:27:56 +0200 Subject: [PATCH] test: snapshot capture + stale reset in wait_for_job Verifies: claim writes plan_snapshot from task.implementation_plan; NULL plan becomes '' snapshot; no job returns null; stale reset SQL includes plan_snapshot = NULL; product_id scoping passes correct param. Co-Authored-By: Claude Sonnet 4.6 --- __tests__/wait-for-job-snapshot.test.ts | 134 ++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 __tests__/wait-for-job-snapshot.test.ts diff --git a/__tests__/wait-for-job-snapshot.test.ts b/__tests__/wait-for-job-snapshot.test.ts new file mode 100644 index 0000000..e4eb059 --- /dev/null +++ b/__tests__/wait-for-job-snapshot.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +vi.mock('../src/prisma.js', () => ({ + prisma: { + $executeRaw: vi.fn(), + $transaction: vi.fn(), + }, +})) + +import { prisma } from '../src/prisma.js' +import { resetStaleClaimedJobs, tryClaimJob } from '../src/tools/wait-for-job.js' + +const mockPrisma = prisma as unknown as { + $executeRaw: ReturnType + $transaction: ReturnType +} + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('resetStaleClaimedJobs', () => { + it('resets plan_snapshot to NULL when resetting stale claimed jobs', async () => { + mockPrisma.$executeRaw.mockResolvedValue(0) + await resetStaleClaimedJobs('user-1') + + expect(mockPrisma.$executeRaw).toHaveBeenCalledOnce() + // Verify the template literal includes plan_snapshot = NULL + const call = mockPrisma.$executeRaw.mock.calls[0] + const sqlParts: string[] = call[0] + const fullSql = sqlParts.join('') + expect(fullSql).toContain('plan_snapshot = NULL') + expect(fullSql).toContain("status = 'QUEUED'") + expect(fullSql).toContain('claimed_at < NOW()') + }) +}) + +describe('tryClaimJob', () => { + it('writes plan_snapshot from task.implementation_plan when claiming a job', async () => { + const jobId = 'job-123' + const implementationPlan = 'Step 1: Do the thing\nStep 2: Done' + + mockPrisma.$transaction.mockImplementation(async (fn: (tx: typeof prisma) => Promise) => { + const mockTx = { + $queryRaw: vi.fn().mockResolvedValue([{ id: jobId, implementation_plan: implementationPlan }]), + $executeRaw: vi.fn().mockResolvedValue(1), + } + return fn(mockTx as unknown as typeof prisma) + }) + + const result = await tryClaimJob('user-1', 'token-1') + + expect(result).toBe(jobId) + + // Verify the transaction was called and the UPDATE included plan_snapshot + expect(mockPrisma.$transaction).toHaveBeenCalledOnce() + const txFn = mockPrisma.$transaction.mock.calls[0][0] + + const capturedTx = { + $queryRaw: vi.fn().mockResolvedValue([{ id: jobId, implementation_plan: implementationPlan }]), + $executeRaw: vi.fn().mockResolvedValue(1), + } + await txFn(capturedTx as unknown as typeof prisma) + + const updateCall = capturedTx.$executeRaw.mock.calls[0] + const sqlParts: string[] = updateCall[0] + const fullSql = sqlParts.join('') + expect(fullSql).toContain('plan_snapshot') + expect(fullSql).toContain("status = 'CLAIMED'") + }) + + it('uses empty string as snapshot when task has no implementation_plan', async () => { + const jobId = 'job-456' + + mockPrisma.$transaction.mockImplementation(async (fn: (tx: typeof prisma) => Promise) => { + const mockTx = { + $queryRaw: vi.fn().mockResolvedValue([{ id: jobId, implementation_plan: null }]), + $executeRaw: vi.fn().mockResolvedValue(1), + } + return fn(mockTx as unknown as typeof prisma) + }) + + const result = await tryClaimJob('user-1', 'token-1') + expect(result).toBe(jobId) + + // Verify the snapshot value passed is '' (empty string, not null) + const capturedTx = { + $queryRaw: vi.fn().mockResolvedValue([{ id: jobId, implementation_plan: null }]), + $executeRaw: vi.fn().mockResolvedValue(1), + } + const txFn = mockPrisma.$transaction.mock.calls[0][0] + await txFn(capturedTx as unknown as typeof prisma) + + const updateCall = capturedTx.$executeRaw.mock.calls[0] + // Template literal params: [0]=sql parts, [1]=tokenId, [2]=snapshot, [3]=jobId + expect(updateCall[2]).toBe('') + }) + + it('returns null when no QUEUED job is available', async () => { + mockPrisma.$transaction.mockImplementation(async (fn: (tx: typeof prisma) => Promise) => { + const mockTx = { + $queryRaw: vi.fn().mockResolvedValue([]), + $executeRaw: vi.fn(), + } + return fn(mockTx as unknown as typeof prisma) + }) + + const result = await tryClaimJob('user-1', 'token-1') + expect(result).toBeNull() + }) + + it('scopes to product_id when provided', async () => { + mockPrisma.$transaction.mockImplementation(async (fn: (tx: typeof prisma) => Promise) => { + const mockTx = { + $queryRaw: vi.fn().mockResolvedValue([]), + $executeRaw: vi.fn(), + } + return fn(mockTx as unknown as typeof prisma) + }) + + await tryClaimJob('user-1', 'token-1', 'product-1') + + const capturedTx = { + $queryRaw: vi.fn().mockResolvedValue([]), + $executeRaw: vi.fn(), + } + const txFn = mockPrisma.$transaction.mock.calls[0][0] + await txFn(capturedTx as unknown as typeof prisma) + + const queryCall = capturedTx.$queryRaw.mock.calls[0] + // product_id should be passed as a parameter + expect(queryCall).toContain('product-1') + }) +})