9ffd68bae5
- admin-vue 新增「记录与版本 → 教程管理」页(menuKey admin_tutorial):
zip 浏览器直传 MinIO(presign → PUT 带进度 → confirm 落库)+ 列表/下载/删除,
最新一条标「当前生效」;补操作指引与 align-tutorial 守卫测试。
- frontend-vue 工具台「立即下载教程」进页面取 /api/tutorial/latest(最新上传优先),
接口异常或无记录回退历史固定直链,按钮不失效。
- Java 侧 tutorial 模块与 V119 迁移已在 dc6e8924 提交并上线(2026-09-13)。
vue-tsc + 两端单测(工具台 695 / 后台 1617)通过;生产已实测
presign→PUT→confirm→latest 切换→删除回落全链路。
399 lines
16 KiB
TypeScript
399 lines
16 KiB
TypeScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { API_ENDPOINTS } from '../src/shared/api/endpoints.ts'
|
|
|
|
const MODULE_KEYS = [
|
|
'files',
|
|
'dedupe',
|
|
'split',
|
|
'convert',
|
|
'publish',
|
|
'deleteBrand',
|
|
'productRisk',
|
|
'shopMatch',
|
|
'patrolDelete',
|
|
'queryAsin',
|
|
'shopDataCrawl',
|
|
'withdraw',
|
|
'priceTrack',
|
|
'similarAsin',
|
|
'appearancePatent',
|
|
'userSecret',
|
|
'notification',
|
|
'collectData',
|
|
'imageVideo',
|
|
'brand',
|
|
'permission',
|
|
'digitalHuman',
|
|
]
|
|
|
|
function allEndpointValues(): string[] {
|
|
const values: string[] = []
|
|
for (const moduleKey of Object.keys(API_ENDPOINTS)) {
|
|
const group = (API_ENDPOINTS as Record<string, Record<string, string>>)[moduleKey]
|
|
for (const value of Object.values(group)) {
|
|
values.push(value)
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
|
|
test('test_endpoints_all_modules_present', () => {
|
|
for (const moduleKey of MODULE_KEYS) {
|
|
assert.ok(
|
|
Object.prototype.hasOwnProperty.call(API_ENDPOINTS, moduleKey),
|
|
`缺少模块分组: ${moduleKey}`,
|
|
)
|
|
assert.ok(
|
|
Object.keys((API_ENDPOINTS as Record<string, Record<string, string>>)[moduleKey]).length > 0,
|
|
`模块分组为空: ${moduleKey}`,
|
|
)
|
|
}
|
|
})
|
|
|
|
test('test_endpoint_path_format', () => {
|
|
for (const value of allEndpointValues()) {
|
|
assert.ok(
|
|
value.startsWith('/api/'),
|
|
`端点应以 /api/ 开头: ${value}`,
|
|
)
|
|
}
|
|
})
|
|
|
|
test('test_no_newapi_prefix_in_endpoint', () => {
|
|
for (const value of allEndpointValues()) {
|
|
assert.ok(
|
|
!value.includes('/newApi'),
|
|
`端点不应包含 /newApi 前缀: ${value}`,
|
|
)
|
|
}
|
|
})
|
|
|
|
test('test_progress_batch_paths_match_controllers', () => {
|
|
const progressPaths = allEndpointValues().filter((value) =>
|
|
value.endsWith('/tasks/progress/batch'),
|
|
)
|
|
assert.deepEqual(
|
|
[...progressPaths].sort(),
|
|
[
|
|
'/api/similar-asin/tasks/progress/batch',
|
|
'/api/appearance-patent/tasks/progress/batch',
|
|
'/api/collect-data/tasks/progress/batch',
|
|
'/api/publish/tasks/progress/batch',
|
|
'/api/delete-brand/tasks/progress/batch',
|
|
'/api/patrol-delete/tasks/progress/batch',
|
|
'/api/price-track/tasks/progress/batch',
|
|
'/api/product-risk-resolve/tasks/progress/batch',
|
|
'/api/shop-match/tasks/progress/batch',
|
|
'/api/query-asin/tasks/progress/batch',
|
|
'/api/shop-data-crawl/tasks/progress/batch',
|
|
'/api/withdraw/tasks/progress/batch',
|
|
].sort(),
|
|
'progress/batch 端点应与 Java controller 路由一一对应',
|
|
)
|
|
})
|
|
|
|
test('test_download_paths_match', () => {
|
|
const resultDownloads = allEndpointValues().filter((value) =>
|
|
value.endsWith('/download'),
|
|
)
|
|
assert.ok(resultDownloads.length >= 12, `resultDownload 端点数量不足: ${resultDownloads.length}`)
|
|
for (const value of resultDownloads) {
|
|
assert.ok(
|
|
/\/results\/\{[a-zA-Z]+\}\/download$/.test(value) ||
|
|
/\/tasks\/\{[a-zA-Z]+\}\/download$/.test(value),
|
|
`download 端点应匹配 /results/{id}/download 或 /tasks/{id}/download 模式: ${value}`,
|
|
)
|
|
}
|
|
})
|
|
|
|
test('test_endpoints_frozen_snapshot', () => {
|
|
assert.deepEqual(API_ENDPOINTS, {
|
|
"files": {
|
|
"upload": "/api/files/upload",
|
|
"excelInfo": "/api/files/excel-info"
|
|
},
|
|
"dedupe": {
|
|
"run": "/api/dedupe/run",
|
|
"runProgress": "/api/dedupe/run/{runId}/progress",
|
|
"history": "/api/dedupe/history",
|
|
"historyDelete": "/api/dedupe/history/{resultId}",
|
|
"resultDownload": "/api/dedupe/results/{resultId}/download"
|
|
},
|
|
"split": {
|
|
"run": "/api/split/run",
|
|
"history": "/api/split/history",
|
|
"historyDelete": "/api/split/history/{resultId}",
|
|
"resultDownload": "/api/split/results/{resultId}/download"
|
|
},
|
|
"convert": {
|
|
"run": "/api/convert/run",
|
|
"history": "/api/convert/history",
|
|
"historyDelete": "/api/convert/history/{resultId}",
|
|
"templates": "/api/convert/templates",
|
|
"templatesImport": "/api/convert/templates/import",
|
|
"templateDefault": "/api/convert/templates/{templateCode}/default",
|
|
"templateDelete": "/api/convert/templates/{templateCode}",
|
|
"resultDownload": "/api/convert/results/{resultId}/download"
|
|
},
|
|
"publish": {
|
|
"parse": "/api/publish/parse",
|
|
"taskActivate": "/api/publish/tasks/{taskId}/activate",
|
|
"fileActivate": "/api/publish/tasks/{taskId}/files/{fileId}/activate",
|
|
"items": "/api/publish/tasks/{taskId}/items",
|
|
"progressBatch": "/api/publish/tasks/progress/batch",
|
|
"progressLight": "/api/publish/tasks/progress/light",
|
|
"result": "/api/publish/tasks/{taskId}/result",
|
|
"dashboard": "/api/publish/dashboard",
|
|
"history": "/api/publish/history",
|
|
"taskDelete": "/api/publish/tasks/{taskId}"
|
|
},
|
|
"deleteBrand": {
|
|
"run": "/api/delete-brand/run",
|
|
"history": "/api/delete-brand/history",
|
|
"historyDelete": "/api/delete-brand/history/{resultId}",
|
|
"taskDetail": "/api/delete-brand/tasks/{taskId}",
|
|
"taskDetails": "/api/delete-brand/tasks/batch",
|
|
"taskDownload": "/api/delete-brand/tasks/{taskId}/download",
|
|
"resultDownload": "/api/delete-brand/results/{resultId}/download",
|
|
"resultSubmit": "/api/delete-brand/tasks/{taskId}/result",
|
|
"progressBatch": "/api/delete-brand/tasks/progress/batch",
|
|
"progressLight": "/api/delete-brand/tasks/progress/light"
|
|
},
|
|
"productRisk": {
|
|
"candidates": "/api/product-risk-resolve/candidates",
|
|
"candidateAdd": "/api/product-risk-resolve/candidates",
|
|
"candidateDelete": "/api/product-risk-resolve/candidates/{id}",
|
|
"countryPreference": "/api/product-risk-resolve/country-preference",
|
|
"matchShops": "/api/product-risk-resolve/match-shops",
|
|
"dashboard": "/api/product-risk-resolve/dashboard",
|
|
"history": "/api/product-risk-resolve/history",
|
|
"historyDelete": "/api/product-risk-resolve/history/{resultId}",
|
|
"taskCreate": "/api/product-risk-resolve/tasks",
|
|
"taskDelete": "/api/product-risk-resolve/tasks/{taskId}",
|
|
"taskBatch": "/api/product-risk-resolve/tasks/batch",
|
|
"progressBatch": "/api/product-risk-resolve/tasks/progress/batch",
|
|
"progressLight": "/api/product-risk-resolve/tasks/progress/light",
|
|
"pendingShopResult": "/api/product-risk-resolve/pending-shop-result",
|
|
"resultDownload": "/api/product-risk-resolve/results/{resultId}/download"
|
|
},
|
|
"shopMatch": {
|
|
"candidates": "/api/shop-match/candidates",
|
|
"candidateAdd": "/api/shop-match/candidates",
|
|
"candidateDelete": "/api/shop-match/candidates/{id}",
|
|
"countryPreference": "/api/shop-match/country-preference",
|
|
"matchShops": "/api/shop-match/match-shops",
|
|
"dashboard": "/api/shop-match/dashboard",
|
|
"history": "/api/shop-match/history",
|
|
"historyDelete": "/api/shop-match/history/{resultId}",
|
|
"taskCreate": "/api/shop-match/tasks",
|
|
"taskDelete": "/api/shop-match/tasks/{taskId}",
|
|
"taskActivate": "/api/shop-match/tasks/{taskId}/activate",
|
|
"stageFinished": "/api/shop-match/tasks/{taskId}/stage-finished",
|
|
"taskBatch": "/api/shop-match/tasks/batch",
|
|
"progressBatch": "/api/shop-match/tasks/progress/batch",
|
|
"progressLight": "/api/shop-match/tasks/progress/light",
|
|
"skipAsins": "/api/shop-match/tasks/{taskId}/skip-asins/paginated",
|
|
"resultDownload": "/api/shop-match/results/{resultId}/download"
|
|
},
|
|
"patrolDelete": {
|
|
"candidates": "/api/patrol-delete/candidates",
|
|
"candidateAdd": "/api/patrol-delete/candidates",
|
|
"candidateDelete": "/api/patrol-delete/candidates/{id}",
|
|
"conditions": "/api/patrol-delete/conditions",
|
|
"conditionAdd": "/api/patrol-delete/conditions",
|
|
"conditionDelete": "/api/patrol-delete/conditions/{id}",
|
|
"matchShops": "/api/patrol-delete/match-shops",
|
|
"dashboard": "/api/patrol-delete/dashboard",
|
|
"history": "/api/patrol-delete/history",
|
|
"historyDelete": "/api/patrol-delete/history/{resultId}",
|
|
"progressBatch": "/api/patrol-delete/tasks/progress/batch",
|
|
"progressLight": "/api/patrol-delete/tasks/progress/light",
|
|
"taskCreate": "/api/patrol-delete/tasks",
|
|
"taskDelete": "/api/patrol-delete/tasks/{taskId}",
|
|
"taskResult": "/api/patrol-delete/tasks/{taskId}/result",
|
|
"resultDownload": "/api/patrol-delete/results/{resultId}/download"
|
|
},
|
|
"queryAsin": {
|
|
"candidates": "/api/query-asin/candidates",
|
|
"candidateAdd": "/api/query-asin/candidates",
|
|
"candidateDelete": "/api/query-asin/candidates/{id}",
|
|
"matchShops": "/api/query-asin/match-shops",
|
|
"dashboard": "/api/query-asin/dashboard",
|
|
"history": "/api/query-asin/history",
|
|
"historyDelete": "/api/query-asin/history/{resultId}",
|
|
"progressBatch": "/api/query-asin/tasks/progress/batch",
|
|
"progressLight": "/api/query-asin/tasks/progress/light",
|
|
"taskCreate": "/api/query-asin/tasks",
|
|
"taskDelete": "/api/query-asin/tasks/{taskId}",
|
|
"taskResult": "/api/query-asin/tasks/{taskId}/result",
|
|
"resultDownload": "/api/query-asin/results/{resultId}/download"
|
|
},
|
|
"shopDataCrawl": {
|
|
"candidates": "/api/shop-data-crawl/candidates",
|
|
"candidateAdd": "/api/shop-data-crawl/candidates",
|
|
"candidateDelete": "/api/shop-data-crawl/candidates/{id}",
|
|
"countryPreference": "/api/shop-data-crawl/country-preference",
|
|
"matchShops": "/api/shop-data-crawl/match-shops",
|
|
"dashboard": "/api/shop-data-crawl/dashboard",
|
|
"history": "/api/shop-data-crawl/history",
|
|
"historyDelete": "/api/shop-data-crawl/history/{resultId}",
|
|
"progressBatch": "/api/shop-data-crawl/tasks/progress/batch",
|
|
"progressLight": "/api/shop-data-crawl/tasks/progress/light",
|
|
"taskCreate": "/api/shop-data-crawl/tasks",
|
|
"taskDelete": "/api/shop-data-crawl/tasks/{taskId}",
|
|
"resultDownload": "/api/shop-data-crawl/results/{resultId}/download"
|
|
},
|
|
"withdraw": {
|
|
"candidates": "/api/withdraw/candidates",
|
|
"candidateAdd": "/api/withdraw/candidates",
|
|
"candidateDelete": "/api/withdraw/candidates/{id}",
|
|
"candidatesClear": "/api/withdraw/candidates/clear",
|
|
"matchShops": "/api/withdraw/match-shops",
|
|
"dashboard": "/api/withdraw/dashboard",
|
|
"history": "/api/withdraw/history",
|
|
"historyDelete": "/api/withdraw/history/{resultId}",
|
|
"progressBatch": "/api/withdraw/tasks/progress/batch",
|
|
"progressLight": "/api/withdraw/tasks/progress/light",
|
|
"taskCreate": "/api/withdraw/tasks",
|
|
"taskDelete": "/api/withdraw/tasks/{taskId}",
|
|
"taskResult": "/api/withdraw/tasks/{taskId}/result",
|
|
"resultDownload": "/api/withdraw/results/{resultId}/download"
|
|
},
|
|
"priceTrack": {
|
|
"candidates": "/api/price-track/candidates",
|
|
"candidateAdd": "/api/price-track/candidates",
|
|
"candidateDelete": "/api/price-track/candidates/{id}",
|
|
"countryPreference": "/api/price-track/country-preference",
|
|
"matchShops": "/api/price-track/match-shops",
|
|
"taskCreate": "/api/price-track/tasks",
|
|
"taskDelete": "/api/price-track/tasks/{taskId}",
|
|
"tasksBatch": "/api/price-track/tasks/batch",
|
|
"progressBatch": "/api/price-track/tasks/progress/batch",
|
|
"progressLight": "/api/price-track/tasks/progress/light",
|
|
"loopRuns": "/api/price-track/loop-runs",
|
|
"loopRunDetail": "/api/price-track/loop-runs/{loopRunId}",
|
|
"loopRunStop": "/api/price-track/loop-runs/{loopRunId}/stop",
|
|
"loopRunDispatchNext": "/api/price-track/loop-runs/{loopRunId}/dispatch-next",
|
|
"loopRunChildFinished": "/api/price-track/loop-runs/{loopRunId}/child-finished",
|
|
"skipAsinCheck": "/api/price-track/tasks/{taskId}/skip-asin/check",
|
|
"skipAsins": "/api/price-track/tasks/{taskId}/skip-asins/paginated",
|
|
"pendingShopResult": "/api/price-track/pending-shop-result",
|
|
"dispatchFailed": "/api/price-track/tasks/{taskId}/dispatch-failed",
|
|
"dashboard": "/api/price-track/dashboard",
|
|
"history": "/api/price-track/history",
|
|
"historyDelete": "/api/price-track/history/{resultId}",
|
|
"resultDownload": "/api/price-track/results/{resultId}/download"
|
|
},
|
|
"similarAsin": {
|
|
"parse": "/api/similar-asin/parse",
|
|
"dashboard": "/api/similar-asin/dashboard",
|
|
"history": "/api/similar-asin/history",
|
|
"historyDelete": "/api/similar-asin/history/{resultId}",
|
|
"progressBatch": "/api/similar-asin/tasks/progress/batch",
|
|
"progressLight": "/api/similar-asin/tasks/progress/light",
|
|
"taskActivate": "/api/similar-asin/tasks/{taskId}/activate",
|
|
"taskDelete": "/api/similar-asin/tasks/{taskId}",
|
|
"resultDownload": "/api/similar-asin/results/{resultId}/download"
|
|
},
|
|
"appearancePatent": {
|
|
"parse": "/api/appearance-patent/parse",
|
|
"parsedPayload": "/api/appearance-patent/tasks/{taskId}/parsed-payload",
|
|
"queuePayload": "/api/appearance-patent/tasks/{taskId}/queue-payload",
|
|
"dashboard": "/api/appearance-patent/dashboard",
|
|
"history": "/api/appearance-patent/history",
|
|
"historyDelete": "/api/appearance-patent/history/{resultId}",
|
|
"progressBatch": "/api/appearance-patent/tasks/progress/batch",
|
|
"progressLight": "/api/appearance-patent/tasks/progress/light",
|
|
"taskActivate": "/api/appearance-patent/tasks/{taskId}/activate",
|
|
"taskDelete": "/api/appearance-patent/tasks/{taskId}",
|
|
"resultDownload": "/api/appearance-patent/results/{resultId}/download"
|
|
},
|
|
"userSecret": {
|
|
"bundle": "/api/user-secrets",
|
|
"save": "/api/user-secrets/{moduleKey}",
|
|
"clear": "/api/user-secrets/{moduleKey}",
|
|
"check": "/api/user-secrets/{moduleKey}/check",
|
|
"migrate": "/api/user-secrets/migrate",
|
|
"proxyBalance": "/api/user-secrets/proxy-balance"
|
|
},
|
|
"notification": {
|
|
"summary": "/api/notifications/summary",
|
|
"list": "/api/notifications",
|
|
"read": "/api/notifications/{id}/read",
|
|
"readAll": "/api/notifications/read-all"
|
|
},
|
|
"collectData": {
|
|
"parse": "/api/collect-data/parse",
|
|
"countryPreference": "/api/collect-data/country-preference",
|
|
"dashboard": "/api/collect-data/dashboard",
|
|
"history": "/api/collect-data/history",
|
|
"historyDelete": "/api/collect-data/history/{resultId}",
|
|
"progressBatch": "/api/collect-data/tasks/progress/batch",
|
|
"progressLight": "/api/collect-data/tasks/progress/light",
|
|
"taskActivate": "/api/collect-data/tasks/{taskId}/activate",
|
|
"taskDelete": "/api/collect-data/tasks/{taskId}",
|
|
"fail": "/api/collect-data/tasks/{taskId}/fail",
|
|
"items": "/api/collect-data/tasks/{taskId}/items",
|
|
"resultDownload": "/api/collect-data/results/{resultId}/download"
|
|
},
|
|
"imageVideo": {
|
|
"workflowRun": "/api/image-video/workflow/run",
|
|
"workflowResult": "/api/image-video/workflow/result",
|
|
"secrets": "/api/image-video/secrets",
|
|
"taskDetail": "/api/image-video/tasks/{taskId}",
|
|
"voiceClone": "/api/image-video/voice/clone",
|
|
"voiceList": "/api/image-video/voice/list",
|
|
"voiceDelete": "/api/image-video/voice/delete",
|
|
"voiceSynthesis": "/api/image-video/voice/synthesis",
|
|
"douyinCopy": "/api/image-video/douyin-copy"
|
|
},
|
|
"brand": {
|
|
"run": "/api/brand/run",
|
|
"expandFolder": "/api/brand/expand-folder",
|
|
"expandFolderRecursive": "/api/brand/expand-folder-recursive",
|
|
"taskDetail": "/api/brand/tasks/{taskId}",
|
|
"taskCancel": "/api/brand/tasks/{taskId}/cancel",
|
|
"taskDownload": "/api/brand/tasks/{taskId}/download",
|
|
"taskEvents": "/api/brand/tasks/{taskId}/events"
|
|
},
|
|
"permission": {
|
|
"columnPermissions": "/api/admin/permission-users/{uid}/column-permissions"
|
|
},
|
|
"digitalHuman": {
|
|
"versions": "/api/digital-human/versions",
|
|
"upload": "/api/digital-human/versions/upload",
|
|
"latest": "/api/digital-human/versions/latest",
|
|
"detail": "/api/digital-human/versions/{version}",
|
|
"release": "/api/digital-human/versions/{version}/release",
|
|
"setLatest": "/api/digital-human/versions/{version}/set-latest",
|
|
"delete": "/api/digital-human/versions/{version}",
|
|
"downloadUrl": "/api/digital-human/versions/{version}/download-url"
|
|
},
|
|
"tutorial": {
|
|
"latest": "/api/tutorial/latest"
|
|
}
|
|
}, '常量表快照不应漂移')
|
|
})
|
|
|
|
test('test_endpoint_values_no_trailing_slash', () => {
|
|
for (const value of allEndpointValues()) {
|
|
assert.ok(
|
|
!value.endsWith('/'),
|
|
`端点不应以斜杠结尾: ${value}`,
|
|
)
|
|
}
|
|
})
|
|
|
|
test('test_endpoint_keys_convention', () => {
|
|
for (const [moduleKey, group] of Object.entries(API_ENDPOINTS)) {
|
|
assert.ok(/^[a-z][a-zA-Z0-9]*$/.test(moduleKey), `模块键应为小驼峰: ${moduleKey}`)
|
|
for (const key of Object.keys(group as Record<string, string>)) {
|
|
assert.ok(
|
|
/^[a-z][a-zA-Z0-9]*$/.test(key),
|
|
`端点键应为小驼峰: ${moduleKey}.${key}`,
|
|
)
|
|
}
|
|
}
|
|
})
|