use super::tests_full::{make_service_resource, make_test_machine};
use super::*;
use crate::core::types::Machine;
use crate::tripwire::hasher;
#[test]
fn test_fj016_detect_drift_full_file_plus_service() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("mixed.txt");
std::fs::write(&file, "stable").unwrap();
let file_hash = hasher::hash_file(&file).unwrap();
let mut config_resources = indexmap::IndexMap::new();
config_resources.insert("my-svc".to_string(), make_service_resource(Some("nginx")));
let machine = make_test_machine();
let query =
crate::core::codegen::state_query_script(config_resources.get("my-svc").unwrap()).unwrap();
let output = crate::transport::exec_script(&machine, &query).unwrap();
let svc_live_hash = hasher::hash_string_or_sentinel(&output.stdout);
let mut lock_resources = indexmap::IndexMap::new();
let mut file_details = std::collections::HashMap::new();
file_details.insert(
"path".to_string(),
serde_yaml_ng::Value::String(file.to_str().unwrap().to_string()),
);
file_details.insert(
"content_hash".to_string(),
serde_yaml_ng::Value::String(file_hash),
);
lock_resources.insert(
"my-file".to_string(),
crate::core::types::ResourceLock {
resource_type: ResourceType::File,
status: ResourceStatus::Converged,
applied_at: None,
duration_seconds: None,
hash: "blake3:desired".to_string(),
observed: None,
details: file_details,
},
);
let mut svc_details = std::collections::HashMap::new();
svc_details.insert(
"live_hash".to_string(),
serde_yaml_ng::Value::String(svc_live_hash),
);
lock_resources.insert(
"my-svc".to_string(),
crate::core::types::ResourceLock {
resource_type: ResourceType::Service,
status: ResourceStatus::Converged,
applied_at: None,
duration_seconds: None,
hash: "blake3:desired".to_string(),
observed: None,
details: svc_details,
},
);
let lock = StateLock {
schema: "1.0".to_string(),
machine: "test".to_string(),
hostname: "test".to_string(),
generated_at: "now".to_string(),
generator: "test".to_string(),
blake3_version: "1.8".to_string(),
resources: lock_resources,
};
let findings = detect_drift_full(&lock, &machine, &config_resources);
assert!(
findings.is_empty(),
"no drift expected when both file and service hashes match"
);
}
#[test]
fn file_drift_consults_the_machine_not_the_controller() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("probe.txt");
std::fs::write(&path, b"CONTROLLER-ONLY\n").unwrap();
let expected = crate::tripwire::hasher::hash_file(&path).expect("hash");
let mut rl = crate::core::types::ResourceLock {
resource_type: ResourceType::File,
status: ResourceStatus::Converged,
applied_at: None,
duration_seconds: None,
hash: "blake3:whatever".to_string(),
observed: None,
details: std::collections::HashMap::new(),
};
rl.details.insert(
"path".to_string(),
serde_yaml_ng::Value::String(path.display().to_string()),
);
rl.details.insert(
"content_hash".to_string(),
serde_yaml_ng::Value::String(expected.clone()),
);
let machine = Machine {
hostname: "nowhere.invalid".to_string(),
addr: "nowhere.invalid".to_string(),
user: "root".to_string(),
arch: "x86_64".to_string(),
ssh_key: None,
roles: vec![],
transport: None,
container: None,
pepita: None,
cost: 0,
allowed_operators: vec![],
};
let (path_str, expected_hash) =
super::file::locked_file_target(&rl).expect("the lock entry carries path + content_hash");
let finding =
super::file::check_file_resource_drift("f", path_str, expected_hash, Some(&machine));
assert!(
finding.is_some(),
"drift reported CLEAN for an unreachable machine — it hashed the \
controller's copy of the file and called that the machine's state"
);
assert!(
super::file::check_file_resource_drift("f", path_str, expected_hash, None).is_none(),
"with no machine known, a file matching its content_hash must be clean"
);
}