test(rest,plugin-approvals,trigger-record-change): 7 个 integration 测试真迁 driver-sql sqlite :memory:(#5704 批次 3) - #5806
Conversation
…ion tests to a real sqlite `:memory:` backend (#5704 batch 3) Batch 3 of the driver-memory test-surface replacement program (#5704), the 真迁 half of the maintainer's 2026-08-06 Q1=C ruling. These seven files were named `integration` while running against a hand-written Map store with a hand-written `matches()`; the store is now `@objectstack/driver-sql` + better-sqlite3 `:memory:`, built the canonical way (examples/app-crm, `cli db clean`, PR #5715's `makeDefaultDriver()`). - packages/rest: export-integration, import-integration, import-job-integration - packages/plugins/plugin-approvals: record-lock-multi-update, record-lock-schedule-run, status-mirror-cascade - packages/triggers/trigger-record-change: record-change-integration Tables are created through the real path (`engine.syncSchemas()` / the post-boot `syncSchemas()` route a late-registered driver uses), never conjured by a store on first write. All three packages gain `@objectstack/driver-sql` as a workspace devDependency; better-sqlite3 comes from its optionalDependency. Two semantic divergences surfaced and were resolved by declaring, not relaxing — both recorded in the PR body: 1. record-change-integration's #3760 case asserted `stamp` was `undefined`. A declared column that was never written reads back as SQL NULL, so the assertion is `?? null` / `toBeNull()` plus a new "the record itself exists" guard — narrower than `toBeFalsy()`, not looser than before. 2. import-job-integration's hand-written `sys_import_job` "minimal mirror" had fallen behind the real object: no `undo_log`, no `reverted_at` (the whole undo feature, #3549's subject) and three datetimes typed `text`. A Map accepted the undeclared columns; a real table answers `no such column: undo_log`. The mirror is retired in favour of `SysImportJob` from @objectstack/platform-objects, already a production dependency of packages/rest. No source changes; test-behavior only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 3 package(s): 14 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
合并后复验补记(AGENTS.md §10 的「重叠面重跑」)。 PR body 里的数字取自分支基线
合并后实测( rest 由 55/782 变 56/784,增量全部来自合并带进来的 #5788 新增文件 变更面复核:PR 实际 changed_files = 11,与申报面一字不差(7 测试文件 + 3 个 package.json + pnpm-lock.yaml);构建顺带改写的 Generated by Claude Code |
CI 在 b8433ca 上判红:@objectstack/rest 记 136,实测 140。原因不是量错了 —— `pull_request` 运行编译的是「分支 merge 进当前 main」的树,而 sweep 之后 main 又落地了三个动 packages/rest 的 PR(#5808 / #5821 / #5806)。合并 main 后重量 得 143,tests 56 -> 58,其余 33 条纹丝不动。 这个竞态是引导期的一次性成本,不是常态:本不变式上了 main 之后,引入错误的那个 PR 自己会红 —— 这正是它的目的。写进 MEASURED 的文档块和 rest 的 note,下一个做 全量重测的人不必再自己发现一遍。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
…de-reference driver-memory in one stub comment (objectstack-ai#5829) (objectstack-ai#5836) objectstack-ai#5704 收口尾单(批次 2/3 已合入 PR objectstack-ai#5797/objectstack-ai#5806),两件留档尾项: 1. `packages/qa/dogfood` 的 `@objectstack/driver-memory` devDep:objectstack-ai#5715(批次 0) 把 read-coercion 一致性测试的 memory 臂移除后,全包已零真实 import (`git grep -in memory -- packages/qa/dogfood` 只剩 README 的 "in-memory SQLite" 散文与 read-coercion-conformance.test.ts:9 记录移除原因的 注释)。摘除声明 + lockfile 随动(仅该一条 importer 链接)。 2. `protocol-batch-atomic.test.ts` 的 `makeSnapshotDriver()` 头注释把这个本地 手写 stub 说成「the same shape driver-memory uses」——packages/objectql 根本 不依赖 driver-memory,这类措辞正是 objectstack-ai#5704 Phase 1 survey 里让 `grep -i memory` 虚高的来源。按 objectstack-ai#5797 同款原则改写为指涉 stub 自身。 `protocol-data.test.ts:759` 经复核**原样保留**:那一句 「Mirror of objectstack-ai#3948's rule for driver-memory: an unapplied filter must not look like a satisfied one」指的是真实 driver-memory 包里真实存在的不变量 (`filter-refusal.ts`、`memory-driver.ts:753/792/817`、 `memory-filter-ast-vocabulary.test.ts`),属于 objectstack-ai#5797 已开的例外类 (描述真实驱动族行为的散文一律保留),不是把 stub 说成 driver-memory。 零行为变更。 Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx Co-authored-by: Claude <noreply@anthropic.com>
Fixes #5785
#5704 批次 3 的真迁半边(维护者 2026-08-06 Q1=C 裁决)。7 个「名为 integration、实跑手写替身」的文件换成真实后端:
@objectstack/driver-sql+ better-sqlite3:memory:,驱动构造用仓库规范形态(examples/app-crm、cli db clean、已合并 PR #5715 的makeDefaultDriver()同款)。前提复核(对 origin/main
afb83d3)git grep -l "makeMemoryDriver" origin/main -- packages/rest packages/plugins/plugin-approvals packages/triggers命中且仅命中 issue 列的 7 个文件,前提成立:packages/rest/src/{export,import,import-job}-integration.test.tspackages/plugins/plugin-approvals/src/{record-lock-multi-update,record-lock-schedule-run,status-mirror-cascade}.integration.test.tspackages/triggers/trigger-record-change/src/record-change-integration.test.ts做法
await engine.syncSchemas();kernel 侧(status-mirror-cascade、record-change-integration)驱动是 bootstrap 之后才注册的,引擎自己的init()早已跑完,所以显式await driver.connect()补上引擎本会做的那一步,再syncSchemas()—— 这正是「对象在 boot 之后才上线」的生产路径。@objectstack/driver-sql(workspace 协议);better-sqlite3 由其 optionalDependency 提供,无循环依赖。:memory:连接的收口(engine.destroy()/driver.disconnect()),避免一个文件里堆几十个活库。endpoints,一个真实生产者从未发过、且已在 #4828 退役的键 #5674 的语境),实测git diff -- packages/rest/src | grep -E "getDiscovery|discovery|endpoints"零命中。逐处语义分叉处置记录
分叉 1 —— 未写过的列:
undefined变 SQL NULL(record-change-integration,#3760 用例)原断言
expect(row?.stamp).toBeUndefined()。Map 里没有「列」这个概念,没人写过的键就是不存在;真表里列由 DDL 声明存在,没写过就是 NULL。改写为expect(row?.stamp ?? null).toBeNull(),并新增一条expect(row).toBeTruthy()(记录本身必须存在 —— 被拒的只是 flow 的写)。处置定性:按 SQL 族语义改写、并收紧。刻意没用toBeFalsy(),那会连空串一起放过;现在断言的仍是「没有值落地,尤其不是 'done'」,保真度不降反升。分叉 2 —— 手写
sys_import_job镜像早已与真对象漂移(import-job-integration)该文件用一个「minimal sys_import_job mirror」当 fixture。换真表后直接见红:
no such column: undo_log,7 个用例全挂在waitForTerminal。查真对象packages/platform-objects/src/audit/sys-import-job.object.ts后确认镜像漏了undo_log与reverted_at(整个 undo 能力,正是 #3549 的主题),另有 3 个 datetime 被写成text。Map 什么键都收,所以镜像可以一路烂下去而测试全绿;真表一句话就说破。处置:按 fixture 三分法的第二档「补声明」,但手工补两列只是把同一个钟重新上一遍,所以退一步换成真身 —— 直接 import
SysImportJob(@objectstack/platform-objects/audit,本就是 packages/rest 的生产依赖,路由本来就按这个名字写这张表)。镜像漂移这一整类问题就此消失。改后 13/13 绿。分叉 3(日志层,非断言层)—— 未声明对象的查询由「静默空集」变「表不存在」
plugin-approvals / trigger 两处 kernel 用例里,ApprovalService 与权限解析会顺带查
sys_approval_delegation/sys_user/sys_member/sys_position等本用例未注册的对象。Map 替身读时即物化集合、静默返回[];sqlite 抛no such table,调用方原有的 catch 照旧当空集处理。判决完全不变,只是日志多了 ERROR 行;不属于断言分叉,如实记录在此。其余 5 处:零分叉。 export-integration(24)、import-integration(28)、record-lock-multi-update(8)、record-lock-schedule-run + status-mirror-cascade(9)一次通过,断言一字未改 —— 包括
filter/orderby/$expand/多值 lookup 与多值 select 的数组往返、boolean 与 date 的存储形态、search与searchFields。被删掉的那些手写 matcher 注释本身就是这次迁移的最好论据(「不认$or/$and会静默返回全部行」「不认$contains会让搜索成为 no-op 而『它过滤了』的断言照样通过」)—— 这些是 fixture matcher 才会有的故障类,生产引擎没有。方向性验证(预判在先)
真表究竟是不是真的在兜底,还是驱动会像 Map 一样悄悄物化?预判:拆掉 DDL 一步应当见红,且红在「表不存在」而不是断言不符。 临时删掉 record-lock-multi-update 的
await engine.syncSchemas()重跑,实测:方向与预判一致,随即恢复。另有一次非人为的同向证据:分叉 2 的
no such column: undo_log是真表主动判出来的,不是我预设的。验证证据(前台阻塞执行,全程
flock串行 +--max-old-space-size=4096)与迁移前基线逐项相同(rest 55/782、plugin-approvals 19/446),没有用例被删或被跳过。
@objectstack/rest无typecheck脚本(在scripts/check-type-check-coverage.mjs的 DEBT + TEST_DEBT 台账里,已计量、只减不增);三个包的 tsconfig 都exclude了**/*.test.ts,本 PR 只动测试文件,台账数字不变,门禁实测通过。申报面与 changeset
变更面 = 7 个测试文件 + 3 个 package.json(仅 devDependencies 各加一行)+ pnpm-lock.yaml(仅 3 条 workspace link,已核)。构建顺带改写的
packages/spec/authorable-surface.base.json已 checkout 还原,不在本 PR 内。测试行为变更、不面向用户 → skip-changeset(label 由 PM 代打)。
Generated by Claude Code