#![cfg(test)] use std::{fs, os::unix::fs::symlink, path::Path, process::Command}; use tempfile::tempdir; fn command() -> Command { let mut command = Command::new(env!("CARGO_BIN_EXE_snapshot-manager")); command .env_remove("SIGNAL_ALERTS") .env_remove("SIGNAL_ALERT_FROM_PHONE") .env_remove("SIGNAL_ALERT_TO_PHONE") .env_remove("SNAPSHOT_TEST_SCRIPT") .env_remove("RUST_LOG"); command } #[test] fn help_works_without_phone_numbers() { let output = command().arg("--help").output().unwrap(); assert!(output.status.success()); assert!( String::from_utf8(output.stdout) .unwrap() .contains("") ); } #[test] fn help_documents_environment_variables_without_exposing_phone_numbers() { let output = command() .env("SIGNAL_ALERT_FROM_PHONE", "+15555550101") .env("SIGNAL_ALERT_TO_PHONE", "+15555550102") .arg("--help") .output() .unwrap(); assert!(output.status.success()); let stdout = String::from_utf8(output.stdout).unwrap(); for name in [ "SIGNAL_ALERTS", "SIGNAL_ALERT_FROM_PHONE", "SIGNAL_ALERT_TO_PHONE", ] { assert!(stdout.contains(name), "{stdout}"); } assert!(!stdout.contains("+15555550101"), "{stdout}"); assert!(!stdout.contains("+15555550102"), "{stdout}"); } #[test] fn config_argument_is_required() { let output = command().output().unwrap(); assert_eq!(output.status.code(), Some(2)); } #[test] fn enabled_alerts_require_phone_numbers_before_running() { for use_env in [false, true] { for (from, to, expected) in [ (None, None, "--signal-alert-from-phone"), (Some(""), None, "--signal-alert-from-phone"), (Some("+15555550101"), None, "--signal-alert-to-phone"), (Some("+15555550101"), Some(""), "--signal-alert-to-phone"), (None, Some("+15555550102"), "--signal-alert-from-phone"), ] { let mut command = command(); if use_env { command.env("SIGNAL_ALERTS", "true"); } else { command.args(["--signal-alerts", "true"]); } if let Some(from) = from { command.env("SIGNAL_ALERT_FROM_PHONE", from); } if let Some(to) = to { command.env("SIGNAL_ALERT_TO_PHONE", to); } let output = command.arg("unused.toml").output().unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); assert_eq!(output.status.code(), Some(2), "{stderr}"); assert!(stderr.contains(expected), "{stderr}"); assert!(!stderr.contains("Starting snapshot_manager"), "{stderr}"); } } } #[test] fn enabled_alerts_accept_environment_variables_and_cli_overrides() { let dir = tempdir().unwrap(); let config = dir.path().join("config.toml"); let scenario = dir.path().join("scenario"); fs::write(&config, "").unwrap(); // An empty dataset list completes successfully without sending an alert. fs::write(&scenario, "").unwrap(); symlink( Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/zfs"), dir.path().join("zfs"), ) .unwrap(); for use_cli in [false, true] { let mut command = command(); if use_cli { command .env("SIGNAL_ALERTS", "invalid") .env("SIGNAL_ALERT_FROM_PHONE", "") .env("SIGNAL_ALERT_TO_PHONE", "") .args([ "--signal-alerts", "true", "--signal-alert-from-phone", "+15555550101", "--signal-alert-to-phone", "+15555550102", ]); } else { command .env("SIGNAL_ALERTS", "true") .env("SIGNAL_ALERT_FROM_PHONE", "+15555550101") .env("SIGNAL_ALERT_TO_PHONE", "+15555550102"); } let output = command .current_dir(dir.path()) .env("PATH", dir.path()) .env("SNAPSHOT_TEST_SCRIPT", &scenario) .arg(&config) .output() .unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); assert!(output.status.success(), "{stderr}"); assert!(stderr.contains("snapshot_manager completed"), "{stderr}"); } } #[test] fn disabled_alerts_allow_snapshot_failures_without_phone_numbers() { let dir = tempdir().unwrap(); let config = dir.path().join("config.toml"); fs::write(&config, "").unwrap(); symlink( Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/zfs"), dir.path().join("zfs"), ) .unwrap(); for (env_value, args) in [ (None, vec![]), (Some("false"), vec![]), (None, vec!["--signal-alerts", "false"]), (Some("true"), vec!["--signal-alerts", "false"]), ] { let mut command = command(); if let Some(value) = env_value { command.env("SIGNAL_ALERTS", value); } let output = command .env("PATH", dir.path()) .args(args) .arg(&config) .output() .unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); assert!(output.status.success(), "{stderr}"); assert!( stderr.contains("pool/home failed to create snapshot"), "{stderr}" ); assert!(stderr.contains("snapshot_manager completed"), "{stderr}"); assert!(!stderr.contains("Signal"), "{stderr}"); } } #[test] fn disabled_alerts_preserve_fatal_errors_without_phone_numbers() { let dir = tempdir().unwrap(); for args in [vec![], vec!["--signal-alerts", "false"]] { let output = command() .args(args) .arg(dir.path().join("missing.toml")) .output() .unwrap(); let stderr = String::from_utf8(output.stderr).unwrap(); assert_eq!(output.status.code(), Some(1), "{stderr}"); assert!(stderr.contains("Failed to read configuration"), "{stderr}"); assert!(!stderr.contains("SIGNAL_ALERT_"), "{stderr}"); assert!(!stderr.contains("Failed to send failure alert"), "{stderr}"); } } #[test] fn signal_alerts_requires_a_boolean_value() { for use_env in [false, true] { let mut command = command(); if use_env { command.env("SIGNAL_ALERTS", "invalid"); } else { command.args(["--signal-alerts", "invalid"]); } let output = command.arg("unused.toml").output().unwrap(); assert_eq!(output.status.code(), Some(2)); } }