52b55df7b2
判死线(治 28131 型「主线程卡死、心跳线程照发」): - 判据改看 biz_task_scope_state.last_chunk_at(HTTP 心跳不刷新它);从未上报跳过不判 - 中央线覆盖 DELETE_BRAND/PRODUCT_RISK_RESOLVE/PRICE_TRACK/SHOP_MATCH/PATROL_DELETE/QUERY_ASIN/WITHDRAW - 自带线接入 COLLECT_DATA/SIMILAR_ASIN/APPEARANCE_PATENT/SHOP_DATA_CRAWL/PUBLISH/BRAND - 客户端心跳带处理位置 progressText,判死文案含最后位置;no-result-upload-timeout-minutes 默认 180(0 关闭) 同期带上另一工作流的待发改动:跟价换 IP 重试、品牌检测重试上限与 LLM 并发下调、 教程包后台管理页与 V126 迁移、admin-vue 教程记录页。
59 lines
3.7 KiB
TypeScript
59 lines
3.7 KiB
TypeScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readSource } from './helpers.ts'
|
|
import { adminPages } from '../src/router/routes.ts'
|
|
|
|
/** 教程管理页(工具台「立即下载教程」的后台入口):上传走 presign 直传、列表取最新、下载/删除留驻反馈。 */
|
|
|
|
test('align_tutorial_page_registered', () => {
|
|
const page = adminPages.find((p) => p.path === 'records/tutorial')
|
|
assert.ok(page, '路由注册表应包含 records/tutorial')
|
|
assert.equal(page.menuKey, 'admin_tutorial')
|
|
assert.equal(page.title, '教程管理')
|
|
})
|
|
|
|
test('align_tutorial_page_wiring', () => {
|
|
const page = readSource('src/pages/records/RecordsTutorialPage.vue')
|
|
// 上传入口:版本号(可空)→ 选择 zip → 直传(进度条)→ 成功提示留驻弹窗。
|
|
assert.match(page, /上传教程包/, '存在上传入口按钮')
|
|
assert.match(page, /uploadTutorialPackage/, '上传走教程包直传链路')
|
|
assert.match(page, /label="版本号"/, '上传弹窗提供版本号输入')
|
|
assert.match(page, /newVersion\.value\.trim\(\)/, '版本号去空白后随上传提交')
|
|
assert.match(page, /请选择 zip 压缩包/, '未选文件时提示')
|
|
assert.match(page, /仅支持 \.zip 格式/, '格式校验提示')
|
|
assert.match(page, /上传成功,工具台「立即下载教程」已切换为该包/, '成功提示说明生效位置')
|
|
assert.match(page, /上传完成,正在登记教程包/, '进度文案')
|
|
// 列表:版本号列 + 上传时间列 + 当前生效标记 + 下载 + 删除 + 空态。
|
|
assert.match(page, /当前生效/, '最新上传的包标记当前生效')
|
|
assert.match(page, /formatFileSize/, '展示包体大小')
|
|
assert.match(page, /row\.version/, '展示版本号列')
|
|
assert.match(page, /toggleSort\('version'\)/, '版本号表头可点击排序')
|
|
assert.match(page, /toggleSort\('createdAt'\)/, '上传时间表头可点击排序')
|
|
assert.match(page, /const sortKey = ref<SortKey>\('createdAt'\)/, '默认排序字段为上传时间')
|
|
assert.match(page, /const sortAsc = ref\(false\)/, '默认降序')
|
|
assert.match(page, /下载/, '操作列提供下载')
|
|
assert.match(page, /<a v-if="row\.fileUrl" class="btn btn-sm dl-btn"[^>]*download/, '下载链接带 download 强制下载')
|
|
assert.match(page, /确认删除选中的/, '批量删除二次确认')
|
|
assert.match(page, /暂无教程包记录/, '空态文案')
|
|
})
|
|
|
|
test('align_tutorial_api_contract', () => {
|
|
const api = readSource('src/pages/records/tutorial-api.ts')
|
|
assert.match(api, /\/api\/admin\/tutorials/, '列表端点')
|
|
assert.match(api, /TUTORIAL_UPLOAD_ENDPOINT = '\/api\/admin\/tutorial'/, '上传端点前缀')
|
|
assert.match(api, /\$\{TUTORIAL_UPLOAD_ENDPOINT\}\/presign/, '直传预签名端点')
|
|
assert.match(api, /\$\{TUTORIAL_UPLOAD_ENDPOINT\}\/confirm/, '确认端点')
|
|
assert.match(api, /\$\{TUTORIAL_UPLOAD_ENDPOINT\}\/delete/, '删除端点')
|
|
assert.match(api, /params: \{ file_name: fileName, version \}/, '预签名回传文件名与版本号')
|
|
assert.match(api, /params: \{ object_key: objectKey, file_name: fileName, version \}/, '确认回传对象 key、文件名与版本号')
|
|
assert.match(api, /withCredentials: false/, '直传实例不挂会话拦截器')
|
|
})
|
|
|
|
test('align_tutorial_model_parsers', () => {
|
|
const model = readSource('src/pages/records/tutorial-model.ts')
|
|
assert.match(model, /r\.file_name \?\? r\.fileName/, '兼容 snake/camel 文件名')
|
|
assert.match(model, /r\.file_url \?\? r\.fileUrl/, '兼容 snake/camel 下载链接')
|
|
assert.match(model, /r\.file_size \?\? r\.fileSize/, '兼容 snake/camel 文件大小')
|
|
assert.match(model, /version: text\(r\.version\)/, '解析版本号(缺失为空串,兼容历史行)')
|
|
})
|