use crate::Fixture;
use assert_cmd::prelude::*;
use predicates::prelude::*;
use std::fs;
use std::process::Command;
#[test]
fn help_produces_usage() {
Command::cargo_bin("enprot")
.unwrap()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("Usage:"));
Command::cargo_bin("enprot")
.unwrap()
.arg("-h")
.assert()
.success()
.stdout(predicate::str::contains("Usage:"));
}
#[test]
fn success_on_no_operation() {
let ept = Fixture::copy("sample/test.ept");
Command::cargo_bin("enprot")
.unwrap()
.arg("passthrough")
.arg(&ept.path)
.assert()
.success();
assert_eq!(
&fs::read_to_string(&ept.source).unwrap(),
&fs::read_to_string(&ept.path).unwrap()
);
}
#[test]
fn verbosity() {
let ept = Fixture::copy("sample/test.ept");
Command::cargo_bin("enprot")
.unwrap()
.arg("passthrough")
.arg(&ept.path)
.assert()
.success()
.stderr(predicate::str::contains("LEFT_SEP").not());
Command::cargo_bin("enprot")
.unwrap()
.arg("-v")
.arg("passthrough")
.arg(&ept.path)
.assert()
.success()
.stderr(predicate::str::contains("LEFT_SEP"));
Command::cargo_bin("enprot")
.unwrap()
.arg("--verbose")
.arg("passthrough")
.arg(&ept.path)
.assert()
.success()
.stderr(predicate::str::contains("LEFT_SEP"));
assert_eq!(
&fs::read_to_string(&ept.source).unwrap(),
&fs::read_to_string(&ept.path).unwrap()
);
}
#[test]
fn output() {
let ept = Fixture::copy("sample/test.ept");
let output = Fixture::blank("out.ept");
Command::cargo_bin("enprot")
.unwrap()
.arg("encrypt")
.arg("--cipher")
.arg("aes-256-siv")
.arg("-w")
.arg("Agent_007")
.arg("--pbkdf")
.arg("legacy")
.arg("-k")
.arg("Agent_007=password")
.arg(&ept.path)
.arg("-o")
.arg(&output.path)
.assert()
.success();
assert_eq!(
&fs::read_to_string(&ept.source).unwrap(),
&fs::read_to_string(&ept.path).unwrap()
);
assert_eq!(
&fs::read_to_string(&output.path).unwrap(),
&fs::read_to_string("test-data/test-encrypt-agent007.ept").unwrap()
);
}
#[test]
fn output_multiple() {
let ept1 = Fixture::copy("sample/test.ept");
let ept2 = Fixture::copy("sample/simple.ept");
let ept3 = Fixture::copy("sample/simple.ept");
let out1 = Fixture::blank("out1.ept");
let out2 = Fixture::blank("out2.ept");
Command::cargo_bin("enprot")
.unwrap()
.arg("encrypt")
.arg("--cipher")
.arg("aes-256-siv")
.arg("-w")
.arg("Agent_007")
.arg("--pbkdf")
.arg("legacy")
.arg("-k")
.arg("Agent_007=password")
.arg(&ept1.path)
.arg("-o")
.arg(&out1.path)
.arg(&ept2.path)
.arg("-o")
.arg(&out2.path)
.arg(&ept3.path)
.assert()
.success();
assert_eq!(
&fs::read_to_string(&ept1.source).unwrap(),
&fs::read_to_string(&ept1.path).unwrap()
);
assert_eq!(
&fs::read_to_string(&ept2.source).unwrap(),
&fs::read_to_string(&ept2.path).unwrap()
);
assert_eq!(
&fs::read_to_string(&out1.path).unwrap(),
&fs::read_to_string("test-data/test-encrypt-agent007.ept").unwrap()
);
assert_eq!(
&fs::read_to_string(&out2.path).unwrap(),
&fs::read_to_string("test-data/simple-encrypt-agent007.ept").unwrap()
);
assert_eq!(
&fs::read_to_string(&ept3.path).unwrap(),
&fs::read_to_string("test-data/simple-encrypt-agent007.ept").unwrap()
);
}
#[test]
fn jobs_zero_rejected() {
let ept = Fixture::copy("sample/test.ept");
Command::cargo_bin("enprot")
.unwrap()
.arg("--jobs")
.arg("0")
.arg("passthrough")
.arg(&ept.path)
.assert()
.failure()
.stderr(predicate::str::contains("must be at least 1"));
}
#[test]
fn fips_policy_conflict_typed_message() {
let ept = Fixture::copy("sample/test.ept");
Command::cargo_bin("enprot")
.unwrap()
.arg("--fips")
.arg("--policy")
.arg("default")
.arg("passthrough")
.arg(&ept.path)
.assert()
.failure()
.stderr(
predicates::str::contains("--fips forces --policy=nist")
.and(predicates::str::contains("--policy=default")),
);
}
#[test]
fn signer_without_anchor_is_warning_only() {
let ept = Fixture::copy("sample/test.ept");
let dir = tempfile::tempdir().unwrap();
let priv_pem = dir.path().join("priv.pem");
Command::cargo_bin("enprot")
.unwrap()
.args(["keygen", "ed25519", "--out-priv"])
.arg(&priv_pem)
.assert()
.success();
Command::cargo_bin("enprot")
.unwrap()
.arg("--signer")
.arg(&priv_pem)
.arg("passthrough")
.arg(&ept.path)
.assert()
.success()
.stderr(predicates::str::contains("warning:"));
}
#[test]
fn sbom_spdx_json_shape() {
use std::process::Command;
let out = Command::cargo_bin("enprot")
.unwrap()
.args(["sbom"])
.output()
.unwrap();
assert!(out.status.success());
let v: serde_json::Value = serde_json::from_slice(&out.stdout).unwrap();
assert_eq!(v["spdxVersion"], "SPDX-2.3");
assert_eq!(v["SPDXID"], "SPDXRef-DOCUMENT");
let pkgs = v["packages"].as_array().unwrap();
assert!(
pkgs.len() > 100,
"full lockfile tree expected, got {pkgs_len}",
pkgs_len = pkgs.len()
);
let names: Vec<&str> = pkgs.iter().filter_map(|p| p["name"].as_str()).collect();
assert!(names.contains(&"enprot"));
assert!(names.contains(&"botan"));
assert!(names.contains(&"librnp"));
let rels = v["relationships"].as_array().unwrap();
assert!(rels.iter().any(|r| r["relationshipType"] == "DESCRIBES"));
}
#[test]
fn sbom_cyclonedx_and_output_file() {
use std::process::Command;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("sbom.json");
let out = Command::cargo_bin("enprot")
.unwrap()
.args(["sbom", "--sbom-format", "cyclonedx-json", "--output"])
.arg(&path)
.output()
.unwrap();
assert!(out.status.success());
let v: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
assert_eq!(v["bomFormat"], "CycloneDX");
assert_eq!(v["specVersion"], "1.5");
assert_eq!(v["metadata"]["component"]["name"], "enprot");
}
#[test]
fn audit_log_records_and_queries() {
use std::process::Command;
let dir = tempfile::tempdir().unwrap();
let log = dir.path().join("audit.jsonl");
let ept = Fixture::copy("sample/test.ept");
for (word, file) in [("W1", "a.ept"), ("W2", "b.ept")] {
let _ = Command::cargo_bin("enprot")
.unwrap()
.args(["--audit-log"])
.arg(&log)
.args(["passthrough", "-w", word])
.arg(&ept.path)
.arg("-o")
.arg(dir.path().join(file))
.output()
.unwrap();
}
let text = std::fs::read_to_string(&log).unwrap();
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines.len(), 2, "{text}");
for l in &lines {
assert!(l.contains(r#""type":"record""#), "{l}");
assert!(l.contains(r#""op":"passthrough""#), "{l}");
}
assert!(lines[0].contains(r#""words":["W1"]"#), "got: {}", lines[0]);
let out = Command::cargo_bin("enprot")
.unwrap()
.args(["audit", "query", "--log"])
.arg(&log)
.args(["--word", "W2"])
.output()
.unwrap();
assert!(out.status.success());
let s = String::from_utf8_lossy(&out.stdout);
assert_eq!(s.lines().count(), 1);
assert!(s.contains(r#""words":["W2"]"#));
}
#[test]
fn audit_sign_and_verify_tamper_evidence() {
use std::process::Command;
let dir = tempfile::tempdir().unwrap();
let log = dir.path().join("audit.jsonl");
let priv_pem = dir.path().join("priv.pem");
let pub_pem = dir.path().join("pub.pem");
Command::cargo_bin("enprot")
.unwrap()
.args(["keygen", "ed25519", "--out-priv"])
.arg(&priv_pem)
.args(["--out-pub"])
.arg(&pub_pem)
.assert()
.success();
let ept = Fixture::copy("sample/simple.ept");
let _ = Command::cargo_bin("enprot")
.unwrap()
.args(["--audit-log"])
.arg(&log)
.args(["--signer"])
.arg(&priv_pem)
.args(["passthrough"])
.arg(&ept.path)
.arg("-o")
.arg(dir.path().join("out.ept"))
.output()
.unwrap();
let text = std::fs::read_to_string(&log).unwrap();
assert!(text.contains(r#""type":"signature""#));
let ok = Command::cargo_bin("enprot")
.unwrap()
.args(["audit", "verify", "--log"])
.arg(&log)
.args(["--trust-root"])
.arg(&pub_pem)
.output()
.unwrap();
assert!(
ok.status.success(),
"{}",
String::from_utf8_lossy(&ok.stderr)
);
let tampered = text.replace("passthrough", "passthru");
std::fs::write(&log, tampered).unwrap();
let bad = Command::cargo_bin("enprot")
.unwrap()
.args(["audit", "verify", "--log"])
.arg(&log)
.args(["--trust-root"])
.arg(&pub_pem)
.output()
.unwrap();
assert!(!bad.status.success(), "tampered log must not verify");
}