use super::*;
use crate::core::parser::parse_config;
use crate::core::store::convergence_runner::ConvergenceResult;
use crate::core::store::coverage_persist::{append_record, load_records, TestCoverageRecord};
use crate::core::types::{CoverageLevel, ForjarConfig, MutationReport, MutationResult};
use std::collections::HashSet;
const CONFIG_YAML: &str = r#"
version: "1.0"
name: cov-stack
machines:
m:
hostname: m
addr: 127.0.0.1
resources:
pkg:
type: file
machine: m
path: /etc/forjar/pkg.conf
content: "original"
"#;
fn parse() -> ForjarConfig {
parse_config(CONFIG_YAML).expect("config parses")
}
fn current_hash(config: &ForjarConfig, rid: &str) -> String {
let r = config.resources.get(rid).expect("resource exists");
let resolved =
crate::core::resolver::resolve_resource_templates(r, &config.params, &config.machines)
.unwrap_or_else(|_| r.clone());
crate::core::planner::hash_desired_state(&resolved)
}
fn entry_for(config: &ForjarConfig, state_dir: &std::path::Path) -> CoverageLevel {
let records = load_records(state_dir);
let spec: HashSet<String> = HashSet::new();
let r = config.resources.get("pkg").unwrap();
coverage_entry("pkg", r, config, &spec, &records).level
}
#[test]
fn falsify_a_fresh_resource_is_static_level() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let level = entry_for(&config, dir.path());
assert_eq!(level, CoverageLevel::L1, "fresh resource must be static L1");
assert!(level < CoverageLevel::L3, "fresh resource is never L3+");
}
#[test]
fn falsify_b_passing_l3_record_promotes() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let hash = current_hash(&config, "pkg");
append_record(
dir.path(),
&TestCoverageRecord::new("pkg", CoverageLevel::L3, true, hash),
)
.unwrap();
assert_eq!(entry_for(&config, dir.path()), CoverageLevel::L3);
}
#[test]
fn falsify_c_config_change_demotes() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
append_record(
dir.path(),
&TestCoverageRecord::new("pkg", CoverageLevel::L5, true, "stale-hash"),
)
.unwrap();
let level = entry_for(&config, dir.path());
assert_eq!(
level,
CoverageLevel::L1,
"stale high-water mark must not survive a config change"
);
}
#[test]
fn falsify_d_l5_record_implies_l3_l4() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let hash = current_hash(&config, "pkg");
append_record(
dir.path(),
&TestCoverageRecord::new("pkg", CoverageLevel::L5, true, hash),
)
.unwrap();
let level = entry_for(&config, dir.path());
assert_eq!(level, CoverageLevel::L5);
assert!(level >= CoverageLevel::L3, "L5 implies L3");
assert!(level >= CoverageLevel::L4, "L5 implies L4");
}
fn conv_result(rid: &str, converged: bool, idempotent: bool, preserved: bool) -> ConvergenceResult {
ConvergenceResult {
resource_id: rid.into(),
resource_type: "file".into(),
converged,
idempotent,
preserved,
duration_ms: 1,
error: None,
}
}
#[test]
fn persist_convergence_writes_l3_on_pass() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let file = dir.path().join("forjar.yaml");
let results = vec![conv_result("pkg", true, true, true)];
persist_convergence_coverage(&file, &config, &results, false);
let records = load_records(&coverage_state_dir(&file));
assert_eq!(records.len(), 1);
assert_eq!(records[0].level, CoverageLevel::L3);
assert_eq!(records[0].config_hash, current_hash(&config, "pkg"));
}
#[test]
fn persist_convergence_writes_l5_when_pairwise() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let file = dir.path().join("forjar.yaml");
let results = vec![conv_result("pkg", true, true, true)];
persist_convergence_coverage(&file, &config, &results, true);
let records = load_records(&coverage_state_dir(&file));
assert_eq!(records[0].level, CoverageLevel::L5);
}
#[test]
fn persist_convergence_writes_failing_record_on_failure() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let file = dir.path().join("forjar.yaml");
let results = vec![conv_result("pkg", false, true, false)];
persist_convergence_coverage(&file, &config, &results, false);
let records = load_records(&coverage_state_dir(&file));
assert_eq!(records.len(), 1, "a failure must record a demotion");
assert_eq!(records[0].level, CoverageLevel::L3);
assert!(!records[0].passed);
}
fn mut_result(rid: &str, detected: bool) -> MutationResult {
use crate::core::types::MutationOperator;
MutationResult {
resource_id: rid.into(),
resource_type: "file".into(),
operator: MutationOperator::DeleteFile,
detected,
reconverged: None,
duration_ms: 1,
error: None,
}
}
#[test]
fn persist_mutation_writes_l4_when_all_detected() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let file = dir.path().join("forjar.yaml");
let report =
MutationReport::from_results(vec![mut_result("pkg", true), mut_result("pkg", true)]);
persist_mutation_coverage(&file, &config, &report);
let records = load_records(&coverage_state_dir(&file));
assert_eq!(records.len(), 1);
assert_eq!(records[0].level, CoverageLevel::L4);
}
#[test]
fn persist_mutation_writes_failing_record_with_survivor() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let file = dir.path().join("forjar.yaml");
let report =
MutationReport::from_results(vec![mut_result("pkg", true), mut_result("pkg", false)]);
persist_mutation_coverage(&file, &config, &report);
let records = load_records(&coverage_state_dir(&file));
assert_eq!(records.len(), 1, "a survivor must record a demotion");
assert_eq!(records[0].level, CoverageLevel::L4);
assert!(!records[0].passed);
}
#[test]
fn end_to_end_convergence_then_coverage_promotes() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let file = dir.path().join("forjar.yaml");
let results = vec![conv_result("pkg", true, true, true)];
persist_convergence_coverage(&file, &config, &results, false);
let records = load_records(&coverage_state_dir(&file));
let spec: HashSet<String> = HashSet::new();
let r = config.resources.get("pkg").unwrap();
let level = coverage_entry("pkg", r, &config, &spec, &records).level;
assert_eq!(level, CoverageLevel::L3);
}
#[test]
fn end_to_end_regression_at_same_hash_demotes() {
let dir = tempfile::tempdir().unwrap();
let config = parse();
let file = dir.path().join("forjar.yaml");
persist_convergence_coverage(
&file,
&config,
&[conv_result("pkg", true, true, true)],
false,
);
persist_convergence_coverage(
&file,
&config,
&[conv_result("pkg", false, true, false)],
false,
);
let records = load_records(&coverage_state_dir(&file));
let spec: HashSet<String> = HashSet::new();
let r = config.resources.get("pkg").unwrap();
let level = coverage_entry("pkg", r, &config, &spec, &records).level;
assert!(
level < CoverageLevel::L3,
"a regression at an unchanged config_hash must demote below L3, got {level:?}"
);
}