gongxiangp2/lib/__tests__/detail-text-recovery-route.test.ts

60 lines
4.0 KiB
TypeScript

/** @jest-environment node */
import { POST } from '@/app/api/detail-generator/jobs/[id]/recover/route'
import { getServerSession } from 'next-auth'
import { isSuperAdmin } from '@/lib/department-permission'
import { prisma } from '@/lib/prisma'
import { prepareDetailTextRecovery, findDetailJob } from '@/lib/detail-generator/repository'
import { processDetailJob } from '@/lib/detail-generator/jobs'
jest.mock('next-auth', () => ({ getServerSession: jest.fn() }))
jest.mock('@/lib/auth', () => ({ authOptions: {} }))
jest.mock('@/lib/department-permission', () => ({ isSuperAdmin: jest.fn() }))
jest.mock('@/lib/prisma', () => ({ prisma: { user: { findUnique: jest.fn() } } }))
jest.mock('@/lib/detail-generator/repository', () => ({ prepareDetailTextRecovery: jest.fn(), findDetailJob: jest.fn(), detailPublicJob: (value: unknown) => value }))
jest.mock('@/lib/detail-generator/jobs', () => ({ processDetailJob: jest.fn() }))
const id = 'a1111111-1111-4111-8111-111111111111'
function request(body: unknown) { return new Request('https://platform.test/api/detail-generator/jobs/' + id + '/recover', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) }
beforeEach(() => {
jest.clearAllMocks(); process.env.DETAIL_GENERATOR_ENABLED = 'true'
;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'owner-1' } })
;(isSuperAdmin as jest.Mock).mockResolvedValue(true)
;(prisma.user.findUnique as jest.Mock).mockResolvedValue({ isActive: true, status: 'active', archivedAt: null })
;(prepareDetailTextRecovery as jest.Mock).mockResolvedValue({ id, status: 'running', leaseToken: 'one-lease' })
;(findDetailJob as jest.Mock).mockResolvedValue({ id, status: 'unknown' })
})
test.each(['anonymous', 'ordinary', 'revoked', 'suspended'] as const)('%s access cannot query or continue a model task', async kind => {
if (kind === 'anonymous') (getServerSession as jest.Mock).mockResolvedValue(null)
if (kind === 'ordinary' || kind === 'revoked') (isSuperAdmin as jest.Mock).mockResolvedValue(false)
if (kind === 'suspended') (prisma.user.findUnique as jest.Mock).mockResolvedValue({ isActive: false, status: 'suspended', archivedAt: null })
const response = await POST(request({ action: 'query' }), { params: { id } })
expect(response.status).toBe(kind === 'anonymous' ? 401 : 403)
expect(prepareDetailTextRecovery).not.toHaveBeenCalled()
expect(processDetailJob).not.toHaveBeenCalled()
})
test.each([{ action: 'query', modelId: 'override' }, { action: 'continue', requestId: id }, { action: 'query', requestId: '../other' }, { action: 'resend' }])('invalid or overridden recovery body never reaches admission: %p', async body => {
const response = await POST(request(body), { params: { id } })
expect(response.status).toBe(400)
expect(prepareDetailTextRecovery).not.toHaveBeenCalled()
})
test('query executes only the prepared original lease; continuation only queues unfinished work', async () => {
const response = await POST(request({ action: 'query' }), { params: { id } })
expect(response.status).toBe(200)
expect(response.headers.get('cache-control')).toContain('no-store')
expect(prepareDetailTextRecovery).toHaveBeenCalledWith(id, 'owner-1', 'query', undefined)
expect(processDetailJob).toHaveBeenCalledTimes(1)
;(processDetailJob as jest.Mock).mockClear()
;(prepareDetailTextRecovery as jest.Mock).mockResolvedValue({ id, status: 'queued', leaseToken: null })
expect((await POST(request({ action: 'continue' }), { params: { id } })).status).toBe(200)
expect(processDetailJob).not.toHaveBeenCalled()
})
test('simple cross-site form and text/plain submissions cannot trigger recovery or continuation', async () => {
const response = await POST(new Request('https://platform.test/api/detail-generator/jobs/' + id + '/recover', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: '{"action":"continue"}' }), { params: { id } })
expect(response.status).toBe(415)
expect(prepareDetailTextRecovery).not.toHaveBeenCalled()
expect(processDetailJob).not.toHaveBeenCalled()
})