The suite is ~700 tests across the workspace, run by CI on Linux, macOS, and Windows. This page documents how it's organized, the conventions to follow when adding tests, and the known coverage gaps.
./scripts/check.sh # fmt + clippy (-D warnings) + tests, mirrors CI
cargo test # all tests
cargo test -p void-core # one crate
cargo test --locked # exactly what CI runsCI (.github/workflows/ci.yml) also runs, on every push and PR:
- fmt —
cargo fmt --all --check - check matrix — clippy
-D warnings+ tests on ubuntu / windows / macOS, all--locked - msrv —
cargo checkon the declared MSRV (pinned inCargo.tomlrust-version); fails when a dependency raises the real floor, signalling a bump is needed - deny —
cargo deny check(license allow-list + RUSTSEC advisories) - coverage —
cargo llvm-cov→ Codecov, non-blocking
Tests are inline #[cfg(test)] mod modules next to the code (so they can reach private items), except void-cli binary tests which live in crates/void-cli/tests/ as integration tests. Shared DB seed fixtures (make_conversation, make_message, …) live in void_core::test_fixtures (feature test-fixtures).
| Area | Where | What |
|---|---|---|
| Binary CLI contract | void-cli/tests/cli_contract.rs |
every command's --help exits 0; required-arg violations exit non-zero |
| Read paths | void-cli/tests/read_paths.rs |
seeds an on-disk void.db in a tempdir, runs inbox/search/messages/… asserting seeded content |
| Read-path JSON snapshots | void-cli/tests/read_paths_snapshots.rs |
insta snapshots of inbox / conversations JSON envelopes (layout regressions) |
| First run | void-cli/tests/first_run.rs |
empty store / missing config never panics; doctor --non-interactive exits cleanly |
| Sync engine | void-core/src/sync/ |
mock Connector drives orchestration, failure isolation, cancellation, LOCK release |
| Database | void-core/src/db/ |
FTS5 search (incl. proptest fuzzing), bulk_archive_before, schema snapshot + migration data-preservation, dedup, mute |
| Hooks | void-core/src/hooks/ |
trigger matching, cron scheduling, active windows, placeholders, and execute_hook against a stub agent binary |
| Remote store | void-core/src/store/ |
fake ssh/scp on PATH verify argv, staging order, error surfacing; cache TTL |
| Config | void-core/src/config/ |
legacy [[accounts]] migration, unknown-type errors |
| Connectors | each void-* crate |
API-response parsing happy + error paths (401/429/5xx/malformed) over wiremock; message/media extraction |
- Determinism: no real network, no wall-clock (
Utc::now()) in assertions — inject fixedchronoinstants. No real user filesystem — usetempfile::tempdir(). - Mock
Connector: an in-crate test double implementing the asyncConnectortrait, recording calls viaArc<Mutex<…>>/atomics with configurable behavior (succeed / fail / block-until-cancelled). Seevoid-core/src/sync/tests.rs. - Stub agent (hooks): a shell script written to a tempdir emitting canned Claude-style stream-json, gated
#[cfg(unix)]. - Fake
ssh/scp(remote store): scripts on a prependedPATH, gated#[cfg(unix)], serialized on a mutex sincePATHis process-global. - HTTP connectors:
wiremock::MockServervia each client'swith_base_url(...)test constructor (including Hacker NewsHnClient::with_base_url). For a 429 retry test, setRetry-After: 0so retries exhaust without sleeping. - No
#[ignore]: a test that can't run is removed or#[cfg]-gated, not left ignored.
Honest list of what is not covered and why — good first contributions:
- Telegram/WhatsApp live sync (
start_sync,authenticate): still require a live MTProto/WhatsApp session;health_checkand pure extract/send helpers are now unit-tested, but full orchestration is not driven end-to-end without a real client.
When you close one of these, update this section.