gongxiangp2/lib/__tests__/chunk-upload.test.ts
chenpei 4712175cb0 feat(素材链路): 发布白底图双轨与AI生图回传能力
- 完成白底图 WebP 双轨接口、上传链路与预览缓存改造
- 新增 AI 生图平台成品回传接口、任务状态与表单规则
- 补齐 AI 生成标签、任务详情补全、相关测试与对接文档
2026-03-25 10:28:02 +08:00

134 lines
3.6 KiB
TypeScript

import { chunkUpload, getAllPendingUploads } from '@/lib/chunk-upload'
jest.mock('@/lib/chunk-upload-config', () => ({
getOptimalChunkSize: jest.fn().mockResolvedValue(1024 * 1024),
getOptimalConcurrency: jest.fn().mockResolvedValue(1),
getChunkTimeout: jest.fn(() => 30_000),
}))
type MockRequest<T = unknown> = {
result?: T
error?: unknown
onsuccess?: ((event?: { target: { result: T } }) => void) | null
onerror?: ((event?: { target: { error: unknown } }) => void) | null
onupgradeneeded?: ((event?: { target: { result: unknown } }) => void) | null
}
function createAsyncRequest<T>(executor: () => T): MockRequest<T> {
const request: MockRequest<T> = {}
setTimeout(() => {
try {
request.result = executor()
request.onsuccess?.({ target: { result: request.result as T } })
} catch (error) {
request.error = error
request.onerror?.({ target: { error } })
}
}, 0)
return request
}
function createIndexedDbMock(): IDBFactory {
const records = new Map<string, unknown>()
let initialized = false
const objectStore = {
put: (value: { fileId: string }) => createAsyncRequest(() => {
records.set(value.fileId, value)
return value.fileId
}),
get: (key: string) => createAsyncRequest(() => records.get(key)),
delete: (key: string) => createAsyncRequest(() => {
records.delete(key)
return undefined
}),
count: () => createAsyncRequest(() => records.size),
getAll: () => createAsyncRequest(() => Array.from(records.values())),
clear: () => createAsyncRequest(() => {
records.clear()
return undefined
}),
}
const db = {
objectStoreNames: {
contains: () => initialized,
},
createObjectStore: () => {
initialized = true
return objectStore
},
transaction: () => ({
objectStore: () => objectStore,
}),
}
return {
open: jest.fn(() => {
const request: MockRequest<typeof db> = {}
setTimeout(() => {
request.result = db
if (!initialized) {
request.onupgradeneeded?.({ target: { result: db } })
initialized = true
}
request.onsuccess?.({ target: { result: db } })
}, 0)
return request as IDBOpenDBRequest
}),
} as unknown as IDBFactory
}
describe('chunkUpload', () => {
beforeEach(() => {
jest.clearAllMocks()
Object.defineProperty(global, 'indexedDB', {
configurable: true,
writable: true,
value: createIndexedDbMock(),
})
Object.defineProperty(global, 'fetch', {
configurable: true,
writable: true,
value: jest.fn(),
})
})
test('空文件会在初始化前被直接拦截', async () => {
const emptyFile = new File([], 'empty.psd', { type: 'application/octet-stream' })
await expect(chunkUpload(emptyFile, { materialType: '任务-详情' })).rejects.toThrow('为空')
expect(global.fetch).not.toHaveBeenCalled()
await expect(getAllPendingUploads()).resolves.toEqual([])
})
test('upload/init 返回 400 时不再无限重试', async () => {
;(global.fetch as jest.Mock).mockResolvedValue({
ok: false,
status: 400,
json: async () => ({ error: '上传文件不能为空' }),
})
const file = new File(['hello'], 'source.psb', { type: 'application/octet-stream' })
await expect(
chunkUpload(
file,
{ materialType: '任务-详情' },
{ maxRetries: 5, retryDelay: 0 },
),
).rejects.toThrow('上传文件不能为空')
expect(global.fetch).toHaveBeenCalledTimes(1)
await expect(getAllPendingUploads()).resolves.toEqual([])
})
})