5ea52e5291
行数据按需拉取(审查 F5 后续): - V125 给 biz_file_result 补 updated_at(DEFAULT/ON UPDATE 由数据库维护, 实体标注 insertStrategy/updateStrategy=NEVER —— 否则 selectById→updateById 的 写回会把旧值写回去、ON UPDATE 不触发,版本信号静默冻结) - 装配器回传 rowsVersion=「最后变更时间毫秒#行数」,5 个品牌工具页版本未变即跳过 带行明细的重型 batch;前端变更信号为 rowsVersion + status/fileStatus/fileReady 复合 (任务收尾常见「行早写完、之后才置成功」,只看行版本会把界面卡在旧状态) 修复线上缺陷(同一功能验证时暴露): - TaskProgressLightAssembler 列裁剪漏选 module_type 却用它做模块过滤 → getModuleType() 恒为 null → light 恒把任务判成 missing;第七批把 light 接进 跟价/定时匹配/商品风险的轮询后,消费方会把运行中任务判为 FAILED - 补选中列 + 守卫用例 taskQueryMustSelectModuleType(已反向验证:去掉修复即红) - 前端 lightClaimsAllTasksMissing:整体性 missing 结论用重型端点复核后再采信 契约与文档:light 白名单补 rowsVersion(Java 契约测试 / spec 06 §2 / 12 个端点描述) 测试:mvn test 2901 全绿;前端 npm test 765 全绿
62 lines
3.1 KiB
TypeScript
62 lines
3.1 KiB
TypeScript
import { test } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { readFileSync } from 'node:fs'
|
||
import { fileURLToPath } from 'node:url'
|
||
import { dirname, join } from 'node:path'
|
||
|
||
/**
|
||
* 「行数据按需拉取」接线守卫(审查 F5 后续):
|
||
* 5 个品牌工具页用轻量端点的结果行版本跳过重型 batch;这里守住两件最容易写错的事
|
||
* ——①接线没被误删;②跳过分支的返回值必须符合调用方契约(曾经把 shopDataCrawl 写成裸 return,
|
||
* 而调用方 waitForTerminal 把 falsy 当「任务已消失 → FAILED」,会误杀正在跑的任务)。
|
||
*/
|
||
const PAGES_DIR = join(dirname(fileURLToPath(import.meta.url)), '../src/pages/brand/components')
|
||
|
||
const WIRED_PAGES = [
|
||
'BrandQueryAsinTab.vue',
|
||
'BrandWithdrawTab.vue',
|
||
'BrandPatrolDeleteTab.vue',
|
||
'BrandShopDataCrawlTab.vue',
|
||
'BrandAppearancePatentTab.vue',
|
||
]
|
||
|
||
function sourceOf(page: string) {
|
||
return readFileSync(join(PAGES_DIR, page), 'utf8')
|
||
}
|
||
|
||
test('已接线的品牌工具页仍使用结果行版本跳过重型 batch', () => {
|
||
for (const page of WIRED_PAGES) {
|
||
const source = sourceOf(page)
|
||
assert.ok(source.includes('createRowsVersionTracker'), `${page} 缺少版本跟踪器声明`)
|
||
assert.ok(source.includes('selectTasksNeedingRows'), `${page} 缺少按需拉取判断`)
|
||
assert.ok(source.includes('getModuleProgressLight'), `${page} 缺少轻量端点调用`)
|
||
}
|
||
})
|
||
|
||
test('轻量请求失败必须回退为全量拉取(不得因此少拉任务)', () => {
|
||
// 多任务页:catch 里把 heavyIds 还原成全部任务
|
||
for (const page of ['BrandQueryAsinTab.vue', 'BrandWithdrawTab.vue', 'BrandPatrolDeleteTab.vue']) {
|
||
assert.match(sourceOf(page), /catch\s*\{[\s\S]{0,200}?heavyIds = ids/, `${page} 缺少轻量失败回退`)
|
||
}
|
||
assert.match(sourceOf('BrandAppearancePatentTab.vue'), /catch\s*\{[\s\S]{0,200}?heavyIds = taskIds/,
|
||
'BrandAppearancePatentTab 缺少轻量失败回退')
|
||
|
||
// 单任务页:catch 里不能有 return(直接落到批量拉取)
|
||
const shopDataCrawl = sourceOf('BrandShopDataCrawlTab.vue')
|
||
const catchBlock = shopDataCrawl.match(/catch\s*\{([^}]*)\}/)?.[1] ?? ''
|
||
assert.ok(catchBlock.length > 0, 'BrandShopDataCrawlTab 未找到 catch 分支')
|
||
assert.ok(!catchBlock.includes('return'), 'BrandShopDataCrawlTab 轻量失败时不得提前返回,必须照旧拉批量')
|
||
})
|
||
|
||
test('跳过分支的返回值符合调用方契约', () => {
|
||
const shopDataCrawl = sourceOf('BrandShopDataCrawlTab.vue')
|
||
// waitForTerminal: `if (!exists) return 'FAILED'` —— 任务仍在时必须返回 true
|
||
assert.match(shopDataCrawl, /selectTasksNeedingRows\([\s\S]{0,200}?return true/,
|
||
'BrandShopDataCrawlTab 跳过分支必须 return true(false/undefined 会被当成任务已消失)')
|
||
|
||
const patrolDelete = sourceOf('BrandPatrolDeleteTab.vue')
|
||
// waitForTaskTerminal: `missingTaskIds.includes(taskId)` —— 未消失时返回空数组
|
||
assert.match(patrolDelete, /selectTasksNeedingRows\([\s\S]{0,300}?return \[\]/,
|
||
'BrandPatrolDeleteTab 跳过分支必须返回空 missingTaskIds 数组')
|
||
})
|