import { test } from 'node:test' import assert from 'node:assert/strict' import { http } from '../src/shared/api/http.ts' import { API_ENDPOINTS } from '../src/shared/api/endpoints.ts' import { getModuleProgressLight, type ProgressLightModule, type TaskProgressLightItem, } from '../src/shared/api/progress-light.ts' import type { TaskProgressBatchVo } from '../src/shared/api/types/task.ts' function setupWindow() { ;(globalThis as Record).window = { localStorage: { getItem: () => '42' }, location: { origin: 'http://localhost' }, } } function mockRequest( t: Parameters[1] extends (t: infer T) => unknown ? T : never, impl: (config: { url?: string; method?: string; params?: Record; data?: unknown; timeout?: number }) => Promise, ) { t.mock.method(http, 'request', impl as never) } const okResponse = (data: unknown) => Promise.resolve({ data: { success: true, message: 'ok', data } }) const LIGHT_MODULES: ProgressLightModule[] = [ 'publish', 'deleteBrand', 'productRisk', 'shopMatch', 'patrolDelete', 'queryAsin', 'shopDataCrawl', 'withdraw', 'priceTrack', 'similarAsin', 'appearancePatent', 'collectData', ] test('test_light_url_module', async (t) => { setupWindow() const seen: { url?: string }[] = [] mockRequest(t, (config) => { seen.push(config) return okResponse({ items: [], missingTaskIds: [] }) }) for (const module of LIGHT_MODULES) { const endpoint = (API_ENDPOINTS as Record>)[module].progressLight assert.ok(endpoint && endpoint.endsWith('/tasks/progress/light'), `${module} 应有 progressLight 端点: ${endpoint}`) await getModuleProgressLight(module, [1]) } assert.equal(seen.length, LIGHT_MODULES.length) assert.deepEqual(seen.map((c) => c.url), LIGHT_MODULES.map((m) => { const raw = (API_ENDPOINTS as Record>)[m].progressLight return `/newApi${raw}` }), '每个模块的 light URL 与端点常量一一对应') }) test('test_light_payload', async (t) => { setupWindow() let captured: { url?: string; method?: string; data?: unknown } = {} mockRequest(t, (config) => { captured = config return okResponse({ items: [], missingTaskIds: [] }) }) await getModuleProgressLight('similarAsin', [5, 3, 5]) assert.equal(captured.url, '/newApi/api/similar-asin/tasks/progress/light') assert.equal(captured.method, 'POST') assert.deepEqual(captured.data, { taskIds: [3, 5] }, 'taskIds 去重排序后进 body') const empty = await getModuleProgressLight('similarAsin', []) assert.deepEqual(empty, { items: [], missingTaskIds: [] }, '空 taskIds 不发请求直接返回空结构') }) test('test_light_response_shape', async (t) => { setupWindow() mockRequest(t, () => okResponse({ items: [ { taskId: 1, status: 'RUNNING', statusCode: null, fileStatus: null, fileError: null, fileReady: false, updatedAt: '2026-09-01T10:00:00' }, { taskId: 2, status: 'SUCCESS', statusCode: null, fileStatus: 'SUCCESS', fileError: null, fileReady: true, updatedAt: '2026-09-01T10:05:00' }, ], missingTaskIds: [], })) const result = await getModuleProgressLight('publish', [1, 2]) assert.equal(result.items.length, 2, 'items 数组保留') assert.equal(result.items[0].taskId, 1) assert.equal(result.items[0].status, 'RUNNING') assert.equal(result.items[0].fileReady, false) assert.equal(result.items[1].fileStatus, 'SUCCESS') assert.equal(result.items[1].fileReady, true) assert.equal(result.items[1].updatedAt, '2026-09-01T10:05:00') assert.deepEqual(result.missingTaskIds, []) }) test('test_light_fallback_flag', async (t) => { setupWindow() let called = 0 mockRequest(t, () => { called += 1 return okResponse({ items: [], missingTaskIds: [] }) }) const result = await getModuleProgressLight('withdraw', [7], { useLight: false }) assert.equal(called, 0, 'useLight=false 时不发起 light 请求(调用方自行回退旧端点)') assert.deepEqual(result, { items: [], missingTaskIds: [] }) const on = await getModuleProgressLight('withdraw', [7], { useLight: true }) assert.equal(called, 1, 'useLight=true(默认)时发起 light 请求') assert.deepEqual(on, { items: [], missingTaskIds: [] }) }) test('test_light_missing_ids', async (t) => { setupWindow() mockRequest(t, () => okResponse({ items: [{ taskId: 1, status: 'RUNNING' }], missingTaskIds: [9, 10], })) const result = await getModuleProgressLight('collectData', [1, 9, 10]) assert.deepEqual(result.missingTaskIds, [9, 10], 'missingTaskIds 映射为 number[]') assert.equal(result.items.length, 1) mockRequest(t, () => okResponse({ items: [{ taskId: 1, status: 'RUNNING' }] })) const noKey = await getModuleProgressLight('collectData', [1], { force: true }) assert.deepEqual(noKey.missingTaskIds, [], '后端缺 missingTaskIds 字段时默认空数组') }) test('test_light_timeout', async (t) => { setupWindow() let captured: { timeout?: number } = {} mockRequest(t, (config) => { captured = config return okResponse({ items: [], missingTaskIds: [] }) }) await getModuleProgressLight('priceTrack', [1], { force: true }) assert.equal(captured.timeout, 10000, '进度类请求默认 10s 超时') }) test('test_light_type_compat', async (t) => { setupWindow() mockRequest(t, () => okResponse({ items: [{ taskId: 1, status: 'PENDING' }], missingTaskIds: [], })) const result = await getModuleProgressLight('shopMatch', [1], { force: true }) const batch: TaskProgressBatchVo = result assert.ok('items' in batch && 'missingTaskIds' in batch, '结构与现有 TaskProgressBatchVo 兼容') assert.equal(batch.items[0].taskId, 1) assert.equal(batch.items[0].status, 'PENDING') }) test('test_light_snapshot', async (t) => { setupWindow() const calls: Array<{ url?: string; method?: string; params?: Record; data?: unknown }> = [] mockRequest(t, (config) => { calls.push(config) return okResponse({ items: [{ taskId: 1, status: 'RUNNING', fileReady: false }], missingTaskIds: [] }) }) await getModuleProgressLight('appearancePatent', [1], { force: true }) await getModuleProgressLight('deleteBrand', [2], { force: true }) assert.equal(calls[0].url, '/newApi/api/appearance-patent/tasks/progress/light') assert.equal(calls[0].method, 'POST') assert.deepEqual(calls[0].data, { taskIds: [1] }) assert.equal(calls[1].url, '/newApi/api/delete-brand/tasks/progress/light') assert.deepEqual(calls[1].data, { taskIds: [2] }) }) test('test_light_reexport', async () => { setupWindow() const javaModules = await import('../src/shared/api/java-modules.ts') const direct = await import('../src/shared/api/progress-light.ts') assert.equal(javaModules.getModuleProgressLight, direct.getModuleProgressLight, 'java-modules 应 re-export getModuleProgressLight') assert.equal(javaModules.TaskProgressLightItem, direct.TaskProgressLightItem, '类型应同一引用') })