use std::path::Path;
use std::process::Command;
use plecto_host::DevSigner;
use plecto_host::test_support::{filter_hello_component, filter_v04_component};
fn run(args: &[&str], dir: &Path) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_plecto"))
.args(args)
.current_dir(dir)
.output()
.unwrap()
}
fn write_key_pair(dir: &Path) {
let (signer, private_pem) = DevSigner::generate().unwrap();
std::fs::write(dir.join("key.pem"), private_pem.as_bytes()).unwrap();
std::fs::write(dir.join("trust.pem"), signer.public_key_pem()).unwrap();
}
fn write_component(dir: &Path) {
std::fs::write(dir.join("filter.wasm"), filter_hello_component()).unwrap();
}
fn write_signed_layout(dir: &Path, component: Vec<u8>) -> String {
let (signer, _private_pem) = DevSigner::generate().unwrap();
std::fs::write(dir.join("trust.pem"), signer.public_key_pem()).unwrap();
let sbom = plecto_control::bound_sbom(&component);
let component_signature = signer.sign(&component).unwrap();
let sbom_signature = signer.sign(&sbom).unwrap();
plecto_control::oci::write_layout(
&dir.join("layout"),
&plecto_control::ResolvedArtifact {
component,
component_signature,
sbom,
sbom_signature,
},
)
.unwrap()
}
fn manifest_pinning(digest: &str) -> String {
format!(
r#"[trust]
keys = ["trust.pem"]
[[filter]]
id = "hello"
source = "layout"
digest = "{digest}"
isolation = "trusted"
[[upstream]]
name = "app"
addresses = ["127.0.0.1:9000"]
[upstream.health]
path = "/healthz"
[[route]]
filters = ["hello"]
upstream = "app"
[route.match]
path_prefix = "/"
"#
)
}
fn package(dir: &Path) -> std::process::Output {
run(
&[
"package",
"filter.wasm",
"--key",
"key.pem",
"--out",
"layout",
],
dir,
)
}
#[test]
fn package_prints_only_the_pinned_digest_and_the_layout_passes_resolve() {
let dir = tempfile::tempdir().unwrap();
write_key_pair(dir.path());
write_component(dir.path());
let out = package(dir.path());
assert!(
out.status.success(),
"package exits 0, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
let digest = stdout.trim_end();
assert!(
digest.starts_with("sha256:") && digest.len() == "sha256:".len() + 64,
"stdout is exactly one sha256:<hex> digest, got: {stdout:?}"
);
assert_eq!(
stdout.lines().count(),
1,
"no extra stdout lines: {stdout:?}"
);
std::fs::write(dir.path().join("plecto.toml"), manifest_pinning(digest)).unwrap();
let resolve = run(&["validate", "--resolve", "plecto.toml"], dir.path());
assert!(
resolve.status.success(),
"validate --resolve accepts the packaged layout, stderr: {}",
String::from_utf8_lossy(&resolve.stderr)
);
}
const EMPTY_COMPONENT: &[u8] = &[0x00, 0x61, 0x73, 0x6d, 0x0d, 0x00, 0x01, 0x00];
#[test]
fn validate_resolve_rejects_a_component_no_shipped_contract_accepts() {
let dir = tempfile::tempdir().unwrap();
let digest = write_signed_layout(dir.path(), EMPTY_COMPONENT.to_vec());
std::fs::write(dir.path().join("plecto.toml"), manifest_pinning(&digest)).unwrap();
let out = run(&["validate", "--resolve", "plecto.toml"], dir.path());
assert!(
!out.status.success(),
"a component no shipped world accepts fails the pre-flight, stdout: {}",
String::from_utf8_lossy(&out.stdout)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("component satisfies no shipped plecto:filter contract"),
"the rejection names the contract gate: {stderr}"
);
for version in ["plecto:filter@0.1.0", "@0.2.0", "@0.3.0", "@0.4.0"] {
assert!(
stderr.contains(version),
"every shipped contract was tried, {version} missing: {stderr}"
);
}
}
#[test]
fn validate_resolve_reports_the_contract_a_green_layout_satisfies() {
let dir = tempfile::tempdir().unwrap();
write_key_pair(dir.path());
std::fs::write(dir.path().join("filter.wasm"), filter_v04_component()).unwrap();
let out = package(dir.path());
assert!(
out.status.success(),
"package exits 0, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
std::fs::write(
dir.path().join("plecto.toml"),
manifest_pinning(stdout.trim_end()),
)
.unwrap();
let resolve = run(&["validate", "--resolve", "plecto.toml"], dir.path());
assert!(
resolve.status.success(),
"a conformant layout still passes, stderr: {}",
String::from_utf8_lossy(&resolve.stderr)
);
let stdout = String::from_utf8_lossy(&resolve.stdout);
assert!(
stdout.contains("filter hello OK: artifact verified"),
"the provenance line is unchanged: {stdout}"
);
assert!(
stdout.contains(
"filter hello contract OK: satisfies plecto:filter@0.4.0 (static check — a pass is \
not a load guarantee; the real load remains authoritative)"
),
"the contract line names the satisfied world and claims nothing more: {stdout}"
);
}
#[test]
fn package_gates_on_conformance_and_writes_nothing_for_junk() {
let dir = tempfile::tempdir().unwrap();
write_key_pair(dir.path());
std::fs::write(dir.path().join("filter.wasm"), b"not a component").unwrap();
let out = package(dir.path());
assert!(!out.status.success(), "junk input is rejected");
assert!(
out.stdout.is_empty(),
"no digest on stdout for a rejected component: {:?}",
String::from_utf8_lossy(&out.stdout)
);
assert!(
!dir.path().join("layout").exists(),
"a non-conformant component never produces a layout"
);
}
#[test]
fn conformance_json_scores_every_case_with_an_id_and_a_verdict() {
let dir = tempfile::tempdir().unwrap();
write_component(dir.path());
let out = run(&["conformance", "filter.wasm", "--json"], dir.path());
assert!(
out.status.success(),
"the fixture filter is conformant, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let report: serde_json::Value = serde_json::from_slice(&out.stdout).expect("stdout is JSON");
assert_eq!(report["conformant"], serde_json::json!(true));
let checks = report["checks"].as_array().expect("the report lists cases");
assert!(
!checks.is_empty(),
"the battery reports its cases: {report}"
);
for check in checks {
for key in ["id", "name", "passed", "verdict", "detail"] {
assert!(
check.get(key).is_some(),
"every case carries `{key}`: {check}"
);
}
assert_eq!(check["verdict"], serde_json::json!("pass"), "{check}");
assert_eq!(
check["passed"],
serde_json::json!(true),
"`passed` stays the `verdict == pass` projection: {check}"
);
}
let ids: Vec<&str> = checks.iter().filter_map(|c| c["id"].as_str()).collect();
assert_eq!(
ids,
["v1.0.0-S1", "v1.0.0-D1"],
"the ids are the battery's stable case names"
);
}
#[test]
fn conformance_reports_the_verdict_of_a_component_that_does_not_load() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("filter.wasm"), b"not a component").unwrap();
let json = run(&["conformance", "filter.wasm", "--json"], dir.path());
assert!(
!json.status.success(),
"a non-conformant component exits non-zero"
);
let report: serde_json::Value = serde_json::from_slice(&json.stdout).expect("stdout is JSON");
assert_eq!(report["conformant"], serde_json::json!(false));
assert_eq!(report["checks"][0]["verdict"], serde_json::json!("fail"));
assert_eq!(report["checks"][0]["passed"], serde_json::json!(false));
let text = run(&["conformance", "filter.wasm"], dir.path());
let stdout = String::from_utf8_lossy(&text.stdout);
let markers: Vec<&str> = stdout.lines().filter(|l| l.starts_with('[')).collect();
assert_eq!(markers.len(), 2, "one marker per case: {stdout:?}");
assert!(
markers.iter().all(|line| line.starts_with("[fail]")),
"the text arm prints the verdict, not a bool-derived word: {stdout:?}"
);
}
#[test]
fn package_accepts_a_supplied_bound_sbom() {
let dir = tempfile::tempdir().unwrap();
write_key_pair(dir.path());
write_component(dir.path());
let component = filter_hello_component();
let default_statement = plecto_host::bound_sbom(&component);
let mut statement: serde_json::Value = serde_json::from_slice(&default_statement).unwrap();
statement["predicate"] = serde_json::json!({"supplier": "moka-1"});
std::fs::write(
dir.path().join("sbom.json"),
serde_json::to_vec(&statement).unwrap(),
)
.unwrap();
let out = run(
&[
"package",
"filter.wasm",
"--key",
"key.pem",
"--out",
"layout",
"--sbom",
"sbom.json",
],
dir.path(),
);
assert!(
out.status.success(),
"package --sbom exits 0, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let digest = String::from_utf8_lossy(&out.stdout).trim_end().to_string();
std::fs::write(dir.path().join("plecto.toml"), manifest_pinning(&digest)).unwrap();
let resolve = run(&["validate", "--resolve", "plecto.toml"], dir.path());
assert!(
resolve.status.success(),
"resolve accepts the supplier SBOM, stderr: {}",
String::from_utf8_lossy(&resolve.stderr)
);
}
#[test]
fn resolve_fails_closed_on_an_untrusted_signature() {
let dir = tempfile::tempdir().unwrap();
write_key_pair(dir.path());
write_component(dir.path());
let out = package(dir.path());
assert!(out.status.success());
let digest = String::from_utf8_lossy(&out.stdout).trim_end().to_string();
let (stranger, _) = DevSigner::generate().unwrap();
std::fs::write(dir.path().join("trust.pem"), stranger.public_key_pem()).unwrap();
std::fs::write(dir.path().join("plecto.toml"), manifest_pinning(&digest)).unwrap();
let stat = run(&["validate", "plecto.toml"], dir.path());
assert!(
stat.status.success(),
"static validate does not resolve artifacts"
);
let resolve = run(&["validate", "--resolve", "plecto.toml"], dir.path());
assert!(
!resolve.status.success(),
"--resolve rejects a layout no trusted key signed"
);
}
#[test]
fn resolve_fails_closed_on_a_tampered_blob() {
let dir = tempfile::tempdir().unwrap();
write_key_pair(dir.path());
write_component(dir.path());
let out = package(dir.path());
assert!(out.status.success());
let digest = String::from_utf8_lossy(&out.stdout).trim_end().to_string();
std::fs::write(dir.path().join("plecto.toml"), manifest_pinning(&digest)).unwrap();
let blobs = dir.path().join("layout/blobs/sha256");
let component_blob = std::fs::read_dir(&blobs)
.unwrap()
.map(|e| e.unwrap().path())
.max_by_key(|p| std::fs::metadata(p).unwrap().len())
.unwrap();
let mut bytes = std::fs::read(&component_blob).unwrap();
bytes[0] ^= 0xFF;
std::fs::write(&component_blob, bytes).unwrap();
let resolve = run(&["validate", "--resolve", "plecto.toml"], dir.path());
assert!(
!resolve.status.success(),
"--resolve rejects a blob whose content diverges from its digest"
);
}