Skip to content

Testing

Four suites cover the two applications. The counts below reflect the current codebase. Python and frontend counts were rechecked on 2026-08-08 from this checkout; Rust still requires a Cargo/Rust toolchain new enough for edition 2024 dependencies.

Suite Command Tests Coverage
Sidecar backend cd backend && ../.venv/bin/pytest 84 collected ~85% line when run with coverage
React renderer npm test 22
Tauri shell cd src-tauri && cargo test -- --test-threads=1 10 expected
Legacy Tkinter app .venv/bin/pytest 144 92.34%

Coverage target is 80% for Python, enforced with --cov-fail-under=80 on the legacy suite.


Sidecar backend

python -m venv .venv
.venv/bin/pip install -e "backend[dev]"
cd backend
../.venv/bin/pytest                          # all 84
../.venv/bin/pytest tests/unit               # domain + services
../.venv/bin/pytest --cov=portfolio_manager  # with coverage

Tiers

Tier Path What it covers
Unit — domain tests/unit/domain/ Scoring thresholds (59/60/79/80), ISO week boundaries, slug rules
Unit — services tests/unit/application/ Project, session, milestone, review, and scoring service rules
Integration tests/integration/ Repository CRUD and cascade, migration idempotency, backup-before-migrate
Contract tests/contract/ Auth (missing / invalid / valid token), full workflow, production hardening
Compatibility tests/compatibility/ A v2-schema legacy database upgrades to v4 and preserves data across restart

The contract tests drive the API through FastAPI's TestClient in-process. That is deliberate: it needs no running server and no network, so the suite works in sandboxes where a cross-process request to a locally started sidecar would be blocked.

test_production_hardening.py asserts the things that must not be present in a production build — Swagger, ReDoc, and the OpenAPI schema all return 404.


React renderer

npm test                # Vitest, jsdom: 22 tests across 7 files
npm run test:watch
npm run test:coverage
npm run typecheck       # tsc --noEmit, strict

Component tests use Testing Library and drive real user interactions (@testing-library/user-event) rather than calling handlers directly.

Run the scripts, not the tools

vite.config.ts and tsconfig.json live in frontend/, but the npm scripts live in the root package.json. The scripts pass the correct root through. Invoking vitest or tsc bare from the repository root finds no config, silently skips the jsdom environment and setup file, and reports failures that have nothing to do with your code.

Timezone sensitivity

src/utils/week.test.ts covers the week-key helpers, which convert between local wall-clock time and the UTC-based dates the rest of the module uses. A mismatch there produces an off-by-one week only in timezones behind UTC — a suite that runs in UTC will pass with the bug present. When touching utils/week.ts, run the suite under at least one negative-offset zone:

TZ=America/Los_Angeles npm test
TZ=UTC npm test
TZ=Asia/Tokyo npm test

Tauri shell

cd src-tauri
cargo test
cargo test -- --test-threads=1   # deterministic; see below

Ten unit tests cover token entropy and redaction, loopback port selection, the sidecar launch plan (asserting the token never appears in argv), readiness timeout, and child-process shutdown.

The build requires a frozen sidecar binary and an RGBA icon to exist before it will compile at all — see Development.

On the current machine, cargo test -- --test-threads=1 still fails under Cargo 1.83 while parsing serde_spanned 1.1.1 because that dependency requires the stabilized edition 2024 feature. Fix the local toolchain first:

rustup update stable
. "$HOME/.cargo/env"
rustc --version   # expect >= 1.85

Known flaky test

security::port::tests::picks_a_nonzero_loopback_port fails roughly 1 run in 10 under parallel execution, and never under --test-threads=1. It asserts that a released ephemeral port is immediately re-bindable, which is exactly the race security/port.rs documents as unavoidable and mitigates with readiness polling. The production code is correct; the assertion is stronger than the design guarantees.


Legacy Tkinter app

.venv/bin/pytest                  # 144 tests, 92.34% coverage
pytest tests/unit
pytest tests/integration
pytest tests/e2e                   # requires a Tk-capable Python

Views and controllers are excluded from coverage — Tkinter GUI code cannot be tested reliably headlessly. The e2e startup test calls Tk.withdraw() to suppress the window and skips itself when no display is available:

def test_builds_without_error(headless_settings):
    try:
        window = build_app(settings=headless_settings)
        window.withdraw()
        window.update()
        window.destroy()
    except tk.TclError as exc:
        pytest.skip(f"No display available: {exc}")

A Python built without _tkinter will skip the whole tier rather than fail.

Shared fixtures (tests/conftest.py)

Fixture Type Description
reset_singletons autouse Resets DatabaseConnection and EventBus around each test
in_memory_db DatabaseConnection Fully migrated in-memory SQLite connection
test_config Settings Settings pointing at a :memory: database
sample_project Project A persisted active project with plan_content
sample_sessions list[Session] Three sessions (planned, completed, cancelled)

Guard rails

Beyond the test suites, two checks enforce architectural invariants:

python scripts/verify_no_renderer_http.py   # renderer makes no direct HTTP calls
python scripts/generate_openapi.py          # regenerate types; diff to detect drift

The first backs ADR-001; the second backs ADR-005. Run both before a release.

See the Traceability Matrix for the requirement-to-test mapping.