use std::{collections::HashSet, fs, path::PathBuf};
use serde_derive::Deserialize;
use shellfirm::checks::run_check_on_command_with_env;
#[derive(Debug, Deserialize, Clone)]
struct CheckTest {
pub test: String,
pub description: String,
pub expect_ids: Vec<String>,
}
struct AllPathsExistEnv;
impl shellfirm::env::Environment for AllPathsExistEnv {
fn var(&self, _key: &str) -> Option<String> {
None
}
fn current_dir(&self) -> shellfirm::error::Result<PathBuf> {
Ok(PathBuf::from("/mock"))
}
fn path_exists(&self, _path: &std::path::Path) -> bool {
true
}
fn home_dir(&self) -> Option<PathBuf> {
Some(PathBuf::from("/home/user"))
}
fn run_command(&self, _cmd: &str, _args: &[&str], _timeout_ms: u64) -> Option<String> {
None
}
fn read_file(&self, _path: &std::path::Path) -> shellfirm::error::Result<String> {
Err(shellfirm::error::Error::Other("not implemented".into()))
}
fn find_file_upward(&self, _start: &std::path::Path, _filename: &str) -> Option<PathBuf> {
None
}
}
#[test]
fn test_all_checks() {
let checks = shellfirm::checks::get_all().unwrap();
let env = AllPathsExistEnv;
let test_files: Vec<PathBuf> = fs::read_dir("./tests/checks")
.unwrap()
.filter_map(|entry| entry.ok().map(|e| e.path()))
.filter(|p| p.extension().map_or(false, |ext| ext == "yaml"))
.collect();
assert!(
!test_files.is_empty(),
"No test YAML files found in tests/checks/"
);
let mut total_cases = 0;
let mut total_positive = 0;
let mut total_negative = 0;
for file in &test_files {
let content = fs::read_to_string(file).unwrap();
let cases: Vec<CheckTest> = serde_yaml::from_str(&content).unwrap_or_else(|e| {
panic!("Failed to parse {}: {}", file.display(), e);
});
for case in &cases {
let matched = run_check_on_command_with_env(&checks, &case.test, &env);
let mut got_ids: Vec<String> = matched.iter().map(|c| c.id.clone()).collect();
got_ids.sort();
let mut want_ids = case.expect_ids.clone();
want_ids.sort();
assert_eq!(
got_ids,
want_ids,
"\n File: {}\n Command: {:?}\n Description: {}\n Expected: {:?}\n Got: {:?}\n",
file.display(),
case.test,
case.description,
want_ids,
got_ids,
);
total_cases += 1;
if case.expect_ids.is_empty() {
total_negative += 1;
} else {
total_positive += 1;
}
}
}
eprintln!(
" check tests: {} total ({} positive, {} negative) across {} files",
total_cases,
total_positive,
total_negative,
test_files.len()
);
}
#[test]
fn test_coverage_completeness() {
let checks = shellfirm::checks::get_all().unwrap();
let all_check_ids: HashSet<String> = checks.iter().map(|c| c.id.clone()).collect();
let mut covered_ids: HashSet<String> = HashSet::new();
let test_files: Vec<PathBuf> = fs::read_dir("./tests/checks")
.unwrap()
.filter_map(|entry| entry.ok().map(|e| e.path()))
.filter(|p| p.extension().map_or(false, |ext| ext == "yaml"))
.collect();
for file in &test_files {
let content = fs::read_to_string(file).unwrap();
let cases: Vec<CheckTest> = serde_yaml::from_str(&content).unwrap_or_else(|e| {
panic!("Failed to parse {}: {}", file.display(), e);
});
for case in cases {
for id in &case.expect_ids {
covered_ids.insert(id.clone());
}
}
}
let mut missing: Vec<&String> = all_check_ids.difference(&covered_ids).collect();
missing.sort();
assert!(
missing.is_empty(),
"Check IDs without any positive test case:\n {}\n\nAdd test cases with these IDs in expect_ids to the appropriate test file in tests/checks/",
missing
.iter()
.map(|id| id.as_str())
.collect::<Vec<_>>()
.join("\n ")
);
}