From d1870f13827e3a4be65fe90389c5dd4c6ec6ac56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Mon, 31 Aug 2026 19:15:10 +0800 Subject: [PATCH] =?UTF-8?q?task-1:=20=E6=96=B0=E5=A2=9E=20API=5FENDPOINTS?= =?UTF-8?q?=20=E8=B7=AF=E5=BE=84=E5=B8=B8=E9=87=8F=E8=A1=A8=EF=BC=88fronte?= =?UTF-8?q?nd-api-adapter=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-vue/src/shared/api/endpoints.ts | 242 +++++++++++++++ frontend-vue/tests/endpoints.test.ts | 366 +++++++++++++++++++++++ 2 files changed, 608 insertions(+) create mode 100644 frontend-vue/src/shared/api/endpoints.ts create mode 100644 frontend-vue/tests/endpoints.test.ts diff --git a/frontend-vue/src/shared/api/endpoints.ts b/frontend-vue/src/shared/api/endpoints.ts new file mode 100644 index 00000000..683abf66 --- /dev/null +++ b/frontend-vue/src/shared/api/endpoints.ts @@ -0,0 +1,242 @@ +/** Java 主后端 API 路径常量表(不含 /newApi/api 前缀,前缀由 buildJavaUrl 统一加) */ + +export const 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', + }, +} as const + +export type ApiEndpointGroup = { + [K in keyof typeof API_ENDPOINTS]: (typeof API_ENDPOINTS)[K][keyof (typeof API_ENDPOINTS)[K]] +} diff --git a/frontend-vue/tests/endpoints.test.ts b/frontend-vue/tests/endpoints.test.ts new file mode 100644 index 00000000..12a8526b --- /dev/null +++ b/frontend-vue/tests/endpoints.test.ts @@ -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>)[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>)[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)) { + assert.ok( + /^[a-z][a-zA-Z0-9]*$/.test(key), + `端点键应为小驼峰: ${moduleKey}.${key}`, + ) + } + } +})