Scrum4Me/__tests__/app/api/jobs/job-by-id-route.test.ts

106 lines
3 KiB
TypeScript

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',
})
})
})