137 lines
4.7 KiB
TypeScript
137 lines
4.7 KiB
TypeScript
/** @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')
|
||
})
|
||
})
|