test(cli/org): failing Card/Key/Totp org add round-trips (B4, pre-A-integration)
Adds run_stdin + create_collection_and_grant fixture helpers and three acceptance tests for org add card/key/totp. Red until B1/B2 wire the subcommands (currently: unrecognized subcommand). Asserts org get masks card number + key material without --show. Edit round-trips land with B3.
This commit is contained in:
@@ -67,6 +67,39 @@ impl OrgFixture {
|
||||
let v: serde_json::Value = serde_json::from_str(&s).unwrap();
|
||||
v["members"][0]["member_id"].as_str().unwrap().to_string()
|
||||
}
|
||||
|
||||
/// Like `run`, but pipes `stdin_data` into the child's stdin — used to drive
|
||||
/// `--*-stdin` secret flags and the interactive edit prompts. `wait_with_output`
|
||||
/// closes stdin for us, so multiline secrets (read-to-EOF) terminate cleanly.
|
||||
fn run_stdin(&self, args: &[&str], stdin_data: &str) -> std::process::Output {
|
||||
use std::io::Write as _;
|
||||
let mut child = Command::cargo_bin("relicario")
|
||||
.unwrap()
|
||||
.env("XDG_CONFIG_HOME", &self.xdg)
|
||||
.env("RELICARIO_ORG_DIR", self.vault.path())
|
||||
.args(args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
child.stdin.as_mut().unwrap().write_all(stdin_data.as_bytes()).unwrap();
|
||||
child.wait_with_output().unwrap()
|
||||
}
|
||||
|
||||
/// Create collection `slug` and grant the owner access to it — the common
|
||||
/// setup the item-type round-trip tests share.
|
||||
fn create_collection_and_grant(&self, slug: &str) {
|
||||
let owner = self.owner_member_id();
|
||||
assert!(
|
||||
self.run(&["org", "create-collection", slug, "--name", slug]).status.success(),
|
||||
"create-collection {slug} failed",
|
||||
);
|
||||
assert!(
|
||||
self.run(&["org", "grant", &owner, slug]).status.success(),
|
||||
"grant {slug} failed",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -215,3 +248,76 @@ fn org_rm_restore_purge_cycle() {
|
||||
let body = String::from_utf8_lossy(&log.stdout).to_string();
|
||||
assert!(body.contains("Relicario-Action: item-purge"), "missing purge trailer: {body}");
|
||||
}
|
||||
|
||||
// --- v0.8.1 org item-type parity: Card / Key / Totp -------------------------
|
||||
// These drive the new `org add <card|key|totp>` subcommands. Secrets enter via
|
||||
// `--*-stdin` (read from piped stdin) or, for Totp, the `--secret` flag. `org get`
|
||||
// must mask every secret unless `--show` is passed — asserted below.
|
||||
|
||||
#[test]
|
||||
fn org_add_card_via_stdin_then_get_masks_secret() {
|
||||
let f = OrgFixture::new();
|
||||
f.create_collection_and_grant("eng");
|
||||
|
||||
// build_card reads number, then cvv, then pin — one line each, in that order.
|
||||
let out = f.run_stdin(
|
||||
&[
|
||||
"org", "add", "card", "--collection", "eng", "--title", "Corp Visa",
|
||||
"--kind", "credit", "--number-stdin", "--cvv-stdin", "--pin-stdin",
|
||||
],
|
||||
"4111111111111111\n123\n4321\n",
|
||||
);
|
||||
assert!(out.status.success(), "add card: {}", String::from_utf8_lossy(&out.stderr));
|
||||
|
||||
// get masks the card number by default.
|
||||
let got = f.run(&["org", "get", "Corp Visa"]);
|
||||
let stdout = String::from_utf8_lossy(&got.stdout).to_string();
|
||||
assert!(stdout.contains("Corp Visa"), "title missing: {stdout}");
|
||||
assert!(stdout.contains("********"), "card number must be masked without --show: {stdout}");
|
||||
assert!(!stdout.contains("4111111111111111"), "secret leaked without --show: {stdout}");
|
||||
|
||||
// --show reveals it.
|
||||
let shown = f.run(&["org", "get", "Corp Visa", "--show"]);
|
||||
let shown = String::from_utf8_lossy(&shown.stdout).to_string();
|
||||
assert!(shown.contains("4111111111111111"), "number not revealed with --show: {shown}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn org_add_key_via_stdin_then_get_masks_material() {
|
||||
let f = OrgFixture::new();
|
||||
f.create_collection_and_grant("eng");
|
||||
|
||||
// build_key reads key material from stdin to EOF (multiline secret).
|
||||
let out = f.run_stdin(
|
||||
&[
|
||||
"org", "add", "key", "--collection", "eng", "--title", "Deploy Key",
|
||||
"--label", "ci", "--algorithm", "ed25519", "--material-stdin",
|
||||
],
|
||||
"-----BEGIN OPENSSH PRIVATE KEY-----\nAAAAsecretmaterial\n-----END OPENSSH PRIVATE KEY-----\n",
|
||||
);
|
||||
assert!(out.status.success(), "add key: {}", String::from_utf8_lossy(&out.stderr));
|
||||
|
||||
let got = f.run(&["org", "get", "Deploy Key"]);
|
||||
let stdout = String::from_utf8_lossy(&got.stdout).to_string();
|
||||
assert!(stdout.contains("Label: ci"), "label missing: {stdout}");
|
||||
assert!(stdout.contains("********"), "key material must be masked without --show: {stdout}");
|
||||
assert!(!stdout.contains("secretmaterial"), "key material leaked without --show: {stdout}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn org_add_totp_with_secret_flag_round_trips() {
|
||||
let f = OrgFixture::new();
|
||||
f.create_collection_and_grant("eng");
|
||||
|
||||
// Totp accepts the base32 secret via --secret (no stdin needed).
|
||||
let out = f.run(&[
|
||||
"org", "add", "totp", "--collection", "eng", "--title", "AWS root",
|
||||
"--issuer", "AWS", "--secret", "JBSWY3DPEHPK3PXP",
|
||||
]);
|
||||
assert!(out.status.success(), "add totp: {}", String::from_utf8_lossy(&out.stderr));
|
||||
|
||||
let got = f.run(&["org", "get", "AWS root"]);
|
||||
let stdout = String::from_utf8_lossy(&got.stdout).to_string();
|
||||
assert!(stdout.contains("AWS root"), "title missing: {stdout}");
|
||||
assert!(stdout.contains("Issuer: AWS"), "issuer missing: {stdout}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user