Run Tests
All testing commands in one place.
Quick Reference
| What | Command | When to use |
|---|---|---|
| All Rust tests | cargo test | Before every commit |
| Single crate | cargo test -p <crate> | After changes to a specific crate |
| Integration (real SQLite) | cargo test -p core-tests | After storage or use case changes |
| All tests (Turbo) | pnpm test | Full suite across Rust + frontend |
| Frontend integration | pnpm test:integration | After UI or Tauri command changes |
| E2E (e2e) | pnpm test:e2e | End-to-end verification before release |
Rust Tests
Unit tests
Unit tests live alongside their source code in #[cfg(test)] modules. Run them with:
# All Rust tests in the workspacecargo test
# Tests for a specific cratecargo test -p domaincargo test -p applicationcargo test -p infrastructure-sqlite
# A specific test functioncargo test test_create_page_enforces_min_block
# All tests matching a patterncargo test word_countTest naming convention: test_[what]_[condition]_[expected].
Application layer tests use mock repositories from crates/application/src/test_helpers.rs:
use crate::test_helpers::MockPageRepository;use crate::permission::test_helpers::owner_guard;
#[test]fn test_get_page_by_slug() { let page = Page::new("My Page"); let repo = MockPageRepository::with_pages(vec![page]); let use_case = GetPageUseCase::new(repo);
let result = use_case.execute(&owner_guard(), "my-page").unwrap(); assert_eq!(result.title, "My Page");}Integration tests
Integration tests in tests/core/ wire real use cases to a real SQLite database in a temporary directory. They are the
authoritative test for storage correctness.
# Run all integration testscargo test -p core-tests
# Run a specific integration test filecargo test -p core-tests --test page_lifecycle
# Run with output visible (useful for debugging)cargo test -p core-tests -- --nocaptureTest files cover full lifecycle scenarios:
tests/core/tests/├── page_lifecycle.rs # Create, read, update, delete├── page_hierarchy.rs # Parent/child relationships, move, cascade delete├── page_search.rs # FTS5 full-text search├── tag_tests.rs # Tag CRUD, merge, page assignment├── attachment_lifecycle.rs # Upload, retrieve, dedup via SHA-256
├── event_log_lifecycle.rs # Event recording and timeline queries├── sync_queue_roundtrip.rs # Sync queue enqueue/dequeue├── loro_block_storage.rs # CRDT BLOB round-trip├── migration_chain.rs # V001 → latest schema migration└── ...setup_workspace (in tests/core/tests/common/mod.rs) handles temp directory creation, SQLite initialization, and
repository wiring. Use it directly in new integration tests:
use common::setup_workspace;
#[test]fn test_my_feature() { let ws = setup_workspace("my-feature-test"); // ws.page_repo, ws.workspace_repo, ws.db, etc. are ready to use}Frontend Tests
Playwright integration tests
Frontend integration tests in apps/desktop/tests/ run the React UI against a mocked Tauri backend. They test UI flows
and component behavior without requiring the Rust backend or native Tauri runtime.
# From the project rootpnpm test:integration
# Interactive UI mode (recommended for local development)pnpm --dir apps/desktop/tests test:ui
# Visible browser (useful for debugging)pnpm --dir apps/desktop/tests test:headed
# Step-through debuggingpnpm --dir apps/desktop/tests test:debug
# CI mode with GitHub reporterpnpm --dir apps/desktop/tests test:ciFirst-time setup:
cd apps/desktop/testspnpm installpnpm exec playwright installTest specs live in apps/desktop/tests/specs/. Each spec covers a feature area:
specs/├── workspace.spec.ts # Workspace creation, switching├── page.spec.ts # Page CRUD, hierarchy├── settings.spec.ts # Settings panel├── first-launch.spec.ts # Onboarding tour├── wiki-links.spec.ts # Wiki-link insertion and navigation├── editor-loro.spec.ts # CRDT editor round-trips└── ...The mock intercepts window.__TAURI__ calls. See fixtures/tauri-mock.ts for supported commands. To add a new mock
handler:
// In fixtures/tauri-mock.tscase "my_new_command": return { result: "data" };Viewing failure reports
After a failed run, open the HTML report:
pnpm --dir apps/desktop/tests reportTrace files are saved at test-results/<test-name>/trace.zip. Open with:
pnpm exec playwright show-trace test-results/<path>/trace.zipE2E Tests
E2E tests (tests/e2e/) are end-to-end tests that drive the real React UI against the real Rust backend via the HTTP
bridge — no mocks, no Tauri runtime required.
Setup
pnpm test:e2e handles bridge startup automatically via Playwright webServer config — no manual infrastructure setup
required.
Running tests
cd tests/e2e
# All testspnpm test
# Visible browserpnpm test:headed
# Interactive UI modepnpm test:ui
# Single spec filepnpm test workspace-page-crud
# Single test by namepnpm test -g "Create a new page"
# Single partitionpnpm test --project=editor-contentTest partitions
Tests are isolated across 5 bridge instances to prevent data collisions:
| Partition | Port | Scope |
|---|---|---|
| workspace-pages | 9990 | Workspace + page lifecycle |
| editor-content | 9991 | Editor + content ops |
| navigation-search | 9992 | Navigation, search, tags |
| advanced-features | 9993 | Import, attachments |
| first-launch | 9994 | Onboarding tour (fresh state) |
Troubleshooting
All tests fail with “sidebar not visible”: The first-launch overlay (PersonaSelector) is blocking. Check that
complete_first_launch ran:
curl -s -X POST http://localhost:9990/invoke/complete_first_launch \ -H 'Content-Type: application/json' -d '{}'Port conflict on startup:
lsof -i :1420 -i :9990 -i :9991 -i :9992 -i :9993 -i :9994tools/qa/stop-infrastructure.sh && pnpm test:e2eWriting New Tests
Which layer to test at
| Change | Where to test |
|---|---|
| Domain entity logic | Unit test in crates/domain/ |
| Use case behavior | Unit test with mocks in crates/application/ |
| Storage query correctness | Integration test in tests/core/ |
| Tauri command + UI wiring | Frontend integration test in apps/desktop/tests/ |
| Full user flow | E2E spec in tests/e2e/ |
Rust test naming
test_[what]_[condition]_[expected]Examples:
test_create_page_without_title_returns_errortest_get_page_not_found_returns_not_found_errortest_word_count_empty_page_returns_zero
Mock patterns
Application layer tests use constructors from crates/application/src/test_helpers.rs:
// Repository with pre-populated datalet repo = MockPageRepository::with_pages(vec![page_a, page_b]);
// Empty repositorylet repo = MockPageRepository::new();
// Permission guardslet guard = owner_guard(); // all capabilitieslet guard = guard_with(&[Capability::PagesRead]); // specific capabilitiesFor reference-aware tests, use MockReferenceRepository::with_refs(refs).
Integration test patterns
Use setup_workspace for real SQLite:
use common::{owner_guard, setup_workspace};
#[test]fn test_my_feature_persists() { let ws = setup_workspace("my-feature-persists"); let guard = owner_guard();
// Create something let use_case = CreatePageUseCase::new(ws.page_repo.clone()); use_case.execute(&guard, CreatePageRequest { title: "Test".to_string(), parent_slug: None, content: None, }).unwrap();
// Verify it persists via a second use case let get_uc = GetPageUseCase::new(ws.page_repo.clone()); let page = get_uc.execute(&guard, "test").unwrap(); assert_eq!(page.title, "Test");}LLM Integration Tests
LLM integration tests exercise the infrastructure-llm crate against a real Ollama server running locally. They are
skipped in CI — all tests carry #[ignore] and must be opted-in explicitly.
Setup
brew install ollama # macOSollama pull qwen3:4b # default test modelollama serve # start the server on localhost:11434Running
# All LLM integration testscargo test -p core-tests --test llm_integration -- --ignored
# A specific testcargo test -p core-tests --test llm_integration test_ollama_completion -- --ignoredAvailable tests
| Test | What it exercises |
|---|---|
test_ollama_completion | Basic prompt → text response round-trip |
test_ollama_streaming | Streaming API — verifies at least one chunk arrives |
test_ollama_tool_calls | Tool definition in request — accepts text or tool-call response |
test_byok_key_roundtrip | InMemoryKeyStore set/get/delete lifecycle (no Ollama needed) |
test_registry_provider_selection | ProviderRegistry default provider routing |
test_graceful_degradation | Unreachable URL returns Err, does not panic |
Skip behaviour
Each test calls OllamaTestProvider::try_new() at the start. If Ollama is not reachable (connection refused or 2 s
timeout), the test prints a SKIP message and returns early. This means you can run -- --ignored at any time without
risk of spurious failures on machines without Ollama.
Adding new LLM tests
Use OllamaTestProvider from the test-support feature:
use infrastructure_llm::test_support::OllamaTestProvider;
#[tokio::test]#[ignore] // Requires local Ollamaasync fn test_my_llm_scenario() { let Some(provider) = OllamaTestProvider::try_new().await else { return; // skip }; let registry = provider.registry(OllamaTestProvider::DEFAULT_MODEL); // ...}Python Sidecar Tests
The Python sidecar (apps/python-sidecar/) has its own test suite managed by pytest via uv.
Setup
cd apps/python-sidecaruv sync --group devRunning tests
# All tests (with coverage enabled by default via pyproject.toml addopts)uv run pytest
# Specific test fileuv run pytest tests/test_protocol.py
# Unit tests only (no external dependencies)uv run pytest -m unit
# Integration tests (may need LLM/sidecar running)uv run pytest -m integration
# With verbose coverage reportuv run pytest --cov --cov-report=term-missingTest files
apps/python-sidecar/tests/├── conftest.py # Shared fixtures (mock_prediction, fake_save_factory)├── test_protocol.py # IPC JSON-RPC protocol validation├── test_all_templates.py # Full template suite (all 10 templates)├── test_templates.py # Individual template behavior├── test_errors.py # Structured error responses├── test_execute.py # Execute service mode├── test_manifest.py # Manifest service mode├── test_dispatcher.py # Request routing└── test_main.py # Entry point and async loopCoverage requirements
Coverage is enforced at 80% minimum (pyproject.toml [tool.coverage.report] fail_under = 80). The --cov flag is
included in addopts so coverage runs on every uv run pytest invocation.
Linting and type checking
uv run ruff check . # Lint (pycodestyle, pyflakes, bugbear, etc.)uv run ruff format --check . # Format checkuv run mypy src/ # Strict type checking (Python 3.14)See Also
tests/core/tests/common/mod.rs—setup_workspaceandTestWorkspacehelpersapps/desktop/tests/README.md— full frontend test documentationtests/e2e/README.md— full E2E documentationapps/python-sidecar/README.md— full sidecar documentation- Development Guide — testing strategy and coverage goals
Was this page helpful?
Thanks for your feedback!