# Testing Suite Quality PRD _Created: 2026-05-05_ ## Summary > Latest speed-hardening measurements and contributor guardrails: `docs/test-speed-audit-FN-5048.md` and `AGENTS.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 test` runs `scripts/test-changed.mjs`, selecting changed packages and using a cache. - `pnpm test:full` runs workspace tests with capped worker fanout. - `pnpm test:locked` exists for cross-worktree contention. - `pnpm test:isolated` checks 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 1. Keep high-signal tests that guard user-visible behavior, data durability, release artifacts, and concurrency safety. 2. Cut or consolidate low-signal tests that only assert implementation shape. 3. Add missing tests around release-critical and runtime-critical gaps. 4. Make local and CI test commands honest about what they cover. 5. 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/engine` imports in `packages/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.json` execution, `--help`, dashboard startup on port `0`, 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. 1. 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. 2. Runtime TypeScript shape tests. - Example: `packages/plugin-sdk/src/__tests__/index.test.ts`. - Keep `definePlugin` identity and real manifest validation behavior. Move type coverage to typecheck or compile-time examples. 3. Redundant API wrapper URL/header tests. - Examples: broad wrapper suites in `packages/dashboard/app/__tests__/api-settings.test.ts` and `api-tasks.test.ts`. - Keep auth/header/error parsing and streaming edge cases. Consolidate to one happy-path per wrapper family plus route contract tests. 4. 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. ### P1 Consolidation Candidates 1. Settings field/default assertions across core. - Consolidate overlap between settings parity, global settings, and store settings blocks. - Keep precedence, migration, and round-trip behavior. 2. 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`. 3. 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. 4. Duplicate desktop integration smoke. - Fold the smaller `main.integration.test.ts` checks into the richer `main-integration.test.ts`. 5. 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. ## What To Add ### P0 Additions 1. Reverse-dependent changed-test selection. - If `@fusion/core` changes, 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. 2. Dirty-worktree cache safety. - Disable the changed-test cache when affected files are dirty, or include working-tree content in package hashes. - `pnpm test` must not report cached success for files that have never been tested. 3. CI package coverage parity. - Generate CI shard package lists from workspace packages with `test` scripts. - Include plugin packages and `@fusion/pi-llama-cpp`, or run a dedicated plugin test lane. 4. Critical release/tarball contract test. - Add an `npm pack --dry-run` or equivalent assertion for `@runfusion/fusion`. - Required contents: `dist/bin.js`, `dist/client/**`, `dist/pi-claude-cli/**`, `dist/droid-cli/**`, `dist/plugins/**`, and `skill/**`. - Excluded contents: standalone binary outputs and unnecessary runtime build artifacts. 5. 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 1. Critical coverage lane. - Add `coverage:critical` for targeted files from `docs/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. 2. 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.ts` malformed/missing settings and valid arrays. - Add a small adapter test for `task-completion.ts` dependency lookup failures. - Add SQLite adapter constructor selection coverage if release binaries depend on it. 3. 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. 4. 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. 5. Accessibility smoke. - Add role/focus/escape coverage for major modals and command surfaces. 6. 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-cpp` tests for unreachable/malformed server responses and model listing. 7. 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 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: 1. Install and build once per shard where needed. 2. Run lint and typecheck. 3. Run deterministic test shards generated from all workspace packages with `test` scripts. 4. Split dashboard into separate lanes: - dashboard app/jsdom, - dashboard server/routes, - dashboard slow process/WebSocket/browser smoke. 5. Run plugin package tests. 6. Run `@fusion/pi-llama-cpp` tests. 7. Run `coverage:critical` only on critical target files. ### Release CI Target state: 1. Run `pnpm verify:workspace`. 2. Run CLI pre-release tests and binary build smoke. 3. Run tarball contract test. 4. Run plugin loader integration. 5. Use the repository release script for real releases: `pnpm release --yes`. ## Governance Rules 1. New tests should cover observable behavior, not implementation shape. 2. New CSS tests should prefer rendered/computed behavior over CSS source text. 3. New parser/regex coverage should be table-driven and limited to representative boundaries. 4. New high-volume tests must name the regression or user-visible contract they protect. 5. Tests tied to FN-ticket regressions are kept unless a better test explicitly replaces them. 6. Every package with a `test` script must either use shared worker budgeting and isolation or be explicitly allowlisted with a reason. 7. Skip markers must be documented in `docs/skipped-test-inventory.md` with an environment gate or replacement coverage owner. 8. 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 `test` script. - `pnpm test` is dirty-worktree safe. - `docs/skipped-test-inventory.md` matches current skip/gate usage. - `coverage:critical` exists 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-cpp` tests to PR coverage. - Apply shared worker budgeting/isolation to `droid-cli`, `pi-claude-cli`, and `pi-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 `--filter` escape 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?