unit-tests.md 8.7 KB


type: testing title: Unit tests description: Overview of the in-module unit test suites in src/*.rs (including the web panel, the version flag, the configuration watcher and the audio transcription client), 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-651d1fb6c9 resource: repo://Cargo.toml
  • id: openwiki-source-1b05507d70 resource: repo://src/ai.rs
  • id: openwiki-source-9fec9d1af4 resource: repo://src/app.rs
  • id: openwiki-source-043e6252ba resource: repo://src/autoreply.rs
  • id: openwiki-source-d6b2055bbb resource: repo://src/config_watch.rs
  • id: openwiki-source-2a737474d8 resource: repo://src/config.rs
  • id: openwiki-source-ce0c4ea8cd resource: repo://src/history.rs
  • id: openwiki-source-b55a21a31e resource: repo://src/main.rs
  • id: openwiki-source-feb00b7ad7 resource: repo://src/transcribe.rs
  • id: openwiki-source-6bddea306b resource: repo://src/ui.rs
  • id: openwiki-source-7b75006986 resource: repo://src/web.rs
  • id: openwiki-source-7ea5eac45e resource: repo://src/whatsapp.rs generated: { by: "opencode", at: "2026-10-24T16:39:50.206Z" } ---

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

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:1238-1258) is skipped by a plain cargo test. It starts a real client on a per-process target/wh-relink-test-<pid>.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.

Suites

Module Tests Location Covers
autoreply 55 src/autoreply.rs:551+ 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 41 src/config.rs:410+ defaults, full files, ignored legacy keys (system_prompt, cooldown keys, transcribe_command), blank values, trimming, base URL normalisation, Debug masking, DB and AI log paths, ai_log, reply_delay_seconds (default, custom and 0, negatives rejected), catch-up and ai_retries defaults/custom values/negative rejection, web_port (unset, valid, 0 and out of range rejected), transcription keys (unset disables with defaults, trimmed, blank falls back), vision model, 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:589+ 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 23 + 1 ignored src/whatsapp.rs:1216+ ignored live relink test (new QR with new device keys), fatal-only app state sync reporting, message rendering and placeholders, captions, image and audio references, voice/image detection, silent messages, JID device suffix, push-name filtering
app 23 src/app.rs:1941+ autoreply enable rules (private, self-chat LID, groups; broadcasts, newsletters and missing AI refused), web_chats filtering and order, descriptions_apply, select_media_to_describe (last five images / last three audios in arrival order, known and out-of-context items skipped, empty when not enabled), arrived_after, manual_reply_after, description and transcription failure statuses, the voice-note text, list filter matching, selection, page steps and toggle helpers, AltGr handling, fixed function-key codes
history 19 src/history.rs:563+ dedupe by message id, ordering and limits, messages_since (boundary, limit keeps most recent, ascending order), chat name fallbacks and rules, per-chat and global autoreply flags (including survival across incoming messages), message_exists, update_message_text, image descriptions (per chat, replaced, deleted with the chat), delete_chat, contacts, on-disk open
transcribe 17 src/transcribe.rs:299+ multipart body shape (fields before file part, headers, terminator, bytes unchanged), language rules (auto/blank omitted, explicit sent once and trimmed), audio_suffix mappings and fallback, MIME sanitisation against header injection, boundary uniqueness, audio validation (empty, oversize), transcription parsing (trim, blank/missing text)
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:208+ 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: 235 test functions — 234 #[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:83-90); the on-disk test writes under std::env::temp_dir() in a per-process directory (src/history.rs:864-872).
  • AI and transcription tests check request bodies, parsing, log records and, for the AI client, 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:865-877).
  • 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:439-444). The config_watch test does the same with a per-process file under target (src/config_watch.rs:61-74).
  • 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, Configuration, AI client, WhatsApp client, Web control panel, History store, Terminal UI, Application state, Runtime and event loop.