tsafe-cli 1.0.21

tsafe CLI — local secret and credential manager (replaces .env files)
Documentation
//! Integration tests for `tsafe policy` and `tsafe rotate-due`.

use assert_cmd::Command;
use predicates::str::contains;
use tempfile::tempdir;

fn tsafe() -> Command {
    Command::cargo_bin("tsafe").unwrap()
}

#[test]
fn policy_set_rejects_invalid_rotate_every() {
    let dir = tempdir().unwrap();
    tsafe()
        .args(["init"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success();

    tsafe()
        .args(["set", "K", "v"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success();

    tsafe()
        .args([
            "policy",
            "set",
            "K",
            "--rotate-every",
            "not-a-valid-duration",
        ])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .failure()
        .stderr(contains("invalid rotation interval"));
}

#[test]
fn policy_set_then_rotate_due_json_shows_no_overdue() {
    let dir = tempdir().unwrap();
    tsafe()
        .args(["init"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success();

    tsafe()
        .args(["set", "DB_PW", "secret"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success();

    tsafe()
        .args(["policy", "set", "DB_PW", "--rotate-every", "90d"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success()
        .stdout(contains("Rotation policy set"));

    tsafe()
        .args(["rotate-due", "--json"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success()
        .stdout(contains("\"overdue_count\": 0"));
}

#[test]
fn policy_remove_succeeds_after_set() {
    let dir = tempdir().unwrap();
    tsafe()
        .args(["init"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success();

    tsafe()
        .args(["set", "X", "y"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success();

    tsafe()
        .args(["policy", "set", "X", "--rotate-every", "30d"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success();

    tsafe()
        .args(["policy", "remove", "X"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .assert()
        .success()
        .stdout(contains("Rotation policy removed"));
}

#[test]
fn validate_cellos_policy_rejects_access_profile_mismatch() {
    let dir = tempdir().unwrap();
    std::fs::write(
        dir.path().join(".tsafe.yml"),
        r#"
contracts:
  readonly_cell:
    allowed_secrets: [CELL_SECRET]
    access_profile: read_only
    trust_level: standard
"#,
    )
    .unwrap();
    std::fs::write(
        dir.path().join("policy.json"),
        r#"{
  "allowedSecretRefs": ["CELL_SECRET"],
  "accessProfile": "read_write"
}"#,
    )
    .unwrap();

    tsafe()
        .args(["validate", "--cellos-policy", "policy.json"])
        .current_dir(dir.path())
        .assert()
        .failure()
        .stderr(contains("accessProfile"));
}

#[test]
fn validate_cellos_policy_rejects_required_secret_ref_mismatch() {
    let dir = tempdir().unwrap();
    std::fs::write(
        dir.path().join(".tsafe.yml"),
        r#"
contracts:
  ci_cell:
    allowed_secrets: [CELL_SECRET]
    required_secrets: [CELL_SECRET]
    trust_level: hardened
"#,
    )
    .unwrap();
    std::fs::write(
        dir.path().join("policy.json"),
        r#"{
  "allowedSecretRefs": ["CELL_SECRET"],
  "requiredSecretRefs": []
}"#,
    )
    .unwrap();

    tsafe()
        .args(["validate", "--cellos-policy", "policy.json"])
        .current_dir(dir.path())
        .assert()
        .failure()
        .stderr(contains("requiredSecretRefs"));
}

#[test]
fn validate_policy_file_alias_works_same_as_cellos_policy() {
    // --policy-file is an alias for --cellos-policy — both should work.
    let dir = tempdir().unwrap();
    std::fs::write(
        dir.path().join(".tsafe.yml"),
        r#"
contracts:
  my_cell:
    allowed_secrets: [SECRET_A]
    access_profile: read_only
    trust_level: standard
"#,
    )
    .unwrap();
    std::fs::write(
        dir.path().join("policy.json"),
        r#"{
  "allowedSecretRefs": ["SECRET_A"]
}"#,
    )
    .unwrap();

    tsafe()
        .args(["validate", "--policy-file", "policy.json"])
        .current_dir(dir.path())
        .assert()
        .success();
}

#[test]
fn validate_json_output_passes_with_matching_contract() {
    let dir = tempdir().unwrap();
    std::fs::write(
        dir.path().join(".tsafe.yml"),
        r#"
contracts:
  my_cell:
    allowed_secrets: [SECRET_A]
    access_profile: read_only
    trust_level: standard
"#,
    )
    .unwrap();
    std::fs::write(
        dir.path().join("policy.json"),
        r#"{
  "allowedSecretRefs": ["SECRET_A"]
}"#,
    )
    .unwrap();

    let output = tsafe()
        .args(["validate", "--policy-file", "policy.json", "--json"])
        .current_dir(dir.path())
        .assert()
        .success()
        .get_output()
        .stdout
        .clone();

    let json: serde_json::Value = serde_json::from_slice(&output).unwrap();
    assert_eq!(json["passed"], true);
    assert_eq!(json["conflict_fields"].as_array().unwrap().len(), 0);
}

#[test]
fn validate_json_output_fails_with_mismatch() {
    let dir = tempdir().unwrap();
    std::fs::write(
        dir.path().join(".tsafe.yml"),
        r#"
contracts:
  my_cell:
    allowed_secrets: [SECRET_A]
    access_profile: read_only
    trust_level: standard
"#,
    )
    .unwrap();
    std::fs::write(
        dir.path().join("policy.json"),
        r#"{
  "allowedSecretRefs": ["DIFFERENT_SECRET"]
}"#,
    )
    .unwrap();

    let output = tsafe()
        .args(["validate", "--policy-file", "policy.json", "--json"])
        .current_dir(dir.path())
        .assert()
        .failure()
        .get_output()
        .stdout
        .clone();

    let json: serde_json::Value = serde_json::from_slice(&output).unwrap();
    assert_eq!(json["passed"], false);
    assert!(!json["conflict_fields"].as_array().unwrap().is_empty());
}

#[test]
fn validate_cellos_policy_accepts_matching_contract_in_heterogeneous_manifest() {
    let dir = tempdir().unwrap();
    std::fs::write(
        dir.path().join(".tsafe.yml"),
        r#"
contracts:
  readonly_cell:
    allowed_secrets: [CELL_SECRET]
    required_secrets: [CELL_SECRET]
    allowed_targets: [terraform]
    access_profile: read_only
    trust_level: hardened
  writer_cell:
    allowed_secrets: [CELL_SECRET]
    required_secrets: [CELL_SECRET]
    allowed_targets: [terraform]
    access_profile: read_write
    trust_level: standard
"#,
    )
    .unwrap();
    std::fs::write(
        dir.path().join("policy.json"),
        r#"{
  "allowedSecretRefs": ["CELL_SECRET"],
  "requiredSecretRefs": ["CELL_SECRET"],
  "allowedTargets": ["terraform"],
  "accessProfile": "read_only",
  "trustLevel": "hardened"
}"#,
    )
    .unwrap();

    tsafe()
        .args(["validate", "--cellos-policy", "policy.json"])
        .current_dir(dir.path())
        .assert()
        .success();
}