Skip to content

test(rest,plugin-approvals,trigger-record-change): 7 个 integration 测试真迁 driver-sql sqlite :memory:(#5704 批次 3) - #5806

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5785-integration-tests-sqlite
Aug 6, 2026
Merged

test(rest,plugin-approvals,trigger-record-change): 7 个 integration 测试真迁 driver-sql sqlite :memory:(#5704 批次 3)#5806
os-zhuang merged 2 commits into
mainfrom
claude/issue-5785-integration-tests-sqlite

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5785

#5704 批次 3 的真迁半边(维护者 2026-08-06 Q1=C 裁决)。7 个「名为 integration、实跑手写替身」的文件换成真实后端:@objectstack/driver-sql + better-sqlite3 :memory:,驱动构造用仓库规范形态(examples/app-crm、cli db clean、已合并 PR #5715makeDefaultDriver() 同款)。

前提复核(对 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.ts
  • packages/plugins/plugin-approvals/src/{record-lock-multi-update,record-lock-schedule-run,status-mirror-cascade}.integration.test.ts
  • packages/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 之后才上线」的生产路径。
  • 3 个包加 devDep @objectstack/driver-sql(workspace 协议);better-sqlite3 由其 optionalDependency 提供,无循环依赖。
  • 每个文件加 :memory: 连接的收口(engine.destroy() / driver.disconnect()),避免一个文件里堆几十个活库。
  • ⛔ 纪律遵守:diff 未触碰任何 getDiscovery / discovery endpoints 替身(留给 packages/rest 的 14 个 getDiscovery 测试替身返回 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_logreverted_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 的存储形态、searchsearchFields。被删掉的那些手写 matcher 注释本身就是这次迁移的最好论据(「不认 $or/$and 会静默返回全部行」「不认 $contains 会让搜索成为 no-op 而『它过滤了』的断言照样通过」)—— 这些是 fixture matcher 才会有的故障类,生产引擎没有。

方向性验证(预判在先)

真表究竟是不是真的在兜底,还是驱动会像 Map 一样悄悄物化?预判:拆掉 DDL 一步应当见红,且红在「表不存在」而不是断言不符。 临时删掉 record-lock-multi-update 的 await engine.syncSchemas() 重跑,实测:

ERROR Insert operation failed {"object":"opportunity","error":{"message":
  "insert into `opportunity` (`amount`, `id`, `name`) values (100, ...) returning * - no such table: opportunity"}}
× the issue verbatim: the by-id write is refused, and so are both predicate rewrites
× refuses an unscoped whole-table update   ... (全红)

方向与预判一致,随即恢复。另有一次非人为的同向证据:分叉 2 的 no such column: undo_log 是真表主动判出来的,不是我预设的。

验证证据(前台阻塞执行,全程 flock 串行 + --max-old-space-size=4096)

pnpm --workspace-concurrency=2 --filter @objectstack/rest \
  --filter @objectstack/plugin-approvals --filter @objectstack/trigger-record-change test
→ TEST_EXIT=0
   trigger-record-change  Test Files  5 passed (5)   Tests   55 passed (55)
   rest                   Test Files 55 passed (55)  Tests  782 passed (782)
   plugin-approvals       Test Files 19 passed (19)  Tests  446 passed (446)

与迁移前基线逐项相同(rest 55/782、plugin-approvals 19/446),没有用例被删或被跳过。

pnpm --filter @objectstack/plugin-approvals --filter @objectstack/trigger-record-change typecheck  → Done ×2
pnpm check:type-check-coverage → OK (63/78 typechecked, ledger 未动)
pnpm check:published-files / check:prerelease-pins / check:driver-conformance → OK
node scripts/check-nul-bytes.mjs → OK;7 个文件另做 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 自扫,零命中

@objectstack/resttypecheck 脚本(在 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

…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
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 6:11am

Request Review

@github-actions github-actions Bot added the size/l label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/plugin-approvals, @objectstack/rest, @objectstack/trigger-record-change.

14 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • content/docs/automation/approvals.mdx (via @objectstack/plugin-approvals)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-approvals)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-approvals, @objectstack/rest)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-approvals, @objectstack/rest, @objectstack/trigger-record-change)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • content/docs/releases/v17.mdx (via @objectstack/rest)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-approvals)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file tests labels Aug 6, 2026
@os-zhuang os-zhuang added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/l dependencies Pull requests that update a dependency file tests labels Aug 6, 2026 — with Claude
@github-actions github-actions Bot added size/l dependencies Pull requests that update a dependency file tests labels Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

合并后复验补记(AGENTS.md §10 的「重叠面重跑」)。

PR body 里的数字取自分支基线 afb83d3。开 PR 后 main 又前进,其中两笔与本 PR 落点重叠,所以合了 origin/main(28ac111)重跑一轮而不是只看 git 干净合并:

合并后实测(flock 串行 + --max-old-space-size=4096,前台阻塞):

trigger-record-change  Test Files  5 passed (5)   Tests   55 passed (55)
rest                   Test Files 56 passed (56)  Tests  784 passed (784)
plugin-approvals       Test Files 19 passed (19)  Tests  446 passed (446)
typecheck(plugin-approvals / trigger-record-change) → Done ×2

rest 由 55/782 变 56/784,增量全部来自合并带进来的 #5788 新增文件 discovery-double-retired-key.test.ts,不是本 PR 产生的用例。两条车道的文件面零相交这一点,至此是实测而非清单比对。

变更面复核:PR 实际 changed_files = 11,与申报面一字不差(7 测试文件 + 3 个 package.json + pnpm-lock.yaml);构建顺带改写的 packages/spec/authorable-surface.base.json 每次都已还原,未入库。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 06:50
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 2a607d2 Aug 6, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5785-integration-tests-sqlite branch August 6, 2026 07:02
baozhoutao pushed a commit that referenced this pull request Aug 6, 2026
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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[#5704 批次3] 7 个 integration 测试真迁 driver-sql sqlite :memory:(rest ×3 / plugin-approvals ×3 / trigger-record-change ×1)

2 participants