133 lines
4.9 KiB
Rust
133 lines
4.9 KiB
Rust
use std::fs;
|
|
|
|
#[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 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\"]\n[profiles.default.readiness]\ndenied = [{{ address = \"10.20.30.40\", port = 8443 }}]\npermitted = [{{ address = \"192.0.2.10\", port = 443 }}]\ndns = [{{ resolver = \"198.51.100.53\", query = \"example.invalid\" }}]\n",
|
|
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
|
|
}
|
|
}
|