gongxiangp2/lib/__tests__/task-detail-asset-batch-route.test.ts

137 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @jest-environment node */
import { getServerSession } from 'next-auth'
import { POST } from '@/app/api/materials/[id]/assets/batch/route'
import {
submitTaskDetailChange,
TaskDetailChangeApprovalError,
} from '@/lib/task-detail-change-approval'
jest.mock('next-auth', () => ({ getServerSession: jest.fn() }))
jest.mock('@/lib/auth', () => ({ authOptions: {} }))
jest.mock('@/lib/task-detail-asset-edit', () => ({
TaskDetailAssetEditError: class TaskDetailAssetEditError extends Error {
statusCode: number
constructor(message: string, statusCode = 400) {
super(message)
this.statusCode = statusCode
}
},
}))
jest.mock('@/lib/task-detail-change-approval', () => ({
TaskDetailChangeApprovalError: class TaskDetailChangeApprovalError extends Error {
statusCode: number
constructor(message: string, statusCode = 400) {
super(message)
this.statusCode = statusCode
}
},
submitTaskDetailChange: jest.fn(),
}))
const mockedSubmit = submitTaskDetailChange as jest.Mock
function requestWith(body: unknown) {
return { json: async () => body } as any
}
describe('任务-详情批量单图编辑路由', () => {
beforeEach(() => {
jest.clearAllMocks()
})
test('未登录返回 401,不触发批量逻辑', async () => {
;(getServerSession as jest.Mock).mockResolvedValue(null)
const response = await POST(
requestWith({ operations: [{ kind: 'delete', assetId: 'a1' }] }),
{ params: { id: 'm1' } },
)
expect(response.status).toBe(401)
expect(mockedSubmit).not.toHaveBeenCalled()
})
test('operations 为空数组返回 400', async () => {
;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'u1', role: 'ADMIN' } })
const response = await POST(requestWith({ operations: [] }), { params: { id: 'm1' } })
expect(response.status).toBe(400)
expect(mockedSubmit).not.toHaveBeenCalled()
})
test('不支持的操作类型返回 400', async () => {
;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'u1' } })
const response = await POST(
requestWith({ operations: [{ kind: 'magic', assetId: 'a1' }] }),
{ params: { id: 'm1' } },
)
expect(response.status).toBe(400)
expect(mockedSubmit).not.toHaveBeenCalled()
})
test('replace 缺少 uploadId 返回 400', async () => {
;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'u1' } })
const response = await POST(
requestWith({ operations: [{ kind: 'replace', assetId: 'a1' }] }),
{ params: { id: 'm1' } },
)
expect(response.status).toBe(400)
})
test('合法混合操作透传给批量引擎并返回结果', async () => {
;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'u1', role: 'ADMIN' } })
mockedSubmit.mockResolvedValue({ mode: 'applied', data: { success: true, appliedCount: 3, entryCount: 7 } })
const response = await POST(
requestWith({
operations: [
{ kind: 'delete', assetId: 'a1' },
{ kind: 'rename', assetId: 'a2', newFileName: '新名字.jpg' },
{ kind: 'replace', assetId: 'a3', uploadId: 'up-1' },
],
}),
{ params: { id: 'm1' } },
)
expect(response.status).toBe(200)
const payload = await response.json()
expect(payload.data.appliedCount).toBe(3)
expect(mockedSubmit).toHaveBeenCalledWith(
'm1',
{ id: 'u1', role: 'ADMIN', isSuperAdmin: false },
[
{ kind: 'delete', assetId: 'a1' },
{ kind: 'rename', assetId: 'a2', newFileName: '新名字.jpg' },
{ kind: 'replace', assetId: 'a3', uploadId: 'up-1' },
],
)
})
test('业务错误透传状态码与文案', async () => {
;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'u1' } })
mockedSubmit.mockRejectedValue(
new TaskDetailChangeApprovalError('批量操作只支持同一分组(主图/SKU/详情)内的图片', 400),
)
const response = await POST(
requestWith({ operations: [{ kind: 'delete', assetId: 'a1' }] }),
{ params: { id: 'm1' } },
)
expect(response.status).toBe(400)
const payload = await response.json()
expect(payload.error).toContain('同一分组')
})
test('未知异常返回 500 且不泄漏内部信息', async () => {
;(getServerSession as jest.Mock).mockResolvedValue({ user: { id: 'u1' } })
mockedSubmit.mockRejectedValue(new Error('internal detail'))
const response = await POST(
requestWith({ operations: [{ kind: 'delete', assetId: 'a1' }] }),
{ params: { id: 'm1' } },
)
expect(response.status).toBe(500)
const payload = await response.json()
expect(payload.error).not.toContain('internal detail')
})
})