Testing Strategy
How OpenHuman tests its product. The definitive source for "where does my test go?" Companion TEST-COVERAGE-MATRIX.md.
Test Tiers
| Tier | Location | What It Tests | Driven By |
|---|---|---|---|
| Rust Unit | #[cfg(test)] mod tests within source *.rs files,同级 tests.rs, or tests/ subdirectory under a domain | Pure domain logic, schemas, RPC handler shapes, in-memory state machines | cargo test |
| Rust Integration | tests/*.rs at repo root | Full domain connections with real Tokio runtime, mock external services, JSON-RPC end-to-end | pnpm test:rust |
| Vitest Unit | Source-collocated *.test.ts(x) or under app/src/**/__tests__/ | React components, hooks, store slices, pure utilities, service layer adapters | pnpm test:unit |
| WDIO E2E | app/test/e2e/specs/*.spec.ts | Full desktop flow: UI → Tauri → core sidecar → JSON-RPC; user-visible behavior | Linux CI: tauri-driver (port 4444). macOS local: Appium Mac2 (port 4723) |
| Manual Smoke | docs/RELEASE-MANUAL-SMOKE.md | OS-level surfaces the driver can't assert: TCC permission prompts, Gatekeeper, code signing, DMG install, OS-native toasts | Human confirmation at release time |
Test Placement Decision Tree
Is the change behind the JSON-RPC boundary (in `src/`)?
├─ Yes - Does it cross domains or talk to external services?
│ ├─ Yes → Rust Integration (tests/*.rs)
│ └─ No → Rust Unit (next to source)
└─ No - The change is in `app/`
├─ Is it a pure function, hook, slice, or isolated component?
│ └─ Yes → Vitest Unit (*.test.tsx collocated)
└─ Is it user-visible and crosses UI ⇄ Tauri ⇄ sidecar ⇄ JSON-RPC?
├─ Yes → WDIO E2E (app/test/e2e/specs/*.spec.ts)
└─ Is it OS-level (TCC, Gatekeeper, install, OS toasts)?
└─ Yes → Manual smoke checklist
If your change touches multiple tiers, write tests at each tier you touch. Don't substitute one tier for another.
Failure Path Requirements
Every feature leaf in the coverage matrix must have at least one failure/edge assertion in addition to the happy path. Examples:
- File write tool: happy path = write bytes; failure path = path restriction rejection
- OAuth flow: happy path = issue token; edge = expired refresh token recovery
- Memory storage: happy path = store + recall; edge = forget then recall returns empty
Mock Strategy
- No real network in unit/integration/E2E. Use the shared mock backend
- External services (Telegram, Slack, Gmail, Notion, Ollama, OpenAI, etc.) are stubbed at the mock backend level
- The only acceptable exception is documented manual smoke steps at release time
Determinism Rules
- Don't use wall-clock waits; use
waitForApp,waitForAppReady,waitForWebViewhelpers or explicit element-ready predicates - Don't use shared filesystem state; each E2E spec runs in an isolated
OPENHUMAN_WORKSPACE - Don't depend on ordering-related specs; each spec must pass when run alone
- Don't depend on absolute coordinates or animation timing
Pre-Merge Checks
# Rust core
cargo fmt --check
cargo check --manifest-path Cargo.toml
cargo clippy --manifest-path Cargo.toml -- -D warnings
cargo test --manifest-path Cargo.toml
# Tauri shell
cargo check --manifest-path app/src-tauri/Cargo.toml
# Frontend
pnpm typecheck
pnpm lint
pnpm format:check
pnpm test:unit
# Rust integration
pnpm test:rust
# E2E (slow — only run when behavior changes visibly)
pnpm test:e2e:build
What the Driver Cannot Automate
Some surfaces cannot be driven by WDIO / Appium driver because they cross OS-level trust boundaries or hardware paths:
- macOS TCC permission prompts (Accessibility, Input Monitoring, Screen Recording, Microphone)
- Gatekeeper signature verification
- Code signature integrity
- DMG install / drag-to-Applications flow
- Auto-update download + restart
- OS-native notification toasts on Linux
Coverage Matrix as Contract
Every feature leaf in the coverage matrix maps to:
- A test path, or
- A
🚫with justification and a manual smoke entry
When you add/delete/rename a feature, update the matrix row in the same PR.
Next Steps
- E2E Testing - End-to-end test details
- Release Policy - Release cadence and versioning strategy