* feat: add pushed_at field to ClaudeJob schema Nullable DateTime column to record when the agent's feature branch was pushed to origin. Enables the UI to show a 'pushed' state independently of DONE status. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: GitHub-link op DONE-card + pushed_at doorvoer - lib/job-status-url.ts: getBranchUrl(repoUrl, branch) → GitHub tree URL - JobState + ClaudeJobEvent: pushed_at? veld toegevoegd - realtime/solo/route.ts: pushed_at in Prisma-select, JobPayload en mapping - SoloBoardProps + TaskDetailDialog: repoUrl prop doorgevoerd - task-detail-dialog: "Open op GitHub"-link als done + pushed_at + branch + repoUrl - 3 unit-tests voor getBranchUrl; totaal 261 tests groen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add VerifyResult enum, verify_only on Task, verify_result on ClaudeJob Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add verify_result+pushed_at to JobState, VerifyResultApi type, SSE payload Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: verify_only field on SoloTask, PATCH route saves verify_only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: TaskDetailDialog — verify_result display + verify_only checkbox Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: verify_only PATCH + verify_result dialog render + store fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: document VerifyResult enum, verify_only task field, pushed_at in architecture Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(M13): cron /api/cron/cleanup-agent-artifacts — hard-delete FAILED/CANCELLED jobs >7 days * feat(M13): add auto_pr field to Product schema + migration * feat(M13): auto_pr toggle in product settings — server action + UI component + tests * feat(M13): add pr_url to ClaudeJob schema + migration * feat(M13): UI — 'Open PR' link on DONE-card; pr_url in JobState + SSE + task-dialog * feat(M13): add retry_count migration + regen erd - Migration ALTER TABLE claude_jobs ADD COLUMN retry_count INT DEFAULT 0 (schema.prisma was reeds bijgewerkt in eerdere commits) - docs/erd.svg geregenereerd voor de complete M13-schema-wijzigingen (verify_result, verify_only, pushed_at, pr_url, auto_pr, retry_count) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
63 lines
2 KiB
TypeScript
63 lines
2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
|
vi.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
claudeJob: { deleteMany: vi.fn() },
|
|
},
|
|
}))
|
|
|
|
import { prisma } from '@/lib/prisma'
|
|
import { POST } from '@/app/api/cron/cleanup-agent-artifacts/route'
|
|
|
|
const mockPrisma = prisma as unknown as {
|
|
claudeJob: { deleteMany: ReturnType<typeof vi.fn> }
|
|
}
|
|
|
|
const SECRET = 'test-cron-secret-abc123'
|
|
|
|
function makeReq(headers: Record<string, string> = {}): Request {
|
|
return new Request('http://localhost:3000/api/cron/cleanup-agent-artifacts', {
|
|
method: 'POST',
|
|
headers,
|
|
})
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
process.env.CRON_SECRET = SECRET
|
|
mockPrisma.claudeJob.deleteMany.mockResolvedValue({ count: 0 })
|
|
})
|
|
|
|
describe('POST /api/cron/cleanup-agent-artifacts', () => {
|
|
it('401 zonder Authorization-header', async () => {
|
|
const res = await POST(makeReq())
|
|
expect(res.status).toBe(401)
|
|
expect(mockPrisma.claudeJob.deleteMany).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('401 met verkeerde secret', async () => {
|
|
const res = await POST(makeReq({ authorization: 'Bearer wrong-secret' }))
|
|
expect(res.status).toBe(401)
|
|
expect(mockPrisma.claudeJob.deleteMany).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('200 met juiste secret + deleteMany aangeroepen voor FAILED/CANCELLED ouder dan 7 dagen', async () => {
|
|
mockPrisma.claudeJob.deleteMany.mockResolvedValue({ count: 5 })
|
|
|
|
const res = await POST(makeReq({ authorization: 'Bearer ' + SECRET }))
|
|
expect(res.status).toBe(200)
|
|
const body = await res.json()
|
|
expect(body.deleted).toBe(5)
|
|
expect(body.ran_at).toMatch(/^\d{4}-\d{2}-\d{2}T/)
|
|
|
|
const arg = mockPrisma.claudeJob.deleteMany.mock.calls[0][0]
|
|
expect(arg.where.status).toEqual({ in: ['FAILED', 'CANCELLED'] })
|
|
expect(arg.where.finished_at.lt).toBeInstanceOf(Date)
|
|
|
|
// cutoff should be approximately 7 days ago
|
|
const cutoff = arg.where.finished_at.lt as Date
|
|
const diffMs = Date.now() - cutoff.getTime()
|
|
const diffDays = diffMs / (1000 * 60 * 60 * 24)
|
|
expect(diffDays).toBeCloseTo(7, 0)
|
|
})
|
|
})
|