//! End-to-end CLI tests that don't need a mail server. use assert_cmd::Command; use predicates::prelude::*; use std::io::Write; fn posta() -> Command { let mut cmd = Command::cargo_bin("posta").unwrap(); // Isolate from the developer's real config and environment. for (k, _) in std::env::vars() { if k.starts_with("POSTA_") { cmd.env_remove(k); } } cmd } fn write_config(dir: &tempfile::TempDir, contents: &str) -> std::path::PathBuf { let path = dir.path().join("config.toml"); let mut f = std::fs::File::create(&path).unwrap(); f.write_all(contents.as_bytes()).unwrap(); path } const SAMPLE: &str = r#" default_account = "personal" [accounts.personal] email = "alice@example.com" imap_url = "imap://alice@mail.example.com" smtp_url = "smtp://alice@mail.example.com" password = "x" [accounts.work] email = "alice@work.example" imap_url = "imaps://alice@imap.work.example" "#; #[test] fn help_mentions_the_basics() { posta() .arg("--help") .assert() .success() .stdout(predicate::str::contains("IMAP/SMTP")) .stdout(predicate::str::contains("send")) .stdout(predicate::str::contains("list")); } #[test] fn version_prints_name_and_number() { posta() .arg("--version") .assert() .success() .stdout(predicate::str::contains("posta")); } #[test] fn accounts_lists_names_without_secrets() { let dir = tempfile::tempdir().unwrap(); let cfg = write_config(&dir, SAMPLE); posta() .arg("--config") .arg(&cfg) .arg("accounts") .assert() .success() .stdout(predicate::str::contains("personal")) .stdout(predicate::str::contains("work")) .stdout(predicate::str::contains("\"x\"").not()); } #[test] fn missing_config_file_is_a_clear_error() { posta() .env("POSTA_CONFIG", "/nonexistent/posta.toml") .arg("status") .assert() .failure() .stderr(predicate::str::contains("cannot read config file")); } #[test] fn unknown_account_error_lists_available_ones() { let dir = tempfile::tempdir().unwrap(); let cfg = write_config(&dir, SAMPLE); posta() .arg("--config") .arg(&cfg) .args(["--account", "nope", "status"]) .assert() .failure() .stderr(predicate::str::contains("personal").and(predicate::str::contains("work"))); } #[test] fn invalid_toml_reports_the_file() { let dir = tempfile::tempdir().unwrap(); let cfg = write_config(&dir, "accounts = 42"); posta() .arg("--config") .arg(&cfg) .arg("status") .assert() .failure() .stderr(predicate::str::contains("invalid config file")); } #[test] fn env_only_mode_needs_no_config_file() { // No config file at all: endpoints come from the environment. The URL is // parsed before any connection is attempted, so a bad one fails fast. posta() .env("POSTA_CONFIG", "/nonexistent/posta.toml") .env_remove("POSTA_CONFIG") // ensure truly unset .env("HOME", "/nonexistent-home") .env("POSTA_IMAP_URL", "ftp://wrong") .arg("status") .assert() .failure() .stderr(predicate::str::contains("imap://")); } #[test] fn send_rejects_invalid_recipient_before_connecting() { let dir = tempfile::tempdir().unwrap(); let cfg = write_config(&dir, SAMPLE); posta() .arg("--config") .arg(&cfg) .args([ "send", "--to", "not-an-address", "--subject", "hi", "--body", "x", ]) .assert() .failure() .stderr(predicate::str::contains("invalid recipient")); }