feat(M13): retry-tracking — stale CLAIMED jobs → QUEUED (retry_count++) or FAILED (≥2 retries)
resetStaleClaimedJobs now uses $queryRaw with RETURNING so it can send pg_notify claude_job_status events per transitioned job. Jobs under the retry limit are re-queued with retry_count incremented; jobs at ≥2 retries are marked FAILED.
This commit is contained in:
parent
2343915a6a
commit
095ebc40f8
2 changed files with 93 additions and 15 deletions
|
|
@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|||
|
||||
vi.mock('../src/prisma.js', () => ({
|
||||
prisma: {
|
||||
$executeRaw: vi.fn(),
|
||||
$queryRaw: vi.fn(),
|
||||
$transaction: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
|
@ -11,27 +11,41 @@ import { prisma } from '../src/prisma.js'
|
|||
import { resetStaleClaimedJobs, tryClaimJob } from '../src/tools/wait-for-job.js'
|
||||
|
||||
const mockPrisma = prisma as unknown as {
|
||||
$executeRaw: ReturnType<typeof vi.fn>
|
||||
$queryRaw: ReturnType<typeof vi.fn>
|
||||
$transaction: ReturnType<typeof vi.fn>
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Default: no stale jobs returned from either query
|
||||
mockPrisma.$queryRaw.mockResolvedValue([])
|
||||
})
|
||||
|
||||
describe('resetStaleClaimedJobs', () => {
|
||||
it('resets plan_snapshot to NULL when resetting stale claimed jobs', async () => {
|
||||
mockPrisma.$executeRaw.mockResolvedValue(0)
|
||||
it('runs two $queryRaw calls: one for FAILED, one for QUEUED re-enqueue', async () => {
|
||||
await resetStaleClaimedJobs('user-1')
|
||||
// Two queries: failed jobs + requeued jobs
|
||||
expect(mockPrisma.$queryRaw).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
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()')
|
||||
it('FAILED query includes plan_snapshot = NULL reset and retry_count >= 2', async () => {
|
||||
await resetStaleClaimedJobs('user-1')
|
||||
const calls = mockPrisma.$queryRaw.mock.calls
|
||||
// First call: FAILED transition
|
||||
const failedSql = (calls[0][0] as string[]).join('')
|
||||
expect(failedSql).toContain("status = 'FAILED'")
|
||||
expect(failedSql).toContain('retry_count >= 2')
|
||||
})
|
||||
|
||||
it('QUEUED re-enqueue query includes plan_snapshot = NULL and retry_count increment', async () => {
|
||||
await resetStaleClaimedJobs('user-1')
|
||||
const calls = mockPrisma.$queryRaw.mock.calls
|
||||
// Second call: re-enqueue transition
|
||||
const requeueSql = (calls[1][0] as string[]).join('')
|
||||
expect(requeueSql).toContain("status = 'QUEUED'")
|
||||
expect(requeueSql).toContain('plan_snapshot = NULL')
|
||||
expect(requeueSql).toContain('retry_count = retry_count + 1')
|
||||
expect(requeueSql).toContain('retry_count < 2')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue