Skip to content

test: run every remaining driver-memory test on the SQL driver - #2104

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-2092-tests-on-sql
Oct 14, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-2092-tests-on-sql

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #2092
Clause-②: no

What this does

The maintainer's ruling A on #2092 (comment 6166370777, verbatim: 「2092 内存测试全部换 sql」), which carries out the #2031 ruling 「就应该在 sql 上测试,作废内存驱动的测试」 (comment 6144864473): every test that ran on @objectstack/driver-memory now runs on the SQL driver. A case whose only subject was the memory driver's stored shape is deleted and named below. The app no longer lists driver-memory among its own dependencies.

Tests only, nothing ships. No src/** change. The changeset has empty frontmatter.

The harness (H2)

Two SQL shapes already existed in test/, and each converted file moves onto whichever shape it already had. No new helper.

  • A booted app or kernel. hotcrmStack() / bootStack default to databaseDriver: 'sqlite-wasm'. The two DefaultDatasourcePlugin({ driver: 'memory' }) kernels move to { driver: 'sqlite-wasm', config: { filename: ':memory:' } }, the spelling case-creation-date.test.ts already uses.
  • A hand-built ObjectQL.create engine. It now gets new SqliteWasmDriver({ filename: ':memory:' }), the driver dashboard-date-range-window, hook-org-inheritance and others already construct inline. ObjectQL.create connects the driver but emits no DDL, so each converted engine runs await ql.syncSchemas() after create. That is the engine's own public method: the same per-object driver.syncSchema(table, obj) loop the runtime's boot runs (ObjectQLPlugin.syncRegisteredSchemas), over the registry's objects, with system fields already applied. It is one line per engine and cannot drift from the registry. The five pre-existing SQL legs that hand-call applySystemFields and initObjects are left untouched.

Driver used: @objectstack/driver-sqlite-wasm (its SQL is @objectstack/driver-sql's), the one every existing SQL suite here uses. undeclared-key-probe keeps its sqlite (driver-sql on better-sqlite3) leg beside it.

Files (before → after)

file before after
activity-seed-coverage engine on InMemoryDriver engine on sqlite-wasm + syncSchemas()
attendee-type-resolution same same
audit-stamp-readonly same same; fixture re-expressed (below)
campaign-member-cascade same same; docstring
case-assignment (2 engines) same same
churn-health-score-block same same
event-attendee-cascade same same; docstring
flow-condition-totality same same; 1 case deleted, end-to-end block re-described
flow-filter-today-token same same; tenant comment ×1 rewritten (memory reason gone)
flow-variable-conditions same same; 1 case re-expressed
forecast-current-quarter-view (2 engines) same same; tenant comment ×2 rewritten
forecast-period-scope same same
knowledge-article-share-links same same
knowledge-deflection loop over memory + sqlite sqlite leg only
lead-duplicate-management engine on InMemoryDriver sqlite-wasm; docstring
line-item-cascade same same
line-item-line-number same same
object-validation-predicates same same; 1 case deleted
opportunity-line-item-add-picker same same
ownership-model same same
quote-contact-required-when (3 engines) memory blocks + one SQL block all SQL; 1 case deleted
quote-discount-ceiling (10 engines) memory blocks + one SQL block all SQL; 1 case deleted, 1 re-expressed
readonly-write-semantics engine on InMemoryDriver sqlite-wasm
sharing-seeding (2 engines) same same; 1 case re-expressed
sla-compliance-gauge loop over memory + sqlite, + 1 memory engine sqlite only
win-loss-capture (5 engines) memory blocks + one SQL block all SQL; 1 case deleted
decorative-field-sweep kernel driver: 'memory' kernel driver: 'sqlite-wasm'
undeclared-key-probe DRIVERS = memory, sqlite, sqlite-wasm sqlite, sqlite-wasm; memory-only case deleted

The two de-looped files (knowledge-deflection, sla-compliance-gauge) are mostly re-indentation; git diff -w shows the real change.

Deleted: their only subject was the memory driver's stored shape (H3)

  • object-validation-predicates, flow-condition-totality, quote-contact-required-when, win-loss-capture: "stores no key for a column it was never given — the precondition". Each asserted 'x' in stored === false, the absent key itself.
  • quote-discount-ceiling › "leaves a quote with no discount at all alone": its business half is already pinned on SQL by "admits a quote with a NULL discount — the column-complete shape". What it added was 'discount' in stored === false.
  • undeclared-key-probe: the memory leg of the half-1 matrix (5 runs), and "memory no longer accepts it — the permissive driver came into line". The premise assertion it also carried ("the line item does not declare tax") is still pinned by "the probe rests on a real asymmetry".
  • knowledge-deflection › "the deflection measures over memory" and sla-compliance-gauge › "the compliance measure over memory": the memory leg of a two-driver loop (6 runs each). The same cases still run over sqlite.

That is 23 fewer test runs (3676 → 3653 passed, 1 skipped both times).

Re-expressed on SQL (H3)

  • audit-stamp-readonly › seedOpportunity: each deal gets its own account. crm_account.name is declared unique: true (src/sales/objects/account.object.ts), SQL enforces it, and the memory driver never did. On SQL, two cases went red with DuplicateRecordError … on 'crm_account' because the fixture reused one name. The app is right to refuse that write. The readonly-strip assertions are unchanged.
  • sharing-seeding › "a filter the driver cannot execute is refused loudly, not answered wrongly": .rejects.toThrow(/\$nor/) became .rejects.toMatchObject({ code: 'INVALID_FILTER', status: 400 }). driver-sql still refuses $nor loudly, but it keeps the key out of the message (server log only, because the predicate is not marked author-written). The regex was pinning the memory driver's wording.
  • quote-discount-ceiling › line case "… and it is NOT the sparse shape" → "… the field default is 0": the 'discount' in stored assertion is dropped. The default 0 is kept.
  • flow-variable-conditions › "findOne answers a miss with null — the precondition for class 1": keeps the findOne miss → null half (true on SQL, and what the guard needs). It drops the sparse-column half, 'status' in stored, which passes regardless on a column-complete driver.
  • undeclared-key-probe › "so the three drivers DO agree" → "so the SQL drivers DO agree".
  • Renamed describe blocks: four "(in-memory driver)" suffixes dropped; "the rule fires on a driver whose stored record omits the key" → "the rule fires on the real engine"; "conditions answer on a driver whose stored record omits the key" → "conditions answer end-to-end on the real engine".

No case went red because the app is wrong on SQL.

Measured while converting

  • The operator matrix in sharing-seeding was measured on driver-memory 17.1.0. Re-measured on sqlite-wasm 18.0.0-next.0, every one of the 9 rows answers the same, including the null-sensitive ones (!= "customer" and !(… == "customer") both return the null-typed row). All 10 rule witnesses pass on SQL. The docstring says so.
  • The end-to-end block in flow-condition-totality had already stopped reaching the absent-key shape on 18.0.0-next.0, on both drivers (measured for one write; it matches chore(deps): upgrade the ObjectStack platform to 18.0.0-next.0 #2091's own note that on 18 the trigger's record is materialised over every declared field). I put case_escalation_on_create's start condition back to its unguarded form and wrote a born-critical case through the real RecordChangeTrigger: no condition abort on memory or on sqlite-wasm. Control: the same flow handed a record without escalated_date directly aborts with No such key: escalated_date on both. So those cases pin "each flow runs end to end on the driver an install uses". The absent-key class is held by the evaluator sweeps ("answers on a record with no keys at all", "any single referenced key is the one that is missing"), which no driver change can reach. The docstring states this.
  • The syncSchemas() idiom (probe): an insert returns the full column-complete row. A second engine over the same driver (the "re-open the SAME store" legacy-row fixtures in quote-discount-ceiling / quote-contact-required-when) sees the stored row and adds its new column. A findOne miss answers null.

Dependency (H4)

Nothing imports @objectstack/driver-memory any more, and no kernel boots driver: 'memory' (git grep: 0 imports, 0 boots). The entry is gone from package.json (it was in dependencies, not devDependencies), and pnpm install regenerated pnpm-lock.yaml. The only lockfile change is the importer entry (3 lines). @objectstack/cli and @objectstack/runtime depend on driver-memory themselves, so it stays installed for them.

Suite time (H5)

Full pnpm test, each under the shared verify lock (held the lock is the reading; a shared box, not an idle one):

tree lock held vitest Duration result
before, 64003979 (memory) 482s 478.89s 179 files, 3676 passed / 1 skipped
after, 2987e6c2 (SQL) 529s 528.09s 179 files, 3653 passed / 1 skipped

The whole-suite delta (+10%) is not the conversion. The same 28 files, run back to back, alternating, on the base tree and on this branch:

run base (memory) branch (SQL)
1 64.12s (785 tests) 60.24s (762 tests)
2 58.17s 58.58s

On these files SQL costs nothing measurable (mean 61.1s vs 59.4s). The full-suite readings differ by shared-box contention (import time rose 512s → 559s across all 179 files, most of which this PR does not touch), so H5's "the suite may grow" is not borne out.

Gates (on 2b74e9f3, the final commit; each through the verify lock, exit code read before any pipe)

  • pnpm validate: ✓ Validation passed
  • pnpm typecheck: tsc --noEmit, exit 0, no diagnostics. tsconfig.json includes test/**/*; --listFiles lists all 28 converted files.
  • pnpm lint: 18 suggestion(s), exit 0 (all metadata suggestions; this diff touches no metadata)
  • pnpm lint:i18n-gate: ✓ i18n lint gate: 0 i18n/missing-* issues
  • pnpm hygiene: ✓ source hygiene clean
  • pnpm hygiene:tokens: ✓ source token ratchet clean
  • pnpm build: ✓ Build complete
  • pnpm test:coverage (what CI runs; also the full suite on the final sha): Test Files 179 passed (179), Tests 3653 passed | 1 skipped (3654), coverage All files | 95.18 | 86.74 | 96.61 | 99.22 (stmts / branch / funcs / lines) against the floors 92 / 78 / 92 / 95. The coverage delta against main was not measured (it needs a second 10-minute run). Coverage is scoped to src/*/objects/*.hook.ts. Of the 23 deleted runs, the only ones with app hooks bound are undeclared-key-probe's memory leg, whose two SQL legs boot the same kernel with the same hooks.
  • Playwright e2e is CI's.

Overlap

PR #2102 (#2016, draft) edits 2 lines of test/flow-variable-conditions.test.ts: the boot([...]) calls of two lead_conversion cases (lines 1137 and 1182), to register a callee flow. This PR's hunks in that file end near line 1000, so the two merge textually, and after the merge those cases boot on SQL through the boot() changed here. Whichever lands second merges main, and its CI runs them on SQL. Dependabot #2063 / #2064 touch package.json / pnpm-lock.yaml; the later one regenerates the lockfile.

Acceptance notes

  • lead-duplicate-management said "the engine test above is the proof" the has() guards work. That proof leaned on the memory driver's absent keys. The pointer now names the no-keys sweep in object-validation-predicates.test.ts.
  • src/** comments that say "measured end-to-end on driver-memory" (for example src/service/flows/case-escalation.flow.ts) are dated history and are left alone (no src/** change here).

Generated by Claude Code

By the maintainer's ruling on #2092 (A), every test that ran on
@objectstack/driver-memory moves to the SQL driver. These 19 files move
with no case deleted or re-expressed:

- each hand-built ObjectQL engine takes a driver-sqlite-wasm ':memory:'
  database and runs the engine's own syncSchemas() after create, the
  per-object syncSchema loop the runtime's boot runs (ObjectQL.create
  connects the datasource but emits no DDL);
- decorative-field-sweep boots its kernel on DefaultDatasourcePlugin
  'sqlite-wasm', the spelling case-creation-date already uses;
- knowledge-deflection and sla-compliance-gauge drop the memory leg of
  their two-driver loop and keep the SQL leg they already had;
- comments that justified the memory driver now describe what runs.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
…that shape

The rest of the #2092 conversion, where a case had to be judged rather
than moved.

Deleted, because their only subject was driver-memory's stored shape:
- the four "stores no key for a column it was never given" preconditions
  (object-validation-predicates, flow-condition-totality,
  quote-contact-required-when, win-loss-capture);
- quote-discount-ceiling "leaves a quote with no discount at all alone",
  whose business half the SQL block already pins ("admits a quote with a
  NULL discount");
- undeclared-key-probe's memory leg and its "memory no longer accepts it"
  case.

Re-expressed on SQL:
- sharing-seeding: the $nor refusal is asserted by its envelope
  (INVALID_FILTER / 400); driver-sql withholds the key from the message;
- audit-stamp-readonly: each seeded deal gets its own account, because
  crm_account.name is unique and SQL enforces it;
- quote-discount-ceiling: the line-default case pins the default 0, not
  the stored-key shape;
- flow-variable-conditions: the class-1 precondition keeps its findOne
  null half and drops the sparse-column half, vacuous on SQL;
- undeclared-key-probe: the convergence case is over the two SQL drivers.

The absent-key shape stays covered by the evaluator sweeps (a record with
no keys at all), which no driver change can reach.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
Nothing in the repo imports it any more, and no kernel boots
driver: 'memory'. @objectstack/cli and @objectstack/runtime still depend
on it themselves, so it stays installed for them. The lockfile was
regenerated by pnpm install: the only change is the importer entry.

Changeset with empty frontmatter: tests only, nothing ships.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
The note now says what was measured (one born-critical write, both
drivers, with a direct-record control) and the cause the 18.0.0-next.0
upgrade recorded, instead of generalising from the one write.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
@vercel

vercel Bot commented Oct 14, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Oct 14, 2026 11:25am UTC

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline dependencies Dependency bumps and lockfile changes

Projects

None yet

2 participants