test(config,vitest): harden test isolation and complete vitest migration - #211
Merged
Conversation
Migrate the test suite runner from `bun test` to vitest so each test file
runs in its own module registry — no more cross-file `globalServiceRegistry`
leaks — and land the follow-on hardening the migration exposed:
* `vitest.config.ts` with `.env.test` autoload, `pool: "forks"` for native
module isolation (better-sqlite3, onnxruntime), and a resolveId plugin
that appends `.js` to the workglow package's internal re-exports (its
published ESM omits the extension, which Node's strict loader rejects).
Route the workglow package through Vite's transform pipeline via
`server.deps.inline: [/workglow/]` so the plugin sees the intra-package
specifiers.
* `src/cli/testing/runCliProcess.ts` — spawn wrapper for the CLI-subprocess
tests. Uses `node:child_process` to keep stdout/stderr cleanly separated
(Bun.spawn + Response capture crossed them under vitest, breaking JSON
parsing) and its `cliEnv` helper now passes through `USER`, `LOGNAME`,
`CI`, `NODE_OPTIONS`, `XDG_CONFIG_HOME`, `XDG_CACHE_HOME` alongside the
original set — some CLI paths (and CI matrices) rely on them.
* Replace `Bun.Glob` scans in the daily / quarterly index tests with a
`readdirSync` + regex filter, and swap `Bun.file(FIXTURE).text()` calls
in the DEFM14A test trio for `readFileSync(FIXTURE, "utf-8")` so those
files load under Node without needing Bun's IO helpers.
* Gate Bun-only tests with `describe.skipIf(typeof Bun === "undefined")` —
`BootstrapDownloadTask` (uses `Bun.file` writers and stubs `Bun.spawn` /
`Bun.which`) and `SecFetchJob`'s wire-level + retry tests (use
`Bun.serve` for a loopback listener). Each carries a TODO noting the
migration off Bun-specific APIs that would let the skip drop.
* `EntityTemporalRepo.test.ts` — `ReturnType<typeof spyOn>` → `ReturnType<
typeof vi.spyOn>` to satisfy the vitest 4 typings.
* Add `better-sqlite3` as a devDependency — `@workglow/sqlite/storage` on
Node loads it via dynamic import for `Sqlite.init`; without it every
setupAllDatabases-driven test fails at bootstrap.
The daily/quarterly index tests are also gated to Bun for now (the
JobQueueServer poll loop times out under Node/vitest at `pollIntervalMs: 1`
— a portability issue with the workglow queue server, pre-existing on the
`chore/vitest-runner` head this PR replays). TODOs on the skips document
the migration path.
Baseline before this PR: 218 failing / 47 passing (265 total) on
`chore/vitest-runner` head. After: 263 passing / 3 skipped, 0 failing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXWNrDwDQnd59uksqbPC2E
Tests share one process (per vitest test file, but still the same worker
process for tests within a file), so a token registered by an earlier test
— `SEC_DB_TYPE = "sqlite"`, `SEC_DRY_RUN = true`, `SEC_RAW_DATA_FOLDER =
"/tmp/xyz"` — silently leaked into later tests through the shared
`globalServiceRegistry`. Test files worked around it with bespoke
save/restore blocks or `SEC_DB_TYPE = "memory" as unknown as ...` casts
that only mostly worked ("no unregister API" — actually there IS a
`container.remove(id)`).
Fix it once, in one place:
* `tokens.ts` — add `ENV_DERIVED_TOKENS`, the canonical list of every
token the CLI's env/DI wiring projects into the registry (`EnvToDI.ts`
plus the CLI preAction flags `SEC_DRY_RUN` / `SEC_JSON_OUTPUT`). The
TSDoc pins the invariant: whenever `EnvToDI.ts` gains a new token, it
must be appended here too.
* `TestingDI.ts` — `resetDependencyInjectionsForTesting()` now iterates
`ENV_DERIVED_TOKENS` and calls `globalServiceRegistry.container.remove
(token.id)` before re-registering the in-memory repositories, so each
test file starts with a clean container regardless of what the
previous file bound.
* `TestingDI.test.ts` — asserts every token in `ENV_DERIVED_TOKENS` is
stripped by the reset and that a representative repo token
(`ADDRESS_REPOSITORY_TOKEN`) is still re-registered afterwards. The
second assertion guards against a future refactor that clobbers the
repo re-registration path.
* Delete the `SEC_DB_TYPE = "memory" as unknown as "sqlite" | "postgres"`
casts in `SpacDealReplace.sqlite.test.ts`,
`Form8KEventReplace.sqlite.test.ts`, and
`Form8KEventLegacyMigration.test.ts` — the afterEach now just calls
`resetDependencyInjectionsForTesting()`. Removed the accompanying "no
unregister API" comments.
* `db.test.ts` — drop the save-and-restore block around `SEC_DB_TYPE`;
the reset takes care of it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXWNrDwDQnd59uksqbPC2E
…imedes-o87xem # Conflicts: # CHANGELOG.md # package.json
This was referenced Jul 24, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bundles two related test-hygiene fixes on top of
main(PR #210's vitestmigration is replayed here as the first commit, since the base branch does not
yet contain it — see note below):
chore(test): complete the vitest migration on top ofmain. Addsvitest.config.tswith a resolveId plugin that appends.jsto theworkglowpackage's internal re-exports (its published ESM omits theextension, which Node's strict loader rejects). Routes
workglowthroughVite's transform pipeline via
server.deps.inline. Usespool: "forks"fornative-module isolation (better-sqlite3, onnxruntime). Ports the
runCliProcesshelper for CLI-subprocess tests tonode:child_process(Bun.spawn crossed stdout/stderr under vitest, breaking
--format jsonparsing) and widens its env passthrough to
USER,LOGNAME,CI,NODE_OPTIONS,XDG_CONFIG_HOME,XDG_CACHE_HOME. ReplacesBun.GlobwithreaddirSync + regexin the daily/quarterly index tests, swapsBun.file(FIXTURE).text()forreadFileSync(..., "utf-8")in the DEFM14Atest trio, and gates the remaining Bun-only tests
(
BootstrapDownloadTask'sBun.filewriters,SecFetchJob'sBun.serveloopback tests, the index-fetch tests whose
JobQueueServerpoll loop timesout under Node/vitest) with
describe.skipIf(typeof Bun === "undefined")+a TODO. Fixes
ReturnType<typeof spyOn>→ReturnType<typeof vi.spyOn>inEntityTemporalRepo.test.ts. Addsbetter-sqlite3as a devDependency so@workglow/sqlite/storagecan bootstrap on Node.test(config): strip env-derived DI tokens onTestingDIreset. AddsENV_DERIVED_TOKENSinsrc/config/tokens.ts(the canonical list of everytoken
EnvToDI.tsand the CLI preAction hook project into the container),and has
resetDependencyInjectionsForTesting()iterate it, callingglobalServiceRegistry.container.remove(token.id)before repore-registration. Deletes the
SEC_DB_TYPE = "memory" as unknown as ...casts in the three sqlite test files
(
SpacDealReplace.sqlite.test.ts,Form8KEventReplace.sqlite.test.ts,Form8KEventLegacyMigration.test.ts), simplifies the save-and-restore hooksin
db.test.tsandBootstrapDownloadTask.test.tsto just call the reset,and adds
src/config/TestingDI.test.ts(asserts every env-derived token isstripped, and one repo token is still re-registered). Adds a TSDoc block
above
resetDependencyInjectionsForTesting()pinning the invariant.Base note (PR #210)
mainat HEAD does not yet contain PR #210(
chore/vitest-runner). The first commit here replays the net-effect finalstate of PR #210 (
vitest.config.ts, converted test files,runCliProcesshelper) so both fixes land in one PR — no
chore/vitest-runnermerge requiredfirst.
Test plan
bun x tsc --noEmit— zero type errors.bun x vitest run— 263 test files pass, 3 skipped, 0 failing (before:218 failing / 47 passing on the
chore/vitest-runnerhead thisreplays).
bun x vitest run src/config/TestingDI.test.ts— the new reset testpasses.
(
BootstrapDownloadTask,SecFetchJobretry/wire, index-fetch) —each carries a TODO noting the underlying Bun-API dependence that would
let the skip drop.
Known gaps
FetchDailyIndexTask.test.ts/FetchQuarterlyIndexTask.test.tsare gatedto Bun for now — the
JobQueueServerpoll loop times out under Node/vitestat
pollIntervalMs: 1. That's a portability issue with the workglow queueserver, pre-existing on the
chore/vitest-runnerhead this PR replays; theskip is annotated with a TODO to migrate the test off the live-server
pattern.
Generated by Claude Code