Files
crawler-plugin/frontend-vue/tests/progress-light.test.ts
T
huangzd1997 fa5a59e5cd
Build Backend JAR / build (push) Has been cancelled
task-116: 后台管理新增/导入表单弹窗化 + 分组管理独立菜单与UI优化 + 轻量进度端点
- 后台管理页(admin)所有面板的新增/导入表单改为弹窗操作,原有字段 ID 全部保留、提交逻辑不变;导入删除入口不再触发二次确认拦截
- 分组管理升级为独立菜单(V102 + schema initializer),移除 5 个面板内的管理分组按钮;分组列表改为蓝白主题、增加权限分组横幅
- Python 侧 group-manage 权限守卫(_ensure_backend_menu_access 补充 group-manage)
- 引入 V101(biz_task_file_job 复合索引)+ 新增 TaskProgressLight/TaskFileJob 轻量端点与进度聚合作
- 前端 progress-light / page-separated-loads / dispatch-guard 共享模块及单元测试
2026-09-01 12:54:13 +08:00

167 lines
6.9 KiB
TypeScript

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<string, unknown>).window = {
localStorage: { getItem: () => '42' },
location: { origin: 'http://localhost' },
}
}
function mockRequest(
t: Parameters<typeof test>[1] extends (t: infer T) => unknown ? T : never,
impl: (config: { url?: string; method?: string; params?: Record<string, unknown>; data?: unknown; timeout?: number }) => Promise<unknown>,
) {
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<string, Record<string, string>>)[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<string, Record<string, string>>)[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<TaskProgressLightItem> = 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<string, unknown>; 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, '类型应同一引用')
})