use std::collections::BTreeMap;
use base64ct::{Base64, Encoding};
use zeroize::Zeroizing;
use envy_secrets::core::{
read_artifact, seal_artifact, set_secret, unseal_artifact, write_artifact,
};
use envy_secrets::crypto::artifact::{
ARTIFACT_VERSION, ArtifactPayload, SyncArtifact, seal_envelope,
};
use envy_secrets::db::{ProjectId, Vault};
const TEST_KEY: [u8; 32] = [9u8; 32];
fn setup_test_vault(tmp: &tempfile::TempDir) -> (Vault, ProjectId) {
let path = tmp.path().join("vault.db");
let vault = Vault::open(&path, &TEST_KEY).expect("vault must open");
let pid = vault
.create_project("integration-test")
.expect("project must be created");
(vault, pid)
}
#[test]
fn e2e_seal_and_unseal_full_vault() {
let tmp = tempfile::tempdir().expect("tempdir must succeed");
let (vault, pid) = setup_test_vault(&tmp);
set_secret(
&vault,
&TEST_KEY,
&pid,
"development",
"STRIPE_KEY",
"sk_test_abc",
)
.expect("set_secret must succeed");
set_secret(
&vault,
&TEST_KEY,
&pid,
"development",
"DATABASE_URL",
"postgres://dev",
)
.expect("set_secret must succeed");
set_secret(
&vault,
&TEST_KEY,
&pid,
"production",
"STRIPE_KEY",
"sk_live_xyz",
)
.expect("set_secret must succeed");
let passphrase = "correct-horse-battery-staple";
let artifact = seal_artifact(
&vault,
&TEST_KEY,
&pid,
passphrase,
Some(&["development", "production"]),
)
.expect("seal_artifact must succeed");
assert_eq!(artifact.version, ARTIFACT_VERSION);
assert!(artifact.environments.contains_key("development"));
assert!(artifact.environments.contains_key("production"));
let result = unseal_artifact(&artifact, passphrase).expect("unseal_artifact must succeed");
assert!(
result.skipped.is_empty(),
"no environments must be skipped with the correct passphrase"
);
assert_eq!(
result.imported.len(),
2,
"both environments must be imported"
);
let dev = &result.imported["development"];
assert_eq!(dev["STRIPE_KEY"].as_str(), "sk_test_abc");
assert_eq!(dev["DATABASE_URL"].as_str(), "postgres://dev");
let prod = &result.imported["production"];
assert_eq!(prod["STRIPE_KEY"].as_str(), "sk_live_xyz");
}
#[test]
fn e2e_wrong_passphrase_skips_all_environments() {
let tmp = tempfile::tempdir().expect("tempdir must succeed");
let (vault, pid) = setup_test_vault(&tmp);
set_secret(&vault, &TEST_KEY, &pid, "development", "KEY", "value")
.expect("set_secret must succeed");
let artifact = seal_artifact(&vault, &TEST_KEY, &pid, "correct", Some(&["development"]))
.expect("seal_artifact must succeed");
let result = unseal_artifact(&artifact, "wrong-passphrase")
.expect("unseal_artifact must return Ok even when all envs are skipped");
assert!(
result.imported.is_empty(),
"nothing must be imported with wrong passphrase"
);
assert_eq!(
result.skipped,
vec!["development"],
"development must be in skipped list"
);
}
#[test]
fn e2e_partial_access_progressive_disclosure() {
let mut dev_secrets = BTreeMap::new();
dev_secrets.insert("API_KEY".to_string(), Zeroizing::new("dev-api".to_string()));
let dev_payload = ArtifactPayload {
secrets: dev_secrets,
};
let mut prod_secrets = BTreeMap::new();
prod_secrets.insert("DB_URL".to_string(), Zeroizing::new("prod-db".to_string()));
let prod_payload = ArtifactPayload {
secrets: prod_secrets,
};
let dev_envelope =
seal_envelope("dev-key", &dev_payload).expect("seal dev envelope must succeed");
let prod_envelope =
seal_envelope("prod-key", &prod_payload).expect("seal prod envelope must succeed");
let mut environments = BTreeMap::new();
environments.insert("development".to_string(), dev_envelope);
environments.insert("production".to_string(), prod_envelope);
let artifact = SyncArtifact {
version: ARTIFACT_VERSION,
environments,
};
let result =
unseal_artifact(&artifact, "dev-key").expect("unseal must succeed with partial access");
assert!(
result.imported.contains_key("development"),
"development must be imported"
);
assert_eq!(
result.imported["development"]["API_KEY"].as_str(),
"dev-api"
);
assert!(
result.skipped.contains(&"production".to_string()),
"production must be skipped"
);
assert_eq!(
result.imported.len(),
1,
"only development must be imported"
);
assert_eq!(result.skipped.len(), 1, "only production must be skipped");
}
#[test]
fn e2e_tampered_ciphertext_skips_environment() {
let tmp = tempfile::tempdir().expect("tempdir must succeed");
let (vault, pid) = setup_test_vault(&tmp);
set_secret(&vault, &TEST_KEY, &pid, "development", "SECRET", "value")
.expect("set_secret must succeed");
let mut artifact = seal_artifact(&vault, &TEST_KEY, &pid, "pass", Some(&["development"]))
.expect("seal_artifact must succeed");
let envelope = artifact
.environments
.get_mut("development")
.expect("env must exist");
let mut ct_bytes =
Base64::decode_vec(&envelope.ciphertext).expect("ciphertext must be valid Base64");
ct_bytes[0] ^= 0xFF;
envelope.ciphertext = Base64::encode_string(&ct_bytes);
let result = unseal_artifact(&artifact, "pass")
.expect("unseal must return Ok even for tampered envelope");
assert!(
result.imported.is_empty(),
"tampered environment must not be imported"
);
assert!(
result.skipped.contains(&"development".to_string()),
"tampered env must be in skipped"
);
}
#[test]
fn e2e_write_read_artifact_file_round_trip() {
let tmp = tempfile::tempdir().expect("tempdir must succeed");
let (vault, pid) = setup_test_vault(&tmp);
let artifact_path = tmp.path().join("envy.enc");
set_secret(&vault, &TEST_KEY, &pid, "development", "KEY_A", "val_a")
.expect("set_secret must succeed");
set_secret(&vault, &TEST_KEY, &pid, "staging", "KEY_B", "val_b")
.expect("set_secret must succeed");
let artifact = seal_artifact(
&vault,
&TEST_KEY,
&pid,
"file-round-trip-pass",
Some(&["development", "staging"]),
)
.expect("seal_artifact must succeed");
write_artifact(&artifact, &artifact_path).expect("write_artifact must succeed");
assert!(artifact_path.exists(), "envy.enc must exist after write");
let raw = std::fs::read_to_string(&artifact_path).expect("must be able to read envy.enc");
assert!(
raw.contains("\"version\""),
"JSON must contain version field"
);
assert!(
raw.contains("\"development\""),
"JSON must contain development key"
);
assert!(raw.contains("\"staging\""), "JSON must contain staging key");
assert!(
!raw.contains("KEY_A"),
"secret key names must not appear in plaintext JSON"
);
assert!(
!raw.contains("val_a"),
"secret values must not appear in plaintext JSON"
);
let recovered = read_artifact(&artifact_path).expect("read_artifact must succeed");
assert_eq!(recovered.version, ARTIFACT_VERSION);
assert!(recovered.environments.contains_key("development"));
assert!(recovered.environments.contains_key("staging"));
assert_eq!(recovered.environments.len(), 2);
}