//! Shared helpers for CLI integration tests. //! //! `TestVault::init()` spins up a fresh vault in a `TempDir` using //! `RELICARIO_TEST_PASSPHRASE` as the escape hatch (bypasses TTY prompts). //! Every `run()` / `run_with_input()` call sets both `RELICARIO_IMAGE` and //! `RELICARIO_TEST_PASSPHRASE`, so vault-mutating commands unlock without //! interactive input. //! //! Note for Task 23 implementers: commands that prompt for a *new item //! password* (i.e. `edit` when changing a Login password) also use //! `rpassword`. Plumb `RELICARIO_TEST_ITEM_PASSWORD` through `cmd_edit` in //! main.rs, or use an item type / edit path that avoids the rpassword call. use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use assert_cmd::cargo::CommandCargoExt; use tempfile::TempDir; pub struct TestVault { pub dir: TempDir, pub reference_image: PathBuf, pub passphrase: String, } impl TestVault { pub fn init() -> Self { let dir = TempDir::new().expect("tempdir"); let carrier = make_test_jpeg(400, 300); let carrier_path = dir.path().join("carrier.jpg"); std::fs::write(&carrier_path, &carrier).unwrap(); let passphrase = "correct horse battery staple 2026".to_string(); let ref_path = dir.path().join("reference.jpg"); let mut cmd = Command::cargo_bin("relicario").unwrap(); cmd.current_dir(dir.path()) .env("RELICARIO_TEST_PASSPHRASE", &passphrase) .args([ "init", "--image", carrier_path.to_str().unwrap(), "--output", ref_path.to_str().unwrap(), ]) .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let out = cmd.output().unwrap(); assert!( out.status.success(), "init failed:\nstdout: {}\nstderr: {}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); Self { dir, reference_image: ref_path, passphrase, } } pub fn path(&self) -> &Path { self.dir.path() } pub fn run(&self, args: &[&str]) -> std::process::Output { let mut cmd = Command::cargo_bin("relicario").unwrap(); cmd.current_dir(self.dir.path()) .env("RELICARIO_IMAGE", &self.reference_image) .env("RELICARIO_TEST_PASSPHRASE", &self.passphrase) .args(args) .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); cmd.output().unwrap() } pub fn run_with_input(&self, args: &[&str], extra: &[&str]) -> std::process::Output { let mut cmd = Command::cargo_bin("relicario").unwrap(); cmd.current_dir(self.dir.path()) .env("RELICARIO_IMAGE", &self.reference_image) .env("RELICARIO_TEST_PASSPHRASE", &self.passphrase) .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let mut child = cmd.spawn().unwrap(); { let stdin = child.stdin.as_mut().unwrap(); for line in extra { writeln!(stdin, "{line}").unwrap(); } } child.wait_with_output().unwrap() } } pub fn make_test_jpeg(w: u32, h: u32) -> Vec { use image::codecs::jpeg::JpegEncoder; use image::{ExtendedColorType, ImageBuffer, ImageEncoder, Rgb}; let img = ImageBuffer::from_fn(w, h, |x, y| { Rgb([ ((x * 7 + y * 13) % 256) as u8, ((x * 11 + y * 3) % 256) as u8, ((x * 5 + y * 17) % 256) as u8, ]) }); let mut out = Vec::new(); JpegEncoder::new_with_quality(&mut out, 92) .write_image(img.as_raw(), w, h, ExtendedColorType::Rgb8) .unwrap(); out }