tsafe-cli 1.0.28

Secrets runtime for developers — inject credentials into processes via exec, never into shell history or .env files
Documentation
//! Integration tests for `tsafe rotate-key` (HR-4).

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

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

/// Verify that after `rotate-key`, the vault opens with the new password and
/// rejects the old one.
#[test]
fn rotate_key_changes_password_and_unlocks_with_new() {
    let dir = tempdir().unwrap();

    // Init vault and store a secret.
    tsafe()
        .args(["init"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "old-master-pw")
        .assert()
        .success();

    tsafe()
        .args(["set", "REKEY_SECRET", "rekey-value"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "old-master-pw")
        .assert()
        .success();

    // rotate-key: provide both passwords via env vars (non-interactive).
    tsafe()
        .args(["rotate-key"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "old-master-pw")
        .env("TSAFE_NEW_MASTER_PASSWORD", "new-master-pw")
        .assert()
        .success()
        .stdout(contains("re-encrypted successfully"));

    // Old password must no longer work.
    tsafe()
        .args(["get", "REKEY_SECRET"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "old-master-pw")
        .assert()
        .failure()
        .stderr(contains("wrong password"));

    // New password must open the vault and retrieve the secret.
    tsafe()
        .args(["get", "REKEY_SECRET"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "new-master-pw")
        .assert()
        .success()
        .stdout("rekey-value");
}

/// rotate-key with an empty TSAFE_NEW_MASTER_PASSWORD must fail with a clear message.
#[test]
fn rotate_key_rejects_empty_new_password() {
    let dir = tempdir().unwrap();

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

    tsafe()
        .args(["rotate-key"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "pw")
        .env("TSAFE_NEW_MASTER_PASSWORD", "")
        .assert()
        .failure()
        .stderr(contains("empty"));
}

/// rotate-key preserves all secrets (round-trips all stored keys).
#[test]
fn rotate_key_preserves_all_secrets() {
    let dir = tempdir().unwrap();

    tsafe()
        .args(["init"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "before")
        .assert()
        .success();

    for (k, v) in [("ALPHA", "a"), ("BETA", "b"), ("GAMMA", "c")] {
        tsafe()
            .args(["set", k, v])
            .env("TSAFE_VAULT_DIR", dir.path())
            .env("TSAFE_PASSWORD", "before")
            .assert()
            .success();
    }

    tsafe()
        .args(["rotate-key"])
        .env("TSAFE_VAULT_DIR", dir.path())
        .env("TSAFE_PASSWORD", "before")
        .env("TSAFE_NEW_MASTER_PASSWORD", "after")
        .assert()
        .success();

    for (k, v) in [("ALPHA", "a"), ("BETA", "b"), ("GAMMA", "c")] {
        let out = tsafe()
            .args(["get", k])
            .env("TSAFE_VAULT_DIR", dir.path())
            .env("TSAFE_PASSWORD", "after")
            .assert()
            .success()
            .get_output()
            .stdout
            .clone();
        assert_eq!(
            String::from_utf8(out).unwrap().trim(),
            v,
            "key {k} should survive rotate-key"
        );
    }
}