17 KiB
Testing Suite Quality PRD
Created: 2026-05-05
Summary
Latest speed-hardening measurements and contributor guardrails:
docs/test-speed-audit-FN-5048.mdandAGENTS.md→ "Standing Rule: Do Not Add Slow Tests (FN-5048)".
Fusion has a large, useful, but uneven test suite. The suite is strongest where it exercises real SQLite state, git operations, published CLI bundle contracts, route auth/lease semantics, runtime orchestration, and user-visible mobile/native flows. It is weakest where it accumulates high-volume mechanical assertions: field-presence checks, CSS source-string checks, parser/regex matrices, type-shape runtime tests, class-name layout checks, and large mock-heavy component shells.
This PRD defines the work needed to make the suite smaller, more trustworthy, and easier to run correctly. The goal is not to chase a smaller test count for its own sake. The goal is to preserve high-signal regression coverage while cutting tests that lock implementation details or duplicate coverage already provided by better tests.
Current State
Static inventory from this worktree:
| Area | Source files | Test files | Test cases | Assertions | Test LOC |
|---|---|---|---|---|---|
packages/dashboard |
377 | 420 | 10,608 | 26,490 | 244,069 |
packages/engine |
89 | 105 | 3,191 | 7,250 | 83,201 |
packages/core |
88 | 96 | 3,470 | 8,474 | 57,270 |
packages/cli |
57 | 59 | 1,031 | 2,427 | 24,510 |
| Other packages/plugins/scripts | 104 | 92 | 1,004 | 2,046 | 18,123 |
The suite has good infrastructure foundations:
pnpm testrunsscripts/test-changed.mjs, selecting changed packages and using a cache.pnpm test:fullruns workspace tests with capped worker fanout.pnpm test:lockedexists for cross-worktree contention.pnpm test:isolatedchecks temp-directory cleanup.- Core/dashboard/engine/CLI share test isolation helpers and worker budgeting in most places.
- Dashboard already separates Node server tests from jsdom UI tests.
Key gaps:
- Local changed-test selection does not include reverse dependents.
- The changed-test cache can miss dirty working-tree edits.
- PR CI shards only a hard-coded subset of packages and omits plugin tests and
@fusion/pi-llama-cpp. - Coverage is available but not used as a meaningful gate for critical code.
- Several configs do not use shared worker budgeting or isolation.
- Skip inventory is stale.
- The largest files are now hard to reason about and invite low-value additions.
Goals
- Keep high-signal tests that guard user-visible behavior, data durability, release artifacts, and concurrency safety.
- Cut or consolidate low-signal tests that only assert implementation shape.
- Add missing tests around release-critical and runtime-critical gaps.
- Make local and CI test commands honest about what they cover.
- Establish governance so future tests improve signal rather than re-growing mechanical coverage.
Non-Goals
- Do not rewrite the test framework.
- Do not remove high-value regression tests just because they are long.
- Do not require browser E2E coverage for every UI path.
- Do not make full coverage thresholds block all packages immediately.
- Do not move desktop/mobile into the default root build unless a separate decision is made for platform cost.
Quality Bar
A test is high-value when it catches a regression a user, release manager, or agent operator would notice:
- state persisted incorrectly,
- task/agent workflow moved to the wrong state,
- user WIP was staged or merged incorrectly,
- published CLI bundle cannot run after npm install,
- auth/lease/security behavior changed,
- mobile/native or desktop flow breaks,
- dashboard feature becomes unreachable,
- process/runtime cleanup fails.
A test is low-value when it mostly asserts:
- one field exists after another test already round-trips the payload,
- a TypeScript type shape at runtime,
- exact CSS declaration text,
- a class name unrelated to behavior,
- every regex/parser permutation with no new behavioral boundary,
- mocked shell wiring already covered by command-specific tests.
What To Keep
Keep and protect these categories.
Core
- SQLite initialization, WAL, transaction/savepoint, migration, FTS5, and verification-cache tests in
packages/core/src/__tests__/db.test.ts. - Store tests that cover write-lock serialization, SQLite-first reads when blobs are corrupt/missing, dependency/status transitions, archive/unarchive recovery fields, and FN-ticket regressions in
packages/core/src/__tests__/store.test.ts. - Agent checkout leasing, conflict, claim, runtime state, and budget tests in
packages/core/src/__tests__/agent-store.test.ts. - Environment-sensitive FTS5/SQLite guards such as
packages/core/src/__tests__/fts5-guard.test.ts.
Engine
- Executor tests covering worktree recovery, stale refs, pause/resume, model hot-swap, workflow rerun, verification gates, and FN-ticket regressions.
- Restart/recovery integration tests in
packages/engine/src/__tests__/restart.integration.test.ts. - Real-git merger staging allowlist tests in
packages/engine/src/__tests__/merger-staging-allowlist.test.ts. - Heartbeat scheduler/monitor tests for pause, budget, timer triggers, checkout conflicts, and concurrency.
- Runtime adapter tests that exercise real process lifecycle or IPC semantics.
Dashboard
- Published-bundle regression guard for static
@fusion/engineimports inpackages/dashboard/src/__tests__/engine-import-regression.test.ts. - Auth and daemon-token integration tests using real
createServer. - Remote node sync route contracts, including API key authorization.
- Checkout lease route tests returning user-visible
409 Conflict. - Real-ish store/SQLite route tests for insights, tasks, file diffs, and project scoping.
- Hook tests for real race conditions such as stale backend hydration overriding local user choice.
- Product-level mobile feature reachability tests.
CLI, Desktop, Mobile, Plugins
- CLI bundle-output tests that verify no bare
@fusion/*imports and validate staged assets. - Binary smoke tests for isolated no-
package.jsonexecution,--help, dashboard startup on port0, and PTY session creation. - CLI package dependency guards that catch clean-install failures hidden by pnpm hoisting.
- Desktop IPC/bootstrap tests for first-run local/remote flows and shell channels.
- Mobile native behavior tests for deep links, share, push notifications, QR/native-shell boundaries.
- Pi adapter protocol tests for stream lifecycle, spawned flags, internal tool filtering, and MCP config.
- Runtime plugin tests that assert subprocess/output semantics.
What To Cut Or Consolidate
P0 Cut Candidates
These should be pruned first because they consume high maintenance time with low regression value.
-
CSS source-string tests that assert exact declarations.
- Examples:
core-modals-mobile.test.tsx,board-mobile.test.tsx,mobile-css.test.tsx,TodoView.mobile-css.test.ts. - Keep a few token/breakpoint guardrails and replace the rest with browser-level layout smoke tests.
- Examples:
-
Runtime TypeScript shape tests.
- Example:
packages/plugin-sdk/src/__tests__/index.test.ts. - Keep
definePluginidentity and real manifest validation behavior. Move type coverage to typecheck or compile-time examples.
- Example:
-
Redundant API wrapper URL/header tests.
- Examples: broad wrapper suites in
packages/dashboard/app/__tests__/api-settings.test.tsandapi-tasks.test.ts. - Keep auth/header/error parsing and streaming edge cases. Consolidate to one happy-path per wrapper family plus route contract tests.
- Examples: broad wrapper suites in
-
Mocked command-router smoke coverage.
- Example:
packages/cli/src/__tests__/bin.test.ts. - Keep a small representative routing table. Let command-specific tests own behavior.
- Example:
P1 Consolidation Candidates
-
Settings field/default assertions across core.
- Consolidate overlap between settings parity, global settings, and store settings blocks.
- Keep precedence, migration, and round-trip behavior.
-
Regex/parser/error detector matrices.
- Examples: context-limit, transient-error, reconcile-step regex, stream parser.
- Keep provider examples, boundaries, and precedence. Convert linear permutations to compact
it.each.
-
Plugin example manifest/schema tests.
- Keep one manifest smoke and real hook/tool behavior per example.
- Drop repeated static schema assertions already covered by SDK validation.
-
Duplicate desktop integration smoke.
- Fold the smaller
main.integration.test.tschecks into the richermain-integration.test.ts.
- Fold the smaller
-
Huge component suite structural assertions.
- Split and prune
TaskDetailModal.test.tsx,App.test.tsx,GitManagerModal.test.tsx,ModelOnboardingModal.test.tsx,AgentDetailView.test.tsx. - Keep behavior paths; cut class-name/layout internals unless they guard a documented regression.
- Split and prune
What To Add
P0 Additions
-
Reverse-dependent changed-test selection.
- If
@fusion/corechanges, run core plus engine, dashboard, CLI, plugin-sdk, and packages importing it. - If dashboard or engine changes, run CLI because CLI aliases those source entries for bundle tests.
- If
-
Dirty-worktree cache safety.
- Disable the changed-test cache when affected files are dirty, or include working-tree content in package hashes.
pnpm testmust not report cached success for files that have never been tested.
-
CI package coverage parity.
- Generate CI shard package lists from workspace packages with
testscripts. - Include plugin packages and
@fusion/pi-llama-cpp, or run a dedicated plugin test lane.
- Generate CI shard package lists from workspace packages with
-
Critical release/tarball contract test.
- Add an
npm pack --dry-runor equivalent assertion for@runfusion/fusion. - Required contents:
dist/bin.js,dist/client/**,dist/pi-claude-cli/**,dist/droid-cli/**,dist/plugins/**, andskill/**. - Excluded contents: standalone binary outputs and unnecessary runtime build artifacts.
- Add an
-
Browser-level dashboard smoke lane.
- Cover mobile nav, header overflow, one modal, board/list switch, and footer/input behavior.
- This should use a real browser because jsdom cannot prove layout overflow or fixed-position behavior.
P1 Additions
-
Critical coverage lane.
- Add
coverage:criticalfor targeted files fromdocs/test-audit-report.md, not the entire recursive workspace. - Initial targets: engine runtime/verification utilities, CLI project resolver/settings import-export, dashboard mission/import/generation routes/components, core
gh-cli/summary utilities.
- Add
-
Engine runtime/process tests.
- Add direct tests for
verification-utils.ts, especially process-group timeout, abort, and buffer overflow behavior. - Add direct tests for
custom-providers.tsmalformed/missing settings and valid arrays. - Add a small adapter test for
task-completion.tsdependency lookup failures. - Add SQLite adapter constructor selection coverage if release binaries depend on it.
- Add direct tests for
-
Dashboard route-store integration contracts.
- Add real
TaskStore/SQLite route-store passes for tasks, settings, nodes, and project scoping. - Reduce fake-store route tests where equivalent real-store tests exist.
- Add real
-
Frontend/backend API contract tests.
- For important wrappers, call exported frontend API functions against an in-process route server.
- This replaces separate fetch-string and route-mock assertions.
-
Accessibility smoke.
- Add role/focus/escape coverage for major modals and command surfaces.
-
Pi/plugin integration repairs.
- Replace stale gated CLI pi extension integration with a smaller current integration test against the built extension entry.
- Add a bundled plugin loader integration test that verifies dashboard view/runtime metadata registration.
- Add
pi-llama-cpptests for unreachable/malformed server responses and model listing.
-
Desktop/mobile build smoke lanes.
- Add non-default CI lanes for desktop build and mobile sync/build smoke.
- Keep them outside fast PR checks unless changed-file detection selects those packages.
Run Model
Local Development
Use the narrowest command that honestly covers the changed surface.
| Situation | Command |
|---|---|
| Package-local change | pnpm --filter <package> test |
| Dirty worktree using changed-test path | pnpm test --no-cache |
| Shared core/engine/dashboard/CLI change | pnpm test:full |
| Multiple worktrees or resource contention | pnpm test:locked |
| Isolation audit | pnpm test:isolated |
| Dashboard bundle/client-dist change | pnpm --filter @fusion/dashboard test:build |
| Published CLI/release-sensitive change | pnpm --filter @runfusion/fusion test && pnpm --filter @runfusion/fusion test:pre-release && pnpm build |
| Extension behavior changed | pnpm --filter @runfusion/fusion test:extension-integration after repairing stale integration |
| Desktop change | pnpm --filter @fusion/desktop test typecheck build |
| Mobile change | pnpm --filter @fusion/mobile test typecheck plus relevant Capacitor sync/build lane |
PR CI
Target state:
- Install and build once per shard where needed.
- Run lint and typecheck.
- Run deterministic test shards generated from all workspace packages with
testscripts. - Split dashboard into separate lanes:
- dashboard app/jsdom,
- dashboard server/routes,
- dashboard slow process/WebSocket/browser smoke.
- Run plugin package tests.
- Run
@fusion/pi-llama-cpptests. - Run
coverage:criticalonly on critical target files.
Release CI
Target state:
- Run
pnpm verify:workspace. - Run CLI pre-release tests and binary build smoke.
- Run tarball contract test.
- Run plugin loader integration.
- Use the repository release script for real releases:
pnpm release --yes.
Governance Rules
- New tests should cover observable behavior, not implementation shape.
- New CSS tests should prefer rendered/computed behavior over CSS source text.
- New parser/regex coverage should be table-driven and limited to representative boundaries.
- New high-volume tests must name the regression or user-visible contract they protect.
- Tests tied to FN-ticket regressions are kept unless a better test explicitly replaces them.
- Every package with a
testscript must either use shared worker budgeting and isolation or be explicitly allowlisted with a reason. - Skip markers must be documented in
docs/skipped-test-inventory.mdwith an environment gate or replacement coverage owner. - Large suites should not grow further; new behavior areas get focused files under
__tests__/.
Success Metrics
Within one cleanup cycle:
- Reduce dashboard test LOC by at least 15% without removing listed high-value categories.
- Reduce top five largest test files by moving or pruning at least 20% of their structural assertions.
- CI shards include every workspace package with a
testscript. pnpm testis dirty-worktree safe.docs/skipped-test-inventory.mdmatches current skip/gate usage.coverage:criticalexists with thresholds for selected critical files.- Browser smoke lane covers the primary dashboard layout risks.
Longer term:
- Keep total test LOC growth below source LOC growth.
- Keep new skipped tests at zero unless they are environment-gated and documented.
- Track flake rate for process/WebSocket/browser lanes separately from deterministic unit lanes.
Implementation Plan
Phase 1: Make Test Runs Honest
- Fix changed-test reverse dependency selection.
- Make changed-test cache dirty-worktree safe.
- Generate CI test shards from workspace package metadata.
- Add plugin and
pi-llama-cpptests to PR coverage. - Apply shared worker budgeting/isolation to
droid-cli,pi-claude-cli, andpi-llama-cpp. - Refresh skipped-test inventory.
Phase 2: Add Missing High-Value Coverage
- Add CLI tarball contract test.
- Add critical engine runtime/process tests.
- Add dashboard browser smoke lane.
- Add route-store integration contracts for tasks/settings/nodes/project scoping.
- Repair CLI pi extension integration.
- Add bundled plugin loader integration.
Phase 3: Prune Low-Value Coverage
- Cut CSS source-string assertions and replace with browser/computed smoke.
- Consolidate dashboard API wrapper tests.
- Consolidate core settings field/default checks.
- Consolidate parser/regex matrices.
- Trim runtime type-shape tests from plugin SDK.
- Merge duplicate desktop integration smoke.
Phase 4: Enforce Governance
- Add governance test for test package config consistency.
- Add
coverage:critical. - Document run lanes in contributor docs.
- Require PRs adding broad test files to identify the behavior contract they protect.
Risks
- Pruning can accidentally remove real regression coverage if done mechanically. Mitigation: only cut tests after mapping each to a better owner or identifying it as implementation-shape coverage.
- Browser smoke adds CI cost. Mitigation: keep the lane tiny and targeted.
- Reverse-dependent changed-test selection will run more tests locally. Mitigation: make the behavior explicit and preserve
--filterescape hatches for expert use. - Coverage thresholds can create churn if applied globally. Mitigation: start with critical files only.
Open Questions
- Should dashboard browser smoke use Playwright in CI, or the existing in-app/browser tooling only for local verification?
- Should process/WebSocket tests be tagged in filenames or moved into separate
__tests__/slow/directories? - What minimum critical coverage thresholds should apply initially per package?
- Should plugin examples be treated as product tests, documentation tests, or both?