From f8660907e58454181c6b86f1a6c444b99ccc8b5c Mon Sep 17 00:00:00 2001 From: l5y <220195275+l5yth@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:05:23 +0100 Subject: [PATCH] matrix: cover missing unit test vectors --- matrix/src/cli.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++ matrix/src/config.rs | 28 +++++++++++++++++++- matrix/src/main.rs | 31 +++++++++++++++++----- 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/matrix/src/cli.rs b/matrix/src/cli.rs index 71388f3..54a7ef3 100644 --- a/matrix/src/cli.rs +++ b/matrix/src/cli.rs @@ -95,3 +95,65 @@ impl Cli { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cli_overrides_map_to_config() { + let cli = Cli::parse_from([ + "bridge", + "--config", + "/tmp/Config.toml", + "--state-file", + "/tmp/state.json", + "--potatomesh-base-url", + "https://potato.example/", + "--potatomesh-poll-interval-secs", + "15", + "--matrix-homeserver", + "https://matrix.example.org", + "--matrix-as-token", + "token", + "--matrix-server-name", + "example.org", + "--matrix-room-id", + "!room:example.org", + "--container-defaults", + ]); + + let overrides = cli.into_overrides(); + assert_eq!(overrides.config_path.as_deref(), Some("/tmp/Config.toml")); + assert_eq!(overrides.container_defaults, Some(true)); + assert_eq!( + overrides.values.potatomesh.base_url.as_deref(), + Some("https://potato.example/") + ); + assert_eq!(overrides.values.potatomesh.poll_interval_secs, Some(15)); + assert_eq!( + overrides.values.matrix.homeserver.as_deref(), + Some("https://matrix.example.org") + ); + assert_eq!(overrides.values.matrix.as_token.as_deref(), Some("token")); + assert_eq!( + overrides.values.matrix.server_name.as_deref(), + Some("example.org") + ); + assert_eq!( + overrides.values.matrix.room_id.as_deref(), + Some("!room:example.org") + ); + assert_eq!( + overrides.values.state.state_file.as_deref(), + Some("/tmp/state.json") + ); + } + + #[test] + fn cli_can_disable_container_defaults() { + let cli = Cli::parse_from(["bridge", "--no-container-defaults"]); + let overrides = cli.into_overrides(); + assert_eq!(overrides.container_defaults, Some(false)); + } +} diff --git a/matrix/src/config.rs b/matrix/src/config.rs index 6af33b0..a865180 100644 --- a/matrix/src/config.rs +++ b/matrix/src/config.rs @@ -237,7 +237,8 @@ fn detect_container() -> bool { /// Detect container context from provided inputs (used for testing). fn detect_container_from(env_value: Option<&str>, cgroup_contents: Option<&str>) -> bool { if let Some(value) = env_value.map(str::trim).filter(|v| !v.is_empty()) { - return value != "0" && value != "false" && value != "False"; + let normalized = value.to_ascii_lowercase(); + return normalized != "0" && normalized != "false"; } if let Some(cgroup) = cgroup_contents { @@ -672,6 +673,7 @@ mod tests { assert!(detect_container_from(Some("true"), None)); assert!(!detect_container_from(Some("0"), None)); assert!(!detect_container_from(Some("false"), None)); + assert!(!detect_container_from(Some("FALSE"), None)); } #[test] @@ -826,4 +828,28 @@ mod tests { Some(PathBuf::from(temp_dir.path())) ); } + + #[test] + #[serial] + fn read_env_bool_rejects_invalid_values() { + let _guard = EnvGuard::set("POTATOMESH_TEST_BOOL", "maybe"); + let result = read_env_bool("POTATOMESH_TEST_BOOL"); + assert!(result.is_err()); + } + + #[test] + #[serial] + fn read_env_u64_rejects_invalid_values() { + let _guard = EnvGuard::set("POTATOMESH_TEST_U64", "not-a-number"); + let result = read_env_u64("POTATOMESH_TEST_U64"); + assert!(result.is_err()); + } + + #[test] + fn read_secret_file_rejects_empty_contents() { + let file = tempfile::NamedTempFile::new().unwrap(); + fs::write(file.path(), " ").unwrap(); + let result = read_secret_file(file.path()); + assert!(result.is_err()); + } } diff --git a/matrix/src/main.rs b/matrix/src/main.rs index ffe7865..4360602 100644 --- a/matrix/src/main.rs +++ b/matrix/src/main.rs @@ -29,6 +29,13 @@ use crate::config::Config; use crate::matrix::MatrixAppserviceClient; use crate::potatomesh::{FetchParams, PotatoClient, PotatoMessage, PotatoNode}; +fn format_runtime_context(context: &config::RuntimeContext) -> String { + format!( + "Runtime context: in_container={} container_defaults={} config_path={} secrets_dir={:?}", + context.in_container, context.container_defaults, context.config_path, context.secrets_dir + ) +} + #[derive(Debug, serde::Serialize, serde::Deserialize, Default)] pub struct BridgeState { /// Highest message id processed by the bridge. @@ -178,13 +185,7 @@ async fn main() -> Result<()> { let cli = Cli::parse(); let bootstrap = Config::load_with_overrides(cli.into_overrides())?; info!("Loaded config: {:?}", bootstrap.config); - info!( - "Runtime context: in_container={} container_defaults={} config_path={} secrets_dir={:?}", - bootstrap.context.in_container, - bootstrap.context.container_defaults, - bootstrap.context.config_path, - bootstrap.context.secrets_dir - ); + info!("{}", format_runtime_context(&bootstrap.context)); let cfg = bootstrap.config; @@ -736,4 +737,20 @@ mod tests { assert_eq!(state.last_message_id, Some(100)); } + + #[test] + fn format_runtime_context_includes_flags() { + let context = config::RuntimeContext { + in_container: true, + container_defaults: false, + config_path: "/app/Config.toml".to_string(), + secrets_dir: Some(std::path::PathBuf::from("/run/secrets")), + }; + + let rendered = format_runtime_context(&context); + assert!(rendered.contains("in_container=true")); + assert!(rendered.contains("container_defaults=false")); + assert!(rendered.contains("/app/Config.toml")); + assert!(rendered.contains("/run/secrets")); + } }