use super::*;
use crate::test_support::{capture_info_logs, capture_warn_logs};
struct TracingCase {
with_cluster: bool,
stored: Option<&'static str>,
explicit: bool,
expected: &'static str,
}
fn reuse_events(logs: &[String]) -> Vec<&String> {
logs.iter()
.filter(|line| line.contains("password_reuse"))
.collect()
}
fn sole_reuse_event(logs: &[String]) -> Result<String> {
let events = reuse_events(logs);
match events.as_slice() {
[only] => Ok((*only).clone()),
other => Err(eyre!(
"expected exactly one password_reuse event, found {}: {logs:?}",
other.len()
)),
}
}
fn info_logs_of(
scratch: &Scratch,
explicit: bool,
) -> (Vec<String>, BootstrapResult<PasswordReuseOutcome>) {
let mut settings = Settings {
password: "fresh".into(),
..Settings::default()
};
capture_info_logs(|| {
reuse_existing_password(
&mut settings,
&scratch.data_dir,
&scratch.password_file,
explicit,
)
})
}
fn warn_logs_of(scratch: &Scratch) -> (Vec<String>, BootstrapResult<PasswordReuseOutcome>) {
let mut settings = Settings {
password: "fresh".into(),
..Settings::default()
};
capture_warn_logs(|| {
reuse_existing_password(
&mut settings,
&scratch.data_dir,
&scratch.password_file,
false,
)
})
}
#[rstest]
#[case::reused(TracingCase { with_cluster: true, stored: Some("kept-secret"), explicit: false, expected: "Reused" })]
#[case::explicit(TracingCase { with_cluster: true, stored: Some("kept-secret"), explicit: true, expected: "ExplicitPassword" })]
#[case::no_cluster(TracingCase { with_cluster: false, stored: None, explicit: false, expected: "NoCluster" })]
fn each_success_branch_emits_its_outcome(scratch: Result<Scratch>, #[case] case: TracingCase) {
let dir = scratch.expect("scratch");
arrange(&dir, case.with_cluster, case.stored).expect("arrange");
let (logs, outcome) = info_logs_of(&dir, case.explicit);
outcome.expect("the reuse must succeed");
let event = sole_reuse_event(&logs).expect("one event");
assert!(
event.contains(case.expected),
"the event must name the {} outcome: {event:?}",
case.expected
);
}
#[rstest]
#[case::missing_file(None, "missing_file")]
#[case::empty_file(Some(""), "empty_file")]
fn each_failure_branch_warns_with_its_outcome(
scratch: Result<Scratch>,
#[case] stored: Option<&str>,
#[case] expected: &str,
) {
let dir = scratch.expect("scratch");
arrange(&dir, true, stored).expect("arrange");
let (logs, outcome) = warn_logs_of(&dir);
outcome.expect_err("the reuse must fail");
let event = sole_reuse_event(&logs).expect("one event");
assert!(
event.contains(expected),
"the event must name the {expected} outcome: {event:?}"
);
}
#[rstest]
fn a_successful_reuse_is_not_a_warning(scratch: Result<Scratch>) {
let dir = scratch.expect("scratch");
arrange(&dir, true, Some("kept-secret")).expect("arrange");
let (logs, outcome) = warn_logs_of(&dir);
outcome.expect("the reuse must succeed");
assert!(
reuse_events(&logs).is_empty(),
"a reuse that succeeded must not warn: {logs:?}"
);
}
#[rstest]
fn the_stored_password_is_never_logged(scratch: Result<Scratch>) {
let dir = scratch.expect("scratch");
arrange(&dir, true, Some("kept-secret")).expect("arrange");
let (info, adopted) = info_logs_of(&dir, false);
adopted.expect("the reuse must succeed");
let (warnings, repeated) = warn_logs_of(&dir);
repeated.expect("the reuse must succeed again");
for line in info.iter().chain(warnings.iter()) {
assert!(
!line.contains("kept-secret"),
"the stored password must never be logged: {line:?}"
);
}
}