task-1: 新增 API_ENDPOINTS 路径常量表(frontend-api-adapter)
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
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',
|
||||
'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',
|
||||
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',
|
||||
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',
|
||||
},
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
taskActivate: '/api/appearance-patent/tasks/{taskId}/activate',
|
||||
taskDelete: '/api/appearance-patent/tasks/{taskId}',
|
||||
resultDownload: '/api/appearance-patent/results/{resultId}/download',
|
||||
},
|
||||
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',
|
||||
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',
|
||||
},
|
||||
}, '常量表快照不应漂移')
|
||||
})
|
||||
|
||||
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}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user