import { describe, it, expect, vi, beforeEach } from 'vitest' const { mockGetSession, mockFindFirstJob, mockFindManyPrice } = vi.hoisted(() => ({ mockGetSession: vi.fn(), mockFindFirstJob: vi.fn(), mockFindManyPrice: vi.fn(), })) vi.mock('@/lib/auth', () => ({ getSession: mockGetSession })) vi.mock('@/lib/prisma', () => ({ prisma: { claudeJob: { findFirst: mockFindFirstJob }, modelPrice: { findMany: mockFindManyPrice }, }, })) import { GET } from '@/app/api/jobs/[id]/route' function makeParams(id = 'job-1'): { params: Promise<{ id: string }> } { return { params: Promise.resolve({ id }) } } function makeRequest(id = 'job-1'): Request { return new Request(`http://localhost/api/jobs/${id}`) } const RAW_JOB = { id: 'job-1', kind: 'TASK_IMPLEMENTATION' as const, status: 'DONE' as const, model_id: 'claude-sonnet-4-6', input_tokens: 100, output_tokens: 50, cache_read_tokens: 0, cache_write_tokens: 0, branch: 'feat/test', pr_url: null, error: null, summary: 'Done', verify_result: 'ALIGNED' as const, started_at: new Date('2026-01-01T10:00:00Z'), finished_at: new Date('2026-01-01T10:05:00Z'), created_at: new Date('2026-01-01T09:59:00Z'), sprint_run_id: null, task: { code: 'T-42', title: 'Some task', description: null, implementation_plan: 'Do the thing', story: { code: 'S-10', pbi: { code: 'PBI-5' } }, }, idea: null, product: { name: 'Scrum4Me', code: 'SCR' }, sprint_run: null, } describe('GET /api/jobs/:id', () => { beforeEach(() => { vi.clearAllMocks() mockGetSession.mockResolvedValue({ userId: 'user-1' }) mockFindFirstJob.mockResolvedValue(RAW_JOB) mockFindManyPrice.mockResolvedValue([]) }) it('returns 401 when not logged in', async () => { mockGetSession.mockResolvedValue({ userId: undefined }) const res = await GET(makeRequest() as never, makeParams()) expect(res.status).toBe(401) const body = await res.json() expect(body.error).toBeTruthy() }) it('returns 404 when job not found', async () => { mockFindFirstJob.mockResolvedValue(null) const res = await GET(makeRequest() as never, makeParams()) expect(res.status).toBe(404) const body = await res.json() expect(body.error).toBeTruthy() }) it('queries with user_id filter to prevent cross-user access', async () => { await GET(makeRequest() as never, makeParams()) expect(mockFindFirstJob).toHaveBeenCalledWith( expect.objectContaining({ where: { id: 'job-1', user_id: 'user-1' }, }) ) }) it('returns 200 with mapped job shape including breadcrumb codes', async () => { const res = await GET(makeRequest() as never, makeParams()) expect(res.status).toBe(200) const body = await res.json() expect(body).toMatchObject({ id: 'job-1', kind: 'TASK_IMPLEMENTATION', status: 'DONE', taskCode: 'T-42', taskTitle: 'Some task', productCode: 'SCR', storyCode: 'S-10', pbiCode: 'PBI-5', branch: 'feat/test', }) }) })