use super::lock_audit::cmd_lock_audit;
use super::lock_core::{cmd_lock_integrity, cmd_lock_validate};
use super::lock_ops::cmd_lock_verify;
use crate::core::state::integrity;
use std::path::{Path, PathBuf};
const LOCK_YAML: &str = r#"schema: "1"
machine: web1
hostname: web1.local
generated_at: "2026-01-01T00:00:00Z"
generator: forjar 1.16.0
blake3_version: "1.5"
resources:
nginx:
type: package
status: converged
applied_at: "2026-01-01T00:00:00Z"
duration_seconds: 2.5
hash: blake3:1111111111111111111111111111111111111111111111111111111111111111
"#;
fn lock_path(state_dir: &Path) -> PathBuf {
state_dir.join("web1").join("state.lock.yaml")
}
fn sidecar_path(state_dir: &Path) -> PathBuf {
state_dir.join("web1").join("state.lock.yaml.b3")
}
fn seal_state(state_dir: &Path, body: &str) {
let lock = lock_path(state_dir);
std::fs::create_dir_all(lock.parent().unwrap()).unwrap();
std::fs::write(&lock, body).unwrap();
integrity::write_b3_sidecar(&lock).unwrap();
}
fn assert_all_reject(state_dir: &Path, scenario: &str) {
for (name, result) in [
("lock-verify", cmd_lock_verify(state_dir, false)),
("lock-integrity", cmd_lock_integrity(state_dir, false)),
("lock-validate", cmd_lock_validate(state_dir, false)),
("lock-audit", cmd_lock_audit(state_dir, false)),
] {
assert!(
result.is_err(),
"{name} returned Ok for {scenario} — a result it did not measure"
);
}
}
fn assert_all_accept(state_dir: &Path, scenario: &str) {
for (name, result) in [
("lock-verify", cmd_lock_verify(state_dir, false)),
("lock-integrity", cmd_lock_integrity(state_dir, false)),
("lock-validate", cmd_lock_validate(state_dir, false)),
("lock-audit", cmd_lock_audit(state_dir, false)),
] {
assert!(
result.is_ok(),
"{name} rejected {scenario}: {:?}",
result.err()
);
}
}
#[test]
fn sealed_lock_verifies() {
let d = tempfile::tempdir().unwrap();
seal_state(d.path(), LOCK_YAML);
assert_all_accept(d.path(), "a sealed, untampered lock");
}
#[test]
fn tampered_lock_body_is_rejected() {
let d = tempfile::tempdir().unwrap();
seal_state(d.path(), LOCK_YAML);
let tampered = LOCK_YAML.replace("blake3:1111", "blake3:2222");
assert_ne!(tampered, LOCK_YAML, "the tamper must change bytes");
std::fs::write(lock_path(d.path()), tampered).unwrap();
assert_all_reject(d.path(), "a lock body tampered after sealing");
}
#[test]
fn zeroed_resource_hash_is_rejected() {
let d = tempfile::tempdir().unwrap();
seal_state(d.path(), LOCK_YAML);
let zeroed = LOCK_YAML.replace(
"hash: blake3:1111111111111111111111111111111111111111111111111111111111111111",
r#"hash: """#,
);
assert_ne!(zeroed, LOCK_YAML, "the tamper must change bytes");
std::fs::write(lock_path(d.path()), zeroed).unwrap();
assert_all_reject(d.path(), "a lock with a zeroed resource hash");
}
#[test]
fn deleted_sidecar_is_rejected() {
let d = tempfile::tempdir().unwrap();
seal_state(d.path(), LOCK_YAML);
std::fs::remove_file(sidecar_path(d.path())).unwrap();
assert_all_reject(d.path(), "a lock whose BLAKE3 sidecar was deleted");
}
#[test]
fn deleted_lock_is_rejected() {
let d = tempfile::tempdir().unwrap();
seal_state(d.path(), LOCK_YAML);
std::fs::remove_file(lock_path(d.path())).unwrap();
assert!(
sidecar_path(d.path()).exists(),
"the orphaned sidecar must survive for this scenario to mean anything"
);
assert_all_reject(d.path(), "a deleted lock whose sidecar survives");
}
#[test]
fn mismatch_names_expected_and_actual() {
let d = tempfile::tempdir().unwrap();
seal_state(d.path(), LOCK_YAML);
std::fs::write(
lock_path(d.path()),
LOCK_YAML.replace("blake3:1111", "blake3:2222"),
)
.unwrap();
let results = integrity::verify_state_integrity(d.path());
let reasons = results
.iter()
.filter_map(integrity::failure_reason)
.collect::<Vec<_>>()
.join("\n");
assert!(
reasons.contains("BLAKE3 mismatch") && reasons.contains("sidecar says"),
"expected a mismatch naming both digests, got:\n{reasons}"
);
}
#[test]
fn apply_still_tolerates_a_missing_sidecar() {
let d = tempfile::tempdir().unwrap();
seal_state(d.path(), LOCK_YAML);
std::fs::remove_file(sidecar_path(d.path())).unwrap();
let results = integrity::verify_state_integrity(d.path());
assert!(
!integrity::has_errors(&results),
"a missing sidecar must stay a warning for apply, not a hard error"
);
assert!(
!integrity::failure_reasons(&results).is_empty(),
"…but the integrity commands must still see it as a failure"
);
}
#[test]
fn apply_refuses_a_lock_deleted_under_its_sidecar() {
let d = tempfile::tempdir().unwrap();
seal_state(d.path(), LOCK_YAML);
std::fs::remove_file(lock_path(d.path())).unwrap();
let results = integrity::verify_state_integrity(d.path());
assert!(
integrity::has_errors(&results),
"a lock deleted under a surviving sidecar must be a hard error"
);
}