use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use tempfile::TempDir;
const FIXTURES_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures");
fn fixture_path(name: &str) -> PathBuf {
Path::new(FIXTURES_DIR).join(name)
}
fn sbom_tools_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_sbom-tools"))
}
fn base_command() -> Command {
let mut cmd = Command::new(sbom_tools_bin());
cmd.arg("--no-color");
cmd.env("RUST_LOG", "error");
cmd.env("RUST_LOG_STYLE", "never");
cmd
}
fn stdout(output: &Output) -> String {
String::from_utf8(output.stdout.clone()).expect("stdout should be utf-8")
}
fn stderr(output: &Output) -> String {
String::from_utf8(output.stderr.clone()).expect("stderr should be utf-8")
}
fn write_config(contents: &str) -> (TempDir, PathBuf) {
let dir = TempDir::new().expect("temp dir");
let path = dir.path().join(".sbom-tools.yaml");
std::fs::write(&path, contents).expect("write config");
(dir, path)
}
#[test]
fn config_file_output_format_takes_effect_on_diff() {
let (_dir, cfg) = write_config("output:\n format: Json\n");
let output = base_command()
.args(["--config", cfg.to_str().unwrap(), "diff"])
.arg(fixture_path("demo-old.cdx.json"))
.arg(fixture_path("demo-new.cdx.json"))
.output()
.expect("diff should run");
assert!(output.status.success(), "{}", stderr(&output));
let text = stdout(&output);
assert!(
text.trim_start().starts_with('{'),
"config output.format=Json should produce JSON, got:\n{text}"
);
}
#[test]
fn explicit_cli_flag_overrides_config_file_value() {
let (_dir, cfg) = write_config("output:\n format: Json\n");
let output = base_command()
.args(["--config", cfg.to_str().unwrap(), "diff"])
.arg(fixture_path("demo-old.cdx.json"))
.arg(fixture_path("demo-new.cdx.json"))
.args(["-o", "summary"])
.output()
.expect("diff should run");
assert!(output.status.success(), "{}", stderr(&output));
let text = stdout(&output);
assert!(
text.contains("SBOM Diff Summary"),
"explicit -o summary should override config Json, got:\n{text}"
);
assert!(
!text.trim_start().starts_with('{'),
"output should not be JSON when -o summary is passed"
);
}
#[test]
fn config_file_fail_on_change_changes_exit_code() {
let (_dir, cfg) = write_config("behavior:\n fail_on_change: true\n");
let output = base_command()
.args(["--config", cfg.to_str().unwrap(), "diff"])
.arg(fixture_path("demo-old.cdx.json"))
.arg(fixture_path("demo-new.cdx.json"))
.args(["-o", "summary"])
.output()
.expect("diff should run");
assert_eq!(
output.status.code(),
Some(1),
"config fail_on_change should yield exit 1; stderr:\n{}",
stderr(&output)
);
}
#[test]
fn no_config_ignores_discovered_file() {
let (dir, cfg) = write_config("behavior:\n fail_on_change: true\n");
let output = base_command()
.arg("--no-config")
.arg("diff")
.arg(fixture_path("demo-old.cdx.json"))
.arg(fixture_path("demo-new.cdx.json"))
.args(["-o", "summary"])
.current_dir(dir.path())
.output()
.expect("diff should run");
let _ = &cfg;
assert_eq!(
output.status.code(),
Some(0),
"--no-config should ignore the .sbom-tools.yaml in cwd; stderr:\n{}",
stderr(&output)
);
}
#[test]
fn discovered_config_in_cwd_applies_without_explicit_flag() {
let (dir, cfg) = write_config("behavior:\n fail_on_change: true\n");
let output = base_command()
.arg("diff")
.arg(fixture_path("demo-old.cdx.json"))
.arg(fixture_path("demo-new.cdx.json"))
.args(["-o", "summary"])
.current_dir(dir.path())
.output()
.expect("diff should run");
let _ = &cfg;
assert_eq!(
output.status.code(),
Some(1),
"discovered config fail_on_change should yield exit 1; stderr:\n{}",
stderr(&output)
);
}
#[test]
fn config_check_prints_and_validates_effective_config() {
let (_dir, cfg) = write_config("matching:\n fuzzy_preset: strict\n");
let output = base_command()
.args(["--config", cfg.to_str().unwrap(), "config", "check"])
.output()
.expect("config check should run");
assert!(output.status.success(), "{}", stderr(&output));
assert!(
stderr(&output).contains("Valid."),
"config check should report validity; stderr:\n{}",
stderr(&output)
);
let text = stdout(&output);
assert!(
text.contains("fuzzy_preset: strict"),
"config check should print the merged config; got:\n{text}"
);
}
#[test]
fn config_check_rejects_invalid_config() {
let (_dir, cfg) = write_config("matching:\n threshold: 5.0\n");
let output = base_command()
.args(["--config", cfg.to_str().unwrap(), "config", "check"])
.output()
.expect("config check should run");
assert!(
!output.status.success(),
"invalid config should fail config check"
);
assert!(
stderr(&output).contains("matching.threshold"),
"error should name the offending field; stderr:\n{}",
stderr(&output)
);
}
#[test]
fn explicit_missing_config_path_is_an_error() {
let output = base_command()
.args([
"--config",
"/nonexistent/sbom-tools.yaml",
"config",
"check",
])
.output()
.expect("command should run");
assert!(
!output.status.success(),
"a missing explicit --config path should be a hard error"
);
assert!(
stderr(&output).contains("not found"),
"stderr should explain the missing file; got:\n{}",
stderr(&output)
);
}