use std::fs; use lazaret_linux::filesystem::try_exclusive_flock; #[allow(clippy::disallowed_methods, clippy::disallowed_types)] #[test] fn machine_and_human_discovery_are_versioned_and_content_free() { let fixture = Fixture::new(); let machine = fixture.command("list").arg("--json").output().unwrap(); assert!( machine.status.success(), "{}", String::from_utf8_lossy(&machine.stderr) ); let document: serde_json::Value = serde_json::from_slice(&machine.stdout).unwrap(); assert_eq!(document["schema"], "lazaret-state-output-v1"); assert_eq!(document["contents_included"], false); assert_eq!(document["anchors"][0]["container_path"], "/usr"); let human = fixture.command("inspect").output().unwrap(); assert!( human.status.success(), "{}", String::from_utf8_lossy(&human.stderr) ); let output = String::from_utf8(human.stdout).unwrap(); assert!(output.contains("State Anchor /usr")); assert!(output.contains("Persisted file contents were not read or printed.")); } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] #[test] fn reset_refuses_an_arbitrary_container_or_host_path_before_state_access() { let fixture = Fixture::new(); let output = fixture .command("reset") .args(["--path", "/tmp/host-upper", "--yes"]) .output() .unwrap(); assert!(!output.status.success()); assert!( String::from_utf8_lossy(&output.stderr) .contains("not an exact configured persistent container path") ); } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] #[test] fn active_domain_reports_actionable_maintenance_contention() { let fixture = Fixture::new(); assert!(fixture.command("list").output().unwrap().status.success()); let lock = try_exclusive_flock( fs::OpenOptions::new() .read(true) .write(true) .open(fixture.domain_root().join("domain.lock")) .unwrap(), ) .unwrap(); let active = fixture.command("verify").output().unwrap(); assert!(!active.status.success()); assert!(String::from_utf8_lossy(&active.stderr).contains( "Domain state is busy; exit active Lazaret sessions or wait for another maintenance command to finish, then retry" )); drop(lock); let inactive = fixture.command("verify").output().unwrap(); assert!( inactive.status.success(), "{}", String::from_utf8_lossy(&inactive.stderr) ); } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] #[test] fn missing_store_succeeds_only_when_the_complete_state_anchor_is_absent() { let fixture = Fixture::new(); for operation in ["list", "inspect", "verify", "reset"] { let mut command = fixture.command(operation); if operation == "reset" { command.args(["--all", "--yes"]); } let absent = command.arg("--json").output().unwrap(); assert!( absent.status.success(), "{operation}: {}", String::from_utf8_lossy(&absent.stderr) ); } let domain = fixture.domain_root(); assert!(!domain.join("domain.redb").exists()); fs::create_dir_all(domain.join("state-anchor/reset-quarantine")).unwrap(); fs::create_dir_all(domain.join("state-anchor/upper/usr")).unwrap(); fs::write(domain.join("state-anchor/upper/usr/value"), b"persisted").unwrap(); for operation in ["list", "inspect", "verify", "reset"] { let mut command = fixture.command(operation); if operation == "reset" { command.args(["--all", "--yes"]); } let output = command.output().unwrap(); assert!( !output.status.success(), "{operation} accepted orphan state" ); assert!( String::from_utf8_lossy(&output.stderr) .contains("State Anchor exists without Domain Store authority") ); assert_eq!( fs::read(domain.join("state-anchor/upper/usr/value")).unwrap(), b"persisted" ); assert!(domain.join("state-anchor/reset-quarantine").is_dir()); assert!(!domain.join("domain.redb").exists()); } } struct Fixture { root: tempfile::TempDir, } impl Fixture { fn new() -> Self { let root = tempfile::tempdir().unwrap(); for name in ["workspace", "runtime", "home"] { fs::create_dir(root.path().join(name)).unwrap(); } fs::write( root.path().join("config.toml"), format!( "schema = \"lazaret-trusted-host-config-v1\"\n[storage]\ndata_root = \"{}\"\n[network]\ndefault_allow = [\"192.0.2.10/32\"]\n[profiles.default]\nimage = \"example.invalid/state:v1\"\npersistent_dirs = [\"/usr\"] ", root.path().join("data").display() ), ) .unwrap(); Self { root } } #[allow(clippy::disallowed_types)] fn command(&self, operation: &str) -> std::process::Command { let mut command = std::process::Command::new(env!("CARGO_BIN_EXE_lazaret")); command .arg("state") .arg(operation) .args(["--profile", "default", "--workspace"]) .arg(self.root.path().join("workspace")) .arg("--config") .arg(self.root.path().join("config.toml")) .env("HOME", self.root.path().join("home")) .env("XDG_RUNTIME_DIR", self.root.path().join("runtime")); command } fn domain_root(&self) -> std::path::PathBuf { let mut domains = fs::read_dir(self.root.path().join("data/domains")).unwrap(); let domain = domains.next().unwrap().unwrap().path(); assert!(domains.next().is_none()); domain } }