198 lines
6.4 KiB
TypeScript
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',
|
|
})
|
|
})
|
|
})
|