use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::tempdir;
fn tsafe() -> Command {
Command::cargo_bin("tsafe").unwrap()
}
fn audit_log_path(dir: &std::path::Path) -> std::path::PathBuf {
dir.parent()
.unwrap()
.join("state")
.join("audit")
.join("default.audit.jsonl")
}
fn audit_log_contents(dir: &std::path::Path) -> String {
std::fs::read_to_string(audit_log_path(dir)).unwrap()
}
fn init_vault_with_key(dir: &std::path::Path, key: &str, val: &str) {
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir)
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
tsafe()
.args(["--profile", "default", "set", key, val])
.env("TSAFE_VAULT_DIR", dir)
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_share_prints_one_time_url() {
let dir = tempdir().unwrap();
init_vault_with_key(dir.path(), "SHARE_KEY", "s3cr3t-value");
let mut server = mockito::Server::new();
let one_time_url = format!("{}/s/abc123token", server.url());
let _m = server
.mock("POST", "/create")
.with_status(200)
.with_header("Content-Type", "application/json")
.with_body(format!(r#"{{"url":"{one_time_url}"}}"#))
.create();
tsafe()
.args(["--profile", "default", "share-once", "SHARE_KEY"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_BASE_URL", server.url())
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("abc123token"));
_m.assert(); }
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_share_appends_audit_entry() {
let dir = tempdir().unwrap();
init_vault_with_key(dir.path(), "SHARE_KEY", "s3cr3t-value");
let mut server = mockito::Server::new();
let _m = server
.mock("POST", "/create")
.with_status(200)
.with_header("Content-Type", "application/json")
.with_body(r#"{"url":"https://ots.example.com/s/abc123token"}"#)
.create();
tsafe()
.args(["--profile", "default", "share-once", "SHARE_KEY"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_BASE_URL", server.url())
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success();
let audit = audit_log_contents(dir.path());
assert!(audit.contains("\"operation\":\"share-once\""));
assert!(audit.contains("\"key\":\"SHARE_KEY\""));
_m.assert();
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_share_custom_create_path() {
let dir = tempdir().unwrap();
init_vault_with_key(dir.path(), "MYKEY", "myval");
let mut server = mockito::Server::new();
let _m = server
.mock("POST", "/api/v1/secrets")
.with_status(200)
.with_header("Content-Type", "application/json")
.with_body(r#"{"url":"https://ots.example.com/s/xyz"}"#)
.create();
tsafe()
.args(["--profile", "default", "share-once", "MYKEY"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_BASE_URL", server.url())
.env("TSAFE_OTS_CREATE_PATH", "/api/v1/secrets")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("ots.example.com/s/xyz"));
_m.assert();
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_share_missing_base_url_fails() {
let dir = tempdir().unwrap();
init_vault_with_key(dir.path(), "KEY", "val");
tsafe()
.args(["--profile", "default", "share-once", "KEY"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.failure()
.stderr(predicates::str::contains("TSAFE_OTS_BASE_URL"));
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_share_http_url_rejected_without_allow_insecure() {
let dir = tempdir().unwrap();
init_vault_with_key(dir.path(), "KEY", "val");
tsafe()
.args(["--profile", "default", "share-once", "KEY"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_BASE_URL", "http://ots.example.com")
.assert()
.failure()
.stderr(predicates::str::contains("https://"));
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_share_server_error_gives_http_code() {
let dir = tempdir().unwrap();
init_vault_with_key(dir.path(), "KEY", "val");
let mut server = mockito::Server::new();
let _m = server
.mock("POST", "/create")
.with_status(503)
.with_body("service unavailable")
.create();
tsafe()
.args(["--profile", "default", "share-once", "KEY"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_BASE_URL", server.url())
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.failure()
.stderr(predicates::str::contains("503"));
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_json_secret_field_prints_value() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let consume_path = "/s/abc123";
let _m = server
.mock("POST", consume_path)
.with_status(200)
.with_header("Content-Type", "application/json")
.with_body(r#"{"secret":"received-value-42"}"#)
.create();
let url = format!("{}{consume_path}", server.url());
tsafe()
.args(["--profile", "default", "snap-receive", &url])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("received-value-42"));
_m.assert();
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_plaintext_field_prints_value() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let _m = server
.mock("POST", "/s/tok")
.with_status(200)
.with_body(r#"{"plaintext":"plain-value"}"#)
.create();
let url = format!("{}/s/tok", server.url());
tsafe()
.args(["--profile", "default", "snap-receive", &url])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("plain-value"));
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_html_secret_content_field() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let html_body = r#"<html><body><div id="secret-content">html-secret-val</div></body></html>"#;
let _m = server
.mock("POST", "/s/html")
.with_status(200)
.with_header("Content-Type", "text/html")
.with_body(html_body)
.create();
let url = format!("{}/s/html", server.url());
tsafe()
.args(["--profile", "default", "snap-receive", &url])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("html-secret-val"));
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_get_fallback_prints_value() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let post_mock = server.mock("POST", "/s/get_only").with_status(405).create();
let get_mock = server
.mock("GET", "/s/get_only")
.with_status(200)
.with_header("Content-Type", "application/json")
.with_body(r#"{"secret":"get-fallback-value"}"#)
.create();
let url = format!("{}/s/get_only", server.url());
tsafe()
.args(["--profile", "default", "snap-receive", &url])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("get-fallback-value"));
post_mock.assert();
get_mock.assert();
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_get_fallback_http_error_surfaces_status() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let post_mock = server
.mock("POST", "/s/get_error")
.with_status(405)
.create();
let get_mock = server
.mock("GET", "/s/get_error")
.with_status(500)
.with_body("backend failure")
.create();
let url = format!("{}/s/get_error", server.url());
tsafe()
.args(["--profile", "default", "snap-receive", &url])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.failure()
.stderr(predicates::str::contains("HTTP 500"))
.stderr(predicates::str::contains("backend failure"));
post_mock.assert();
get_mock.assert();
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_store_saves_to_vault() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let _m = server
.mock("POST", "/s/store_tok")
.with_status(200)
.with_body(r#"{"secret":"stored-secret-xyz"}"#)
.create();
let url = format!("{}/s/store_tok", server.url());
tsafe()
.args([
"--profile",
"default",
"snap-receive",
&url,
"--store",
"RECEIVED_KEY",
])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("RECEIVED_KEY"));
let audit = audit_log_contents(dir.path());
assert!(audit.contains("\"operation\":\"receive-once\""));
assert!(audit.contains("\"key\":\"RECEIVED_KEY\""));
tsafe()
.args(["--profile", "default", "get", "RECEIVED_KEY"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success()
.stdout(predicates::str::contains("stored-secret-xyz"));
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_404_reports_consumed_or_expired() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let _m = server.mock("POST", "/s/gone").with_status(404).create();
let url = format!("{}/s/gone", server.url());
tsafe()
.args(["--profile", "default", "snap-receive", &url])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.failure()
.stderr(predicates::str::contains("consumed").or(predicates::str::contains("expired")));
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_http_url_rejected_without_allow_insecure() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
tsafe()
.args([
"--profile",
"default",
"snap-receive",
"http://ots.example.com/s/token",
])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.failure()
.stderr(predicates::str::contains("https://"));
}
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_fragment_is_stripped_from_url() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let _m = server
.mock("POST", "/s/frag")
.with_status(200)
.with_body(r#"{"secret":"frag-value"}"#)
.create();
let url = format!("{}/s/frag#ignore-this-part", server.url());
tsafe()
.args(["--profile", "default", "snap-receive", &url])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("frag-value"));
_m.assert(); }
#[cfg_attr(not(feature = "ots-sharing"), ignore = "requires ots-sharing feature")]
#[test]
fn snap_receive_without_store_appends_audit_entry() {
let dir = tempdir().unwrap();
tsafe()
.args(["--profile", "default", "init"])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.assert()
.success();
let mut server = mockito::Server::new();
let _m = server
.mock("POST", "/s/plain")
.with_status(200)
.with_header("Content-Type", "application/json")
.with_body(r#"{"secret":"plain-audit-value"}"#)
.create();
let url = format!("{}/s/plain", server.url());
tsafe()
.args(["--profile", "default", "snap-receive", &url])
.env("TSAFE_VAULT_DIR", dir.path())
.env("TSAFE_PASSWORD", "test-pw")
.env("TSAFE_OTS_ALLOW_INSECURE", "1")
.assert()
.success()
.stdout(predicates::str::contains("plain-audit-value"));
let audit = audit_log_contents(dir.path());
assert!(audit.contains("\"operation\":\"receive-once\""));
assert!(
!audit.contains("\"key\":\"plain-audit-value\""),
"plaintext should not be written into the audit log"
);
_m.assert();
}