feat(import): 全链编排与导入报告
Task 6(Plan 3):四家写入器汇流为默认编排管道。 - import.service:注入 events/pipeline 缺席时走真实事件流(runWxrAdapter 惰性 async generator,mapConfig.locale=site.default_locale 前置事实 8); 分派严格串行 content→term→media→route(禁 Promise.all);finish 硬序 term(sync)→content(sync)→media(await)→route(await);counts 由 ⊕ 覆盖 改为逐 key 求和聚合;新增 stale 回收(同站点 queued/running >24h 一条 SQL 置 stale);报告新增 paths/productsPath+newsPath、issueGroups 六组、 sanitize 清洗统计(事件流 changedRows/byDiffType 聚合)、adapterTiming。 - content-writer:NULL-entry 收编(快照行在而 content_entry 空,五列先验 三分支:无先验新建挂接/同 checksum 挂接 skipped/异 checksum 挂接新版本)。 - route-writer:attachment 降级汇总按去重 sourceId 计(重跑仍必现); normalizeListPath 导出;writeAttachmentRoute 返回 written/resolved/blocked。 - 四家写入器 identity 收敛共享基类型 WriterContext = Pick<ImportJobContext,'db'|'siteId'|'connectionId'>(type-only import, 无运行时环依赖)。 - CLI import 增 --products-path/--news-path(前置事实 9,缺省 /products/ /news/)记入 job 报告。 - 测试:tests/integration/import-full.test.ts 6 例(全链 zh-CN 接线证明 +published 全 NULL/报告四域计数+sanitize+分组/幂等重跑零写语义/ 无 media 快照域缺失不失败/NULL-entry 收编/stale 回收/REPORT_WRITE_FAILED 遗留补测);import-job 契约测试钉死 counts 求和(100⊕3=103)。 全量 323 通过;typecheck/lint 干净。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
// 显式 id 不存在或属他站 → SNAPSHOT_NOT_FOUND;站点不存在 → SITE_NOT_FOUND;均零 job 行;
|
||||
// 5. failed 路径:管道抛错 → running→failed(error_code/error_summary/finished_at,
|
||||
// '[pipeline]' 前缀),重提交同 key 复用失败 job 不重跑;管道抛 ImportError 保留原 code;
|
||||
// 6. Outcome 契约:ctx.report 累加 ⊕ 返回值(counts 同名键返回值覆盖)→ succeeded_with_issues;
|
||||
// 6. Outcome 契约:ctx.report 累加 ⊕ 返回值(counts 同名键**求和**——Task 6 编排聚合)→ succeeded_with_issues;
|
||||
// 7. 复用口径忠实化:reuse 返回只含 job 行锚定字段(快照/版本字段缺席);
|
||||
// 8. 23505 收敛确定性探针:未提交孪生 job 持有幂等键(真实 PG 锁语义,零代理)。
|
||||
// 运行于 globalSetup 临时库(ADR-015);storage/报告根用 os.tmpdir 临时目录,finally 清理。
|
||||
@@ -434,7 +434,7 @@ describe('import_jobs 生命周期(Task 1 骨架)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('Outcome 契约:ctx.report 累加 ⊕ 返回值(返回值 counts 覆盖同名键),issues 非空 → succeeded_with_issues,计数与问题入报告', async () => {
|
||||
it('Outcome 契约:ctx.report 累加 ⊕ 返回值按**键求和**(Task 6 编排聚合),issues 非空 → succeeded_with_issues,计数与问题入报告', async () => {
|
||||
await withTestDb(async (db) => {
|
||||
const ctx = await makeCtx()
|
||||
try {
|
||||
@@ -449,7 +449,7 @@ describe('import_jobs 生命周期(Task 1 骨架)', () => {
|
||||
pipeline: async (jobCtx) => {
|
||||
// 写入器式累加(Task 6 写入器的使用形态)
|
||||
jobCtx.report.counts['terms'] = 2
|
||||
jobCtx.report.counts['contentEntries'] = 999 // 被返回值同名键覆盖
|
||||
jobCtx.report.counts['contentEntries'] = 100 // 与返回值同名键**求和**(Task 6 钉死:防覆盖安全网)
|
||||
jobCtx.report.issues.push('自建缺失 term:1(category/uncategorized)')
|
||||
return {
|
||||
counts: { contentEntries: 3, routes: 8 },
|
||||
@@ -468,8 +468,8 @@ describe('import_jobs 生命周期(Task 1 骨架)', () => {
|
||||
await readFile(join(ctx.importReportRoot, result.jobId, 'reports', 'import.json'), 'utf8'),
|
||||
) as { status: string; counts: Record<string, number>; issues: string[] }
|
||||
expect(report.status).toBe('succeeded_with_issues')
|
||||
// 汇合规则:counts 按键合并(返回值覆盖累加器同名键)、issues 累加器在前返回值在后
|
||||
expect(report.counts).toStrictEqual({ terms: 2, contentEntries: 3, routes: 8 })
|
||||
// 汇合规则:counts 按键**求和**(Task 6 钉死——CLOBBER 废弃)、issues 累加器在前返回值在后
|
||||
expect(report.counts).toStrictEqual({ terms: 2, contentEntries: 103, routes: 8 })
|
||||
expect(report.issues).toStrictEqual([
|
||||
'自建缺失 term:1(category/uncategorized)',
|
||||
'悬空 attachment 引用:2 条(mini 203/204)',
|
||||
|
||||
Reference in New Issue
Block a user