use super::{classify, hashed_fields, Observability};
#[test]
fn every_hashed_field_is_classified() {
let unclassified: Vec<String> = hashed_fields()
.into_iter()
.filter(|f| classify(f).is_none())
.collect();
assert!(
unclassified.is_empty(),
"\n{} desired-state field(s) are not classified in the observability \
registry:\n\n{}\n\nEach changes hash_desired_state, so forjar will \
re-apply when it changes — but nothing decides whether the HOST can be \
asked about it. Add each to src/core/observe/mod.rs::classify as \
Observed{{alt}} (the host can be asked; `alt` is a different valid \
value the behavioural gate mutates to), or Unobservable(reason) with a \
real reason.\n",
unclassified.len(),
unclassified
.iter()
.map(|f| format!(" - {f}"))
.collect::<Vec<_>>()
.join("\n")
);
}
#[test]
fn the_reflection_actually_finds_fields() {
let fields = hashed_fields();
assert!(
fields.len() >= 8,
"reflection found only {} hashed field(s); the probe has stopped \
working and the gate above is vacuous: {fields:?}",
fields.len()
);
for required in ["source", "content", "version", "path"] {
assert!(
fields.iter().any(|f| f == required),
"`{required}` changes the desired-state hash but reflection did not \
find it — the probe cannot represent its type. found: {fields:?}"
);
}
}
#[test]
fn unobservable_must_carry_a_reason() {
for f in hashed_fields() {
if let Some(Observability::Unobservable(reason)) = classify(&f) {
assert!(
reason.len() > 20,
"`{f}` is Unobservable with no real reason: {reason:?}"
);
}
}
}
#[test]
fn every_observed_alt_actually_dirties_the_baseline() {
use crate::core::planner::hashing::hash_desired_state;
use crate::core::types::Resource;
for f in hashed_fields() {
let Some(Observability::Observed { alt }) = classify(&f) else {
continue;
};
assert!(!alt.is_empty(), "`{f}` has an empty alt value");
let base = Resource::default();
let Ok(serde_json::Value::Object(mut m)) = serde_json::to_value(&base) else {
continue;
};
m.insert(f.clone(), serde_json::json!(alt));
let scalar = serde_json::from_value::<Resource>(serde_json::Value::Object(m.clone()));
let mut ml = m.clone();
ml.insert(f.clone(), serde_json::json!([alt]));
let list = serde_json::from_value::<Resource>(serde_json::Value::Object(ml));
let moved = [scalar, list].iter().any(|r| {
r.as_ref()
.is_ok_and(|r| hash_desired_state(r) != hash_desired_state(&base))
});
assert!(
moved,
"`{f}` is Observed with alt {alt:?}, but setting the field to that \
value does not change the desired-state hash — so it cannot dirty a \
baseline and any convergence test using it would pass vacuously"
);
}
}