use super::hashing::hash_desired_state;
use crate::core::types::{MachineTarget, Resource, ResourceType};
use std::io::Write;
fn source_resource(path: &str) -> Resource {
Resource {
resource_type: ResourceType::File,
machine: MachineTarget::Single("m1".to_string()),
path: Some("/tmp/deployed-target".to_string()),
source: Some(path.to_string()),
mode: Some("0644".to_string()),
..Default::default()
}
}
fn write(path: &std::path::Path, body: &str) {
let mut f = std::fs::File::create(path).expect("create");
f.write_all(body.as_bytes()).expect("write");
f.flush().expect("flush");
}
#[test]
fn source_content_change_changes_desired_hash() {
let dir = tempfile::tempdir().expect("tempdir");
let src = dir.path().join("payload.txt");
let src_str = src.to_str().expect("utf8");
write(&src, "VERSION-ONE\n");
let h1 = hash_desired_state(&source_resource(src_str));
write(&src, "VERSION-TWO\n");
let h2 = hash_desired_state(&source_resource(src_str));
assert_ne!(
h1, h2,
"editing a source: file must change the desired-state hash; \
otherwise plan reports NoOp and apply deploys stale content while \
printing 'unchanged' (GH-206)"
);
}
#[test]
fn identical_source_content_hashes_identically() {
let dir = tempfile::tempdir().expect("tempdir");
let src = dir.path().join("payload.txt");
let src_str = src.to_str().expect("utf8");
write(&src, "STABLE\n");
let h1 = hash_desired_state(&source_resource(src_str));
let h2 = hash_desired_state(&source_resource(src_str));
assert_eq!(
h1, h2,
"hashing must stay deterministic - an unchanged source must not force \
a spurious re-apply on every run"
);
}
#[test]
fn two_sources_with_same_content_but_different_paths_differ() {
let dir = tempfile::tempdir().expect("tempdir");
let a = dir.path().join("a.txt");
let b = dir.path().join("b.txt");
write(&a, "SAME\n");
write(&b, "SAME\n");
assert_ne!(
hash_desired_state(&source_resource(a.to_str().unwrap())),
hash_desired_state(&source_resource(b.to_str().unwrap())),
"the path is still part of identity: two resources reading different \
files must not collide just because the bytes match today"
);
}
#[test]
fn missing_source_is_distinguishable_from_present_source() {
let dir = tempfile::tempdir().expect("tempdir");
let src = dir.path().join("payload.txt");
let src_str = src.to_str().expect("utf8");
let h_missing = hash_desired_state(&source_resource(src_str));
write(&src, "NOW-EXISTS\n");
let h_present = hash_desired_state(&source_resource(src_str));
assert_ne!(
h_missing, h_present,
"a source file appearing must change the hash, so a resource that was \
unappliable becomes appliable rather than staying 'unchanged'"
);
}
#[test]
fn resource_without_source_is_unaffected() {
let pkg = Resource {
resource_type: ResourceType::Package,
machine: MachineTarget::Single("m1".to_string()),
provider: Some("apt".to_string()),
packages: vec!["curl".to_string()],
..Default::default()
};
let a = hash_desired_state(&pkg);
let b = hash_desired_state(&pkg);
assert_eq!(a, b, "determinism");
let inline = Resource {
resource_type: ResourceType::File,
machine: MachineTarget::Single("m1".to_string()),
path: Some("/tmp/x".to_string()),
content: Some("hello".to_string()),
..Default::default()
};
assert_eq!(
hash_desired_state(&inline),
hash_desired_state(&inline),
"inline content resources keep their existing hash behaviour"
);
}