Testing
September 10, 2026 · View on GitHub
Runner: node:test (Node 22 built-in). Property-based tests: fast-check 4. No root test script — each package owns its lane. Every package builds first, then tests the compiled output (dist/ or lib/). ESLint ignores **/test/ and **/scripts/; test files are type-checked by each package's tsc -p tsconfig.test.json inside the test lane.
Commands
| Package | Command | Covers |
|---|---|---|
| uniterra-desktop | pnpm --filter @uniterra-solutions/uniterra-desktop test | profile-bootstrap unit tests + built-ins/readiness PBT |
| uniterra-provider | pnpm --filter @uniterra-solutions/uniterra-provider test | composition + dual-protocol translate + reasoning-preservation regressions + seeded properties |
| uniterra-cli | pnpm --filter @uniterra-solutions/uniterra test | CLI unit tests + install-logic PBT |
| uniterra-updater | pnpm --filter @uniterra-solutions/uniterra-updater test | decision semantics (fast-check) |
| uniterra-skills | pnpm --filter @uniterra-solutions/uniterra-skills test | provisioning tests (every skill ships SKILL.md; idempotent) |
| uniterra-systemprompt | pnpm --filter @uniterra-solutions/uniterra-systemprompt test | rule injection: registration, append, no cross-turn duplication |
| container | scripts/verify-cli-container/run.sh | clean-container installer replay + provisioning PBT + real dsh boot |
| windows-install | scripts/verify-windows-install/verify.ps1 | real uniterra setup --source --move-source on windows-latest: package → move-embed (the closer-to-real user flow, no full-tree robocopy) → install → shortcut → Uniterra.exe boot smoke (runs in the release gate, not locally on macOS) |
Static gates: pnpm run typecheck (tsc -b --noEmit), pnpm run lint (eslint ., max-warnings 0), pnpm format:check. Pre-commit hook runs lint-staged with --max-warnings 0.
PBT Invariants (the safety net)
| Suite | Invariants |
|---|---|
packages/uniterra-desktop/test/builtin-pbt.test.mjs | hasAllBuiltins true iff every expected bundle present (order/extras irrelevant, malformed → false); vendored/workspace staleness by version + shared-file content identity (drifted bytes under the SAME version still re-provision); readiness URL parse across chunk-boundary splits (port-completeness regex); dsh plugin add + boot to a reachable URL |
packages/uniterra-cli/test/pbt.test.mts | parseArgs semantics (--no-open/--dry-run commute, --source consumes the next token, --version wins, first positional = command); installPlan stage order (update = CLI refresh → rebuild → relaunch, setup never touches the CLI, dry-run runs nothing, relaunch iff open); .app discovery over all mac-* shapes; win-unpacked discovery; install destinations / builder args / launch targets / shortcut script quoting per platform; remapJunctionTarget (staging prefix → embedded prefix with suffix intact, non-staging → undefined, no-suffix → undefined, \\?\/forward-slash/case normalization) |
packages/uniterra-provider/test/reasoning-preservation.test.mjs | No loss / no duplication of reasoning, text, and tool calls across every gateway wire shape — per-shape regressions + seeded randomized interleavings (300 runs); thinking-mode reasoning passback — EVERY tool-call turn carries the marker (Chat reasoning_content: "" / Responses reasoning item, the placeholder when no chain of thought exists yet) because the gateway cannot recognize a replayed call id it did not mint, plus upstream call-id adoption by the Responses translator (call_id, item id only a fallback) — locked by deterministic agent-loop cases (translate → serialize round trip included) + seeded serialize/translate properties |
packages/uniterra-provider/test/smoke.test.mjs | Registration faces, credentials seam, settings write-point validation, dual-protocol parity, models.dev matching, RPC channel, per-model protocol pinning |
packages/uniterra-updater/test/decision.test.mts | Semver comparison incl. prereleases + unparseable→equal; update verdict merge; skip-prompt semantics; update-action mapping (Update Now → uniterra update, Skip → persisted version, otherwise none) |
packages/uniterra-desktop/test/dsh-shortcuts-*.test.mjs | Every dsh seam the plugin uses exists in the PINNED family (vendor/dsh-harness): plugin-side surface table == recorded calls per feature; dsh-api oracle fails by name when a service/method/projection-key/slot/loading-protocol fact drifts; behavior: combo matching + shifted-punctuation normalization, self-match vs single-bit-change PBT, Tab-hold effort combos, permission cycle never targets custom and always lands on a provided preset (loopback route, no slash command), settings persistence/migration, recording UI, theme toggle keeps a registered skin active, new-session via sessions.create, copy-last-message via binding.eventSource; real dsh web boot smoke (scripts/verify-dsh-shortcuts-smoke/run.sh) |
scripts/verify-cli-container/pbt/provisioning-pbt.test.mjs | Every workspace/vendored built-in ships the entry file its manifest points at; BUNDLES_SET; staleness detection; ensureBuiltinPlugins + real dsh --profile web boot to a 2xx readiness URL (300 s cap) |
Test Conventions
- PBT-first: business logic is pinned as properties before fixes (
uniterra-pbt-debugging) and before implementation (uniterra-implementfailing-PBT red phase). - Pure logic is extracted into packages without heavy imports (e.g.
uniterra-updaterhas no Electron/fs imports) so semantics are unit-testable. - Deterministic randomness: seeded 32-bit PRNG for provider seeded properties.
- Vendored community plugins are pinned against the vendored dsh family (
vendor/dsh-harness) by a local white-box suite per plugin: the oracle reads the harness SOURCES, so an upstream rename/removal fails by name, and a customized plugin's patch must keep the suite green (seedocs/modules/vendor-plugins.md). - The container harness is hermetic — pristine source archive, no
.git, nonode_modules,CI=trueinstall; no macOS runner required. The Windows harness verifies the real installer on windows-latest; both gate a release.
Coverage
No coverage tooling configured. Invariant coverage is expressed through the PBT lanes above.
How to Update
- New test lane / command → add a row to the Commands table.
- New invariant suite → add a row to the PBT Invariants table.
- Testing convention changes → update the Test Conventions section and
AGENTS.md.
Find It Fast
find packages scripts -name '*.test.*' -not -path '*/node_modules/*' # every test file