gongxiangp2/lib/__tests__/standard-model-white-background-by-barcode-route.test.ts
2026-07-31 10:40:00 +08:00

198 lines
6.4 KiB
TypeScript

/** @jest-environment node */
import { POST as uploadByBarcode } from '@/app/api/external/standard-model-white-background/by-barcode/route'
import { POST as cleanupByBarcode } from '@/app/api/external/standard-model-white-background/by-barcode/cleanup/route'
import { logApiCall, validateApiKey } from '@/lib/external-api'
import {
completeExternalApiIdempotencyRecord,
resolveStandardModelSkuByBarcode,
startExternalApiIdempotencyRecord,
} from '@/lib/external-standard-model-white-background-by-barcode'
import {
cleanupStandardModelWhiteBackgroundForSku,
uploadStandardModelWhiteBackground,
} from '@/lib/standard-model-white-background-service'
jest.mock('@/lib/external-api', () => ({
getApiKeyFromRequest: jest.fn((request: { headers: Headers }) => request.headers.get('X-API-Key')),
getClientIp: jest.fn(() => '127.0.0.1'),
logApiCall: jest.fn().mockResolvedValue(undefined),
validateApiKey: jest.fn(),
}))
jest.mock('@/lib/external-standard-model-white-background-by-barcode', () => {
const actual = jest.requireActual('@/lib/external-standard-model-white-background-by-barcode')
return {
...actual,
completeExternalApiIdempotencyRecord: jest.fn().mockResolvedValue(undefined),
failExternalApiIdempotencyRecord: jest.fn().mockResolvedValue(undefined),
resolveStandardModelSkuByBarcode: jest.fn(),
startExternalApiIdempotencyRecord: jest.fn(),
}
})
jest.mock('@/lib/standard-model-white-background-service', () => ({
cleanupStandardModelWhiteBackgroundForSku: jest.fn(),
uploadStandardModelWhiteBackground: jest.fn(),
}))
describe('external standard-model-white-background by barcode routes', () => {
beforeEach(() => {
jest.clearAllMocks()
;(validateApiKey as jest.Mock).mockResolvedValue({
id: 'api-1',
name: 'fixture-key',
createdBy: 'user-1',
isActive: true,
})
;(startExternalApiIdempotencyRecord as jest.Mock).mockResolvedValue({
mode: 'created',
record: { id: 'idem-1' },
})
;(resolveStandardModelSkuByBarcode as jest.Mock).mockResolvedValue({
barcode: '690000000001',
materialId: 'material-1',
skuId: 'sku-1',
skuName: 'XG014799',
resolvedSpu: 'XG0147',
resolvedSkuName: 'XG014799',
})
;(uploadStandardModelWhiteBackground as jest.Mock).mockResolvedValue({
whiteBackgroundUploaded: true,
uploadedFixedCount: 1,
fixedCount: 1,
otherCount: 0,
whiteBackgroundCount: 1,
isComplete: false,
sourceType: 'AI',
whiteBackgroundUploadedBy: 'user-1',
whiteBackgroundUploadedAt: '2026-06-08T12:00:00.000Z',
downloadFileName: 'XG014799_白底图.zip',
})
;(cleanupStandardModelWhiteBackgroundForSku as jest.Mock).mockResolvedValue({
deletedAssetCount: 1,
deletedFileCount: 2,
failedFileCount: 0,
clearedMetadata: true,
})
})
function createUploadRequest(formData: FormData) {
return {
headers: new Headers({
'X-API-Key': 'test-key',
'user-agent': 'jest',
'content-length': '1024',
}),
formData: jest.fn().mockResolvedValue(formData),
} as any
}
function createCleanupRequest(body: unknown) {
return {
headers: new Headers({
'X-API-Key': 'test-key',
'user-agent': 'jest',
}),
json: jest.fn().mockResolvedValue(body),
} as any
}
test('少于 8 张固定视图也应按现有白底图规则上传', async () => {
const formData = new FormData()
formData.append('clientRequestId', 'req-001')
formData.append('barcode', '690000000001')
formData.append('sourceType', 'AI')
formData.append('sourceSystem', 'furniture-3d-system')
formData.append('productId', 'product-1')
formData.append('skuBatchNo', '1')
formData.append(
'whiteBgRightFront45',
new File([Buffer.from('png-source')], 'right-front-45.png', { type: 'image/png' }),
)
const response = await uploadByBarcode(createUploadRequest(formData))
const body = await response.json()
expect(response.status).toBe(200)
expect(body.success).toBe(true)
expect(body.data).toMatchObject({
materialId: 'material-1',
skuId: 'sku-1',
traceId: expect.any(String),
uploadedFixedCount: 1,
isComplete: false,
})
expect(uploadStandardModelWhiteBackground).toHaveBeenCalledWith(
expect.objectContaining({
materialId: 'material-1',
skuId: 'sku-1',
sourceType: 'AI',
operatorUserId: 'user-1',
}),
)
expect(completeExternalApiIdempotencyRecord).toHaveBeenCalled()
expect(logApiCall).toHaveBeenCalledWith(expect.objectContaining({ statusCode: 200 }))
})
test('没有任何有效白底图时才返回 MISSING_REQUIRED_VIEWS', async () => {
const formData = new FormData()
formData.append('clientRequestId', 'req-002')
formData.append('barcode', '690000000001')
formData.append('sourceType', 'AI')
const response = await uploadByBarcode(createUploadRequest(formData))
const body = await response.json()
expect(response.status).toBe(400)
expect(body).toEqual({
success: false,
error: {
code: 'MISSING_REQUIRED_VIEWS',
message: '请至少上传 1 张白底图',
},
})
expect(uploadStandardModelWhiteBackground).not.toHaveBeenCalled()
})
test('缺少请求大小时在解析 multipart 前拒绝', async () => {
const request = {
headers: new Headers({ 'X-API-Key': 'test-key' }),
formData: jest.fn(),
} as any
const response = await uploadByBarcode(request)
const body = await response.json()
expect(response.status).toBe(411)
expect(body.error.code).toBe('CONTENT_LENGTH_REQUIRED')
expect(request.formData).not.toHaveBeenCalled()
})
test('cleanup 只按条码解析到的 SKU 调用 SKU 范围清理', async () => {
const response = await cleanupByBarcode(
createCleanupRequest({
clientRequestId: 'cleanup-001',
reason: 'replace-upload',
mode: 'soft',
skuBatchNo: 1,
barcodes: ['690000000001'],
}),
)
const body = await response.json()
expect(response.status).toBe(200)
expect(body.success).toBe(true)
expect(body.data).toMatchObject({
traceId: expect.any(String),
cleanedCount: 1,
})
expect(cleanupStandardModelWhiteBackgroundForSku).toHaveBeenCalledWith({
materialId: 'material-1',
skuId: 'sku-1',
operatorUserId: 'user-1',
reason: 'replace-upload',
})
})
})