--- type: testing title: Unit tests description: Overview of the in-module unit test suites in src/*.rs (including the web panel, the version flag and the configuration watcher), the ignored live relink test, what each one covers, how they isolate files, and how to run them. tags: [testing, cargo, unit-tests] sources: - id: openwiki-source-651d1fb6c9e49916a916ab51 resource: repo://Cargo.toml - id: openwiki-source-1b05507d7085e8e99a9b64f1 resource: repo://src/ai.rs - id: openwiki-source-9fec9d1af408501c9c455161 resource: repo://src/app.rs - id: openwiki-source-043e6252badc57a1ee6bfb48 resource: repo://src/autoreply.rs - id: openwiki-source-d6b2055bbb49804b8a855fd2 resource: repo://src/config_watch.rs - id: openwiki-source-2a737474d86fc75cc9d9694f resource: repo://src/config.rs - id: openwiki-source-ce0c4ea8cd3b15431fa691ce resource: repo://src/history.rs - id: openwiki-source-b55a21a31ede1b56cd31a6a6 resource: repo://src/main.rs - id: openwiki-source-6bddea306b9b87f180f1fc02 resource: repo://src/ui.rs - id: openwiki-source-7b75006986d8b0337adc6ca0 resource: repo://src/web.rs - id: openwiki-source-7ea5eac45e5030178a6ea05b resource: repo://src/whatsapp.rs generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" } verified: - by: openwiki/0.7.2 at: 2026-10-23T21:26:24.156Z --- # Unit tests All tests are plain Rust unit tests in `#[cfg(test)] mod tests` blocks at the bottom of each source file. There are no integration tests and no `[dev-dependencies]` (`Cargo.toml` only has `[dependencies]`). Apart from one `#[ignore]`d live test (see below), no test touches the network, binds a port or uses a real WhatsApp session. ## Running ```sh cargo test # whole suite cargo test autoreply:: # one module cargo test should_schedule # by name filter cargo test -- --ignored # only the ignored live test (needs network) ``` The live test `whatsapp::tests::relink_deletes_the_session_and_offers_a_new_qr` (`src/whatsapp.rs:1163-1183`) is skipped by a plain `cargo test`. It starts a real client on a per-process `target/wh-relink-test-.db`, waits up to 60 s for a pairing QR from the WhatsApp servers, calls `WaHandle::relink`, waits for the next QR and asserts that the device keys in the two QR payloads differ (a new device). It finally stops the session and empties the test database, because on Windows the file stays open until the process exits. See [WhatsApp relink and pairing](../workflows/relink-and-pairing.md). ## Suites | Module | Tests | Location | Covers | |---|---|---|---| | `autoreply` | 55 | `src/autoreply.rs:505+` | private/group/self-chat detection, `supports_autoreply` (groups yes, broadcasts and newsletters no), `ReplyGate::should_schedule` rules (disabled, own messages, self-chat, backlog, blank/placeholder text, voice notes and images trigger, pending chat), pending tracking and `take_pending`, `answered_by_owner`, `catch_up_due` rules, `build_turns` (system turn naming private chat, group or self-chat, roles, `is_ai` in the self-chat, context cap, blank texts skipped, voice notes, stored image descriptions vs. unseen images, assistant turns untouched, group sender prefixes), `context_start`, `mark_own_mentions` | | `config` | 37 | `src/config.rs:350+` | defaults, full files, ignored legacy `system_prompt` key, blank values, trimming, base URL normalisation, `Debug` masking, DB and AI log paths, `ai_log`, `reply_delay_seconds` (default, custom and `0`, negatives rejected, legacy cooldown keys ignored), catch-up and `ai_retries` defaults/custom values/negative rejection, `web_port` (unset, valid, `0` and out of range rejected), BOM/CRLF, non-leaking parse errors, missing/invalid/unreadable files, `SOUL.md` reading (missing, blank, as written without BOM, non-UTF-8 error naming the path) | | `ai` | 27 | `src/ai.rs:581+` | text request body with the `no_reply` tool, image description body (no tools) and its `VISION.md`/default request, image validation limits, `describe_image` errors, `Debug` redaction, reply parsing (`no_reply` with or without text, other tool calls ignored, missing choices/content), endpoint construction, AI log records (no key, images redacted, `no_reply` record), logging only when enabled, append-only log writes, retry policy (`is_retryable`, `StatusError` message, linear `retry_delay`), truncation | | `whatsapp` | 22 + 1 ignored | `src/whatsapp.rs:1140+` | ignored live relink test (new QR with new device keys), fatal-only app state sync reporting, message rendering and placeholders, captions, image references, voice/image detection, silent messages, JID device suffix, push-name filtering | | `app` | 20 | `src/app.rs:1596+` | autoreply enable rules (private, self-chat LID, groups; broadcasts, newsletters and missing AI refused), `web_chats` filtering and order, vision applicability, `select_images_to_describe` (last five in arrival order, known and out-of-context images skipped, nothing without vision), `arrived_after`, `manual_reply_after`, `description_failure_status`, list filter matching, selection, page steps and toggle helpers, AltGr handling, fixed function-key codes | | `history` | 18 | `src/history.rs:543+` | dedupe by message id, ordering and limits, `messages_since` (boundary, limit keeps most recent, ascending order), chat name fallbacks, per-chat and global autoreply flags, `message_exists`, image descriptions (per chat, replaced, deleted with the chat), `delete_chat`, contacts, on-disk open | | `ui` | 15 | `src/ui.rs:579+` | footer key help (F4 highlight), version label, sender labels, clock time, QR text, wrapping, visible lines, input tail, messages title, list title (global marker, filter), selection clamp | | `web` | 11 | `src/web.rs:411+` | query stripping, route resolution (including `POST /api/relink`, and `GET /api/relink` not found), `X-Wh-Request` header check (any case, missing/wrong value), which routes require it (relink included), `qr_svg` output is an SVG document, request body trimming, empty/invalid UTF-8 rejection and the 1024-byte limit | | `main` | 2 | `src/main.rs:207+` | only `--version` / `-V` request the version; `BUILD_VERSION` starts with the package version and names a commit | | `config_watch` | 1 | `src/config_watch.rs:54+` | modification-time snapshots are `None` for a missing file and change when the file is created and deleted | Total: 209 test functions — 208 `#[test]` run by `cargo test` plus the ignored `#[tokio::test]` live relink test. Relink, configuration reload and F3 targeting have no tests at the `App` level. ## Test isolation - History tests use `History::open_in_memory()` (only compiled for tests, `src/history.rs:78-89`); the on-disk test writes under `std::env::temp_dir()` in a per-process directory (`src/history.rs:837-852`). - AI tests check request bodies, parsing, log records and the retry policy without sending requests; the append test writes a per-process file under `std::env::temp_dir()` and removes it (`src/ai.rs:861-873`). - Config tests that read `SOUL.md`-style prompt files write per-process scratch files under the `target` directory and remove them (`scratch_path`, `src/config.rs:357-383`). The `config_watch` test does the same with a per-process file under `target` (`src/config_watch.rs:54-73`). - Web tests exercise the pure routing, parsing and QR rendering helpers; the HTTP server itself is never started. - The live relink test writes its session database under `target` and is ignored by default because it needs the WhatsApp servers. Pages with module details: [AI autoreply](../workflows/ai-autoreply.md), [Configuration](../operations/configuration-and-data.md), [AI client](../integrations/ai-client.md), [WhatsApp client](../integrations/whatsapp-client.md), [Web control panel](../integrations/web-control-panel.md), [History store](../concepts/history-store.md), [Terminal UI](../architecture/terminal-ui.md), [Application state](../architecture/app-state.md), [Runtime and event loop](../architecture/runtime-and-event-loop.md).