#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code: unwrap and expect on fixture setup are the expected diagnostics"
)]
#[path = "common/env.rs"]
mod env;
use aviso::resolve::{CodeAuth, CodeInputs, EnvAddress, ResolvedSettings, Source, resolve};
use aviso::{AvisoClient, AvisoClientBuilder, ClientError};
use env::{Sources, write_config};
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn resolve_here(inputs: &CodeInputs) -> ResolvedSettings {
resolve(
inputs,
&aviso::auth::DiscoveryPaths::from_env(),
EnvAddress::Read,
)
.expect("resolve")
.settings
}
#[test]
fn the_address_comes_from_code_then_the_environment_then_the_file() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(dir.path(), "base_url: https://file.example.org\n");
let _sources = Sources::in_dir(dir.path());
let from_file = resolve_here(&CodeInputs::default());
let url = from_file.base_url.expect("file supplies it");
assert_eq!(url.value, "https://file.example.org/");
assert_eq!(
url.source,
Source::ConfigFile(dir.path().join("config.yaml"))
);
unsafe { std::env::set_var("AVISO_BASE_URL", "https://env.example.org") };
let from_env = resolve_here(&CodeInputs::default());
let url = from_env.base_url.expect("env supplies it");
assert_eq!(url.value, "https://env.example.org/");
assert_eq!(url.source, Source::Environment("AVISO_BASE_URL"));
let inputs = CodeInputs {
base_url: Some("https://code.example.org".into()),
..CodeInputs::default()
};
let from_code = resolve_here(&inputs);
let url = from_code.base_url.expect("code supplies it");
assert_eq!(url.value, "https://code.example.org/");
assert_eq!(url.source, Source::Code);
Ok(())
}
#[test]
fn a_client_built_from_the_environment_uses_the_environment_address() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(dir.path(), "base_url: https://file.example.org\n");
let _sources = Sources::in_dir(dir.path());
unsafe { std::env::set_var("AVISO_BASE_URL", "https://env.example.org") };
let client = AvisoClientBuilder::from_environment(&CodeInputs::default())?.build()?;
assert_eq!(client.display_base_url(), "https://env.example.org/");
Ok(())
}
#[test]
fn the_report_hides_the_secret_and_names_each_source() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(
dir.path(),
"base_url: https://alice:hunter2@file.example.org\ntimeout: 7s\nauth:\n bearer_token: from-file\n",
);
let _sources = Sources::in_dir(dir.path());
let report = resolve_here(&CodeInputs::default());
let shown = format!("{report:?}");
assert!(!shown.contains("hunter2"), "got: {shown}");
assert!(!shown.contains("from-file"), "got: {shown}");
let url = report.base_url.expect("set");
assert_eq!(url.value, "https://file.example.org/");
assert_eq!(
report.timeout.value,
Some(std::time::Duration::from_secs(7))
);
assert!(matches!(report.timeout.source, Source::ConfigFile(_)));
assert_eq!(report.heartbeat_interval.value, None);
assert_eq!(report.heartbeat_interval.source, Source::Default);
let auth = report.auth.expect("found");
assert_eq!(auth.kind, "bearer");
assert!(matches!(auth.source, Source::ConfigFile(_)));
assert_eq!(auth.refused, None);
assert_eq!(report.config_file, Some(dir.path().join("config.yaml")));
Ok(())
}
#[test]
fn the_report_says_when_a_found_credential_would_be_refused() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(
dir.path(),
"base_url: http://public.example.org\nauth:\n bearer_token: from-file\n",
);
let _sources = Sources::in_dir(dir.path());
let report = resolve_here(&CodeInputs::default());
let auth = report.auth.expect("found");
let refused = auth.refused.expect("refused for plain http");
assert!(refused.contains("refused"), "got: {refused}");
assert!(refused.contains("public.example.org"), "got: {refused}");
let inputs = CodeInputs {
auth: Some(CodeAuth::Named(std::sync::Arc::new(
aviso::auth::Bearer::new("named")?,
))),
..CodeInputs::default()
};
let report = resolve_here(&inputs);
let auth = report.auth.expect("named");
assert_eq!(auth.kind, "bearer");
assert_eq!(auth.source, Source::Code);
assert_eq!(auth.refused, None);
let built = AvisoClientBuilder::from_environment(&inputs)?;
assert!(
format!("{built:?}").contains("Bearer"),
"the named credential must be on the builder: {built:?}"
);
built.build()?;
Ok(())
}
#[test]
fn no_address_anywhere_is_reported_as_none_and_refused_at_build() -> TestResult {
let dir = tempfile::tempdir()?;
let _sources = Sources::in_dir(dir.path());
let report = resolve_here(&CodeInputs::default());
assert_eq!(report.base_url, None);
assert_eq!(report.config_file, None);
let error = AvisoClientBuilder::from_environment(&CodeInputs::default())?
.build()
.unwrap_err();
assert!(matches!(error, ClientError::Config(_)), "got {error:?}");
assert!(error.to_string().contains("AVISO_BASE_URL"), "got {error}");
Ok(())
}
#[test]
fn from_file_reads_the_address_from_the_file_only() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(dir.path(), "base_url: https://file.example.org\n");
let _sources = Sources::in_dir(dir.path());
unsafe { std::env::set_var("AVISO_BASE_URL", "https://env.example.org") };
let client = AvisoClientBuilder::from_file()?.build()?;
assert_eq!(client.display_base_url(), "https://file.example.org/");
let client = AvisoClientBuilder::from_file_at(dir.path().join("config.yaml"))?.build()?;
assert_eq!(client.display_base_url(), "https://file.example.org/");
let ignored = resolve(
&CodeInputs::default(),
&aviso::auth::DiscoveryPaths::from_env(),
EnvAddress::Ignore,
)?
.settings;
assert!(matches!(
ignored.base_url.map(|u| u.source),
Some(Source::ConfigFile(_))
));
Ok(())
}
#[test]
fn from_environment_applies_the_values_passed_in_code() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(
dir.path(),
"base_url: https://file.example.org\ntimeout: 7s\nheartbeat_interval: 9s\n",
);
let _sources = Sources::in_dir(dir.path());
let inputs = CodeInputs {
base_url: Some("https://code.example.org/api".into()),
timeout: Some(std::time::Duration::from_secs(3)),
..CodeInputs::default()
};
let builder = AvisoClientBuilder::from_environment(&inputs)?;
assert!(
format!("{builder:?}").contains("timeout: Some(3s)"),
"got {builder:?}"
);
let client = builder.build()?;
assert_eq!(client.display_base_url(), "https://code.example.org/api/");
let report = resolve_here(&inputs);
assert_eq!(
report.base_url.expect("set").value,
"https://code.example.org/api/"
);
assert_eq!(report.timeout.source, Source::Code);
assert_eq!(
report.heartbeat_interval.value,
Some(std::time::Duration::from_secs(9))
);
Ok(())
}
#[test]
fn the_resolution_debug_output_hides_userinfo() -> TestResult {
let dir = tempfile::tempdir()?;
let _sources = Sources::in_dir(dir.path());
unsafe { std::env::set_var("AVISO_BASE_URL", "http://op:hunter2@127.0.0.1:1") };
let resolution = resolve(
&CodeInputs::default(),
&aviso::auth::DiscoveryPaths::from_env(),
EnvAddress::Read,
)?;
let shown = format!("{resolution:?}");
assert!(!shown.contains("hunter2"), "got: {shown}");
assert!(shown.contains("127.0.0.1"), "got: {shown}");
Ok(())
}
#[test]
fn a_plain_builder_error_does_not_claim_to_have_looked_anywhere() {
let error = AvisoClient::builder().build().unwrap_err();
assert!(!error.to_string().contains("AVISO_BASE_URL"), "got {error}");
}
#[test]
fn the_report_displays_one_setting_per_line_without_the_secret() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(
dir.path(),
"base_url: https://alice:hunter2@file.example.org\ntimeout: 7s\nauth:\n bearer_token: from-file\n",
);
let _sources = Sources::in_dir(dir.path());
let shown = resolve_here(&CodeInputs::default()).to_string();
assert!(!shown.contains("hunter2"), "got: {shown}");
assert!(!shown.contains("from-file"), "got: {shown}");
let lines: Vec<&str> = shown.lines().collect();
assert_eq!(lines.len(), 6, "got: {shown}");
assert!(lines[0].starts_with("base_url"), "got: {shown}");
assert!(
lines[0].contains("https://file.example.org/"),
"got: {shown}"
);
assert!(lines[1].contains("bearer"), "got: {shown}");
assert!(lines[2].contains("7s"), "got: {shown}");
assert!(lines[3].ends_with("(default)"), "got: {shown}");
Ok(())
}
#[test]
fn a_named_file_is_parsed_from_the_text_already_read() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(dir.path(), "base_url: https://first.example.org\n");
let _sources = Sources::in_dir(dir.path());
let loaded = aviso::ClientSettings::read(&dir.path().join("config.yaml"))?;
let mut paths = aviso::auth::DiscoveryPaths::from_env();
paths.config_file = Some(loaded.path);
paths.config_content = Some(loaded.content);
write_config(dir.path(), "base_url: https://second.example.org\n");
let report = resolve(&CodeInputs::default(), &paths, EnvAddress::Ignore)?.settings;
assert_eq!(
report.base_url.expect("set").value,
"https://first.example.org/"
);
Ok(())
}
#[test]
fn explicit_tls_keys_are_attributed_to_the_file() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(
dir.path(),
"base_url: https://file.example.org\ntls:\n ca_bundle: []\n danger_accept_invalid_certs: false\n",
);
let _sources = Sources::in_dir(dir.path());
let report = resolve_here(&CodeInputs::default());
assert!(matches!(report.ca_bundle.source, Source::ConfigFile(_)));
assert!(matches!(
report.danger_accept_invalid_certs.source,
Source::ConfigFile(_)
));
assert!(!report.danger_accept_invalid_certs.value);
Ok(())
}
#[test]
fn the_credentials_file_is_reported_only_when_the_search_reached_it() -> TestResult {
let dir = tempfile::tempdir()?;
write_config(dir.path(), "base_url: https://file.example.org\n");
let _sources = Sources::in_dir(dir.path());
let report = resolve_here(&CodeInputs::default());
assert!(report.credentials_file.is_some());
unsafe { std::env::set_var("AVISO_TOKEN", "from-env") };
let report = resolve_here(&CodeInputs::default());
assert_eq!(report.credentials_file, None);
let report = resolve_here(&CodeInputs {
auth: Some(CodeAuth::Anonymous),
..CodeInputs::default()
});
assert_eq!(report.credentials_file, None);
Ok(())
}
#[test]
fn from_file_without_an_address_does_not_mention_the_environment() -> TestResult {
let dir = tempfile::tempdir()?;
let _sources = Sources::in_dir(dir.path());
let error = AvisoClientBuilder::from_file()?.build().unwrap_err();
assert!(!error.to_string().contains("AVISO_BASE_URL"), "got {error}");
assert!(error.to_string().contains("config file"), "got {error}");
Ok(())
}