use assert_cmd::Command;
#[test]
fn no_log_files_written_to_cwd_when_home_unset() {
let workdir = tempfile::tempdir().expect("tempdir");
Command::cargo_bin("bito")
.expect("bito binary should build")
.env("BITO_NO_UPDATE_CHECK", "1")
.arg("info")
.current_dir(workdir.path())
.env_remove("HOME")
.env_remove("XDG_STATE_HOME")
.env_remove("BITO_LOG_DIR")
.env_remove("BITO_LOG_PATH")
.assert()
.success();
let strays: Vec<_> = std::fs::read_dir(workdir.path())
.expect("read workdir")
.filter_map(Result::ok)
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.filter(|name| name.contains("jsonl"))
.collect();
assert!(
strays.is_empty(),
"bito wrote log files into the working directory: {strays:?}"
);
}
#[test]
fn live_log_file_has_stable_name() {
let logdir = tempfile::tempdir().expect("tempdir");
Command::cargo_bin("bito")
.expect("bito binary should build")
.env("BITO_NO_UPDATE_CHECK", "1")
.args(["-v", "info"])
.env("BITO_LOG_DIR", logdir.path())
.env_remove("RUST_LOG")
.assert()
.success();
let entries: Vec<String> = std::fs::read_dir(logdir.path())
.expect("read logdir")
.filter_map(Result::ok)
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect();
let live = logdir.path().join("bito.jsonl");
let written = std::fs::metadata(&live).map(|m| m.len()).unwrap_or(0);
assert!(
written > 0,
"bito.jsonl should be the live log and hold the run's output; found: {entries:?}"
);
let siblings: Vec<&String> = entries
.iter()
.filter(|name| *name != "bito.jsonl")
.collect();
assert!(
siblings.is_empty(),
"a same-day run should leave only the live log; found: {siblings:?}"
);
}
#[test]
#[cfg(unix)]
fn log_file_is_private() {
use std::os::unix::fs::PermissionsExt as _;
let logdir = tempfile::tempdir().expect("tempdir");
Command::cargo_bin("bito")
.expect("bito binary should build")
.env("BITO_NO_UPDATE_CHECK", "1")
.arg("info")
.env("BITO_LOG_DIR", logdir.path())
.assert()
.success();
let live = logdir.path().join("bito.jsonl");
let mode = std::fs::metadata(&live)
.expect("log file should exist")
.permissions()
.mode()
& 0o777;
assert_eq!(mode, 0o600, "log file should be 0600, got {mode:o}");
}
fn plant_stale_rotated_log(dir: &std::path::Path, days_old: u64) -> std::path::PathBuf {
let path = dir.join("bito.2020-01-01.jsonl");
let file = std::fs::File::create(&path).expect("create rotated log");
let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(days_old * 86_400);
file.set_times(std::fs::FileTimes::new().set_modified(stale))
.expect("backdate rotated log");
path
}
#[test]
fn stale_rotated_logs_are_pruned_by_default() {
let logdir = tempfile::tempdir().expect("tempdir");
let stale = plant_stale_rotated_log(logdir.path(), 30);
Command::cargo_bin("bito")
.expect("bito binary should build")
.env("BITO_NO_UPDATE_CHECK", "1")
.arg("info")
.env("BITO_LOG_DIR", logdir.path())
.assert()
.success();
assert!(
!stale.exists(),
"a 30-day-old rotated log should be pruned under the 7-day default"
);
}
#[test]
fn log_retention_days_zero_keeps_rotated_logs() {
let logdir = tempfile::tempdir().expect("tempdir");
let confdir = tempfile::tempdir().expect("tempdir");
let stale = plant_stale_rotated_log(logdir.path(), 30);
let config = confdir.path().join("bito.toml");
std::fs::write(&config, "log_retention_days = 0\n").expect("write config");
Command::cargo_bin("bito")
.expect("bito binary should build")
.env("BITO_NO_UPDATE_CHECK", "1")
.arg("--config")
.arg(&config)
.arg("info")
.env("BITO_LOG_DIR", logdir.path())
.assert()
.success();
assert!(
stale.exists(),
"log_retention_days = 0 should disable pruning entirely"
);
}
#[test]
fn log_dir_env_var_outranks_an_explicit_config_file() {
let workdir = tempfile::tempdir().expect("tempdir");
let from_file = workdir.path().join("from-file");
let from_env = workdir.path().join("from-env");
std::fs::create_dir_all(&from_file).expect("create from-file");
std::fs::create_dir_all(&from_env).expect("create from-env");
let config = workdir.path().join("explicit.toml");
std::fs::write(
&config,
format!("log_dir = {:?}\n", from_file.to_str().expect("utf-8")),
)
.expect("write config");
Command::cargo_bin("bito")
.expect("bito binary should build")
.env("BITO_NO_UPDATE_CHECK", "1")
.env("BITO_LOG_DIR", &from_env)
.env_remove("BITO_LOG_PATH")
.args(["-c", config.to_str().expect("utf-8"), "info"])
.current_dir(workdir.path())
.assert()
.success();
assert!(
from_env.join("bito.jsonl").is_file(),
"BITO_LOG_DIR should decide the destination"
);
assert!(
!from_file.join("bito.jsonl").exists(),
"the config file's log_dir should not have been used"
);
}