test(cli): export/restore round-trip + error paths

This commit is contained in:
adlee-was-taken
2026-04-28 19:32:58 -04:00
parent 734325a31f
commit bd7bef7ce4
2 changed files with 155 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
mod common;
use common::TestVault;
use std::process::Command;
use assert_cmd::cargo::CommandCargoExt;
const BACKUP_PASS: &str = "strong-backup-pass-test-2026";
#[test]
fn export_then_restore_round_trip() {
let v = TestVault::init();
v.run(&["add", "login", "--title", "GitHub", "--username", "alice", "--password", "p"]);
v.run(&["add", "login", "--title", "Email", "--username", "bob", "--password", "q"]);
let backup_path = v.path().join("vault.relbak");
let out = v.run_with_backup_pass(
&["backup", "export", backup_path.to_str().unwrap()],
BACKUP_PASS,
);
assert!(out.status.success(), "export failed: {:?}", String::from_utf8_lossy(&out.stderr));
assert!(backup_path.exists());
assert!(v.path().join(".relicario/last_backup").exists());
// Restore into a fresh dir.
let restore_dir = tempfile::TempDir::new().unwrap();
let out = Command::cargo_bin("relicario")
.unwrap()
.current_dir(restore_dir.path())
.env("RELICARIO_TEST_BACKUP_PASSPHRASE", BACKUP_PASS)
.args(["backup", "restore", backup_path.to_str().unwrap(), "."])
.output()
.unwrap();
assert!(out.status.success(), "restore failed: {:?}", String::from_utf8_lossy(&out.stderr));
// Vault should be unlockable in the restore dir using the same passphrase + image.
// Since the original vault didn't include the image, we copy it in manually
// (the standard restore-without-image flow expects the user to keep their
// reference image separately).
std::fs::copy(&v.reference_image, restore_dir.path().join("reference.jpg")).unwrap();
let out = Command::cargo_bin("relicario")
.unwrap()
.current_dir(restore_dir.path())
.env("RELICARIO_TEST_PASSPHRASE", &v.passphrase)
.env("RELICARIO_IMAGE", restore_dir.path().join("reference.jpg"))
.args(["list"])
.output()
.unwrap();
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(stdout.contains("GitHub"));
assert!(stdout.contains("Email"));
}
#[test]
fn restore_refuses_non_empty_target() {
let v = TestVault::init();
let backup_path = v.path().join("vault.relbak");
v.run_with_backup_pass(&["backup", "export", backup_path.to_str().unwrap()], BACKUP_PASS);
let out = Command::cargo_bin("relicario")
.unwrap()
.current_dir(v.path()) // already has a .relicario/
.env("RELICARIO_TEST_BACKUP_PASSPHRASE", BACKUP_PASS)
.args(["backup", "restore", backup_path.to_str().unwrap(), "."])
.output()
.unwrap();
assert!(!out.status.success());
let err = String::from_utf8(out.stderr).unwrap();
assert!(err.contains("already contains a relicario vault"), "stderr: {err}");
}
#[test]
fn export_with_include_image_round_trips_the_image() {
let v = TestVault::init();
let backup_path = v.path().join("vault.relbak");
v.run_with_backup_pass(
&["backup", "export", backup_path.to_str().unwrap(), "--include-image"],
BACKUP_PASS,
);
let restore_dir = tempfile::TempDir::new().unwrap();
let out = Command::cargo_bin("relicario")
.unwrap()
.current_dir(restore_dir.path())
.env("RELICARIO_TEST_BACKUP_PASSPHRASE", BACKUP_PASS)
.args(["backup", "restore", backup_path.to_str().unwrap(), "."])
.output()
.unwrap();
assert!(out.status.success(), "{:?}", String::from_utf8_lossy(&out.stderr));
assert!(restore_dir.path().join("reference.jpg").exists(),
"image should be restored when --include-image was used");
}
#[test]
fn export_with_no_history_skips_git_dir() {
let v = TestVault::init();
let backup_path = v.path().join("vault.relbak");
v.run_with_backup_pass(
&["backup", "export", backup_path.to_str().unwrap(), "--no-history"],
BACKUP_PASS,
);
let restore_dir = tempfile::TempDir::new().unwrap();
let out = Command::cargo_bin("relicario")
.unwrap()
.current_dir(restore_dir.path())
.env("RELICARIO_TEST_BACKUP_PASSPHRASE", BACKUP_PASS)
.args(["backup", "restore", backup_path.to_str().unwrap(), "."])
.output()
.unwrap();
assert!(out.status.success(), "{:?}", String::from_utf8_lossy(&out.stderr));
// .git/ should exist but contain only the "restore from backup ..." commit.
assert!(restore_dir.path().join(".git").is_dir());
let out = std::process::Command::new("git")
.current_dir(restore_dir.path())
.args(["log", "--oneline"])
.output()
.unwrap();
let log = String::from_utf8(out.stdout).unwrap();
assert_eq!(log.lines().count(), 1, "expected one commit, got: {log}");
assert!(log.contains("restore from backup"));
}
#[test]
fn wrong_backup_passphrase_fails() {
let v = TestVault::init();
let backup_path = v.path().join("vault.relbak");
v.run_with_backup_pass(&["backup", "export", backup_path.to_str().unwrap()], BACKUP_PASS);
let restore_dir = tempfile::TempDir::new().unwrap();
let out = Command::cargo_bin("relicario")
.unwrap()
.current_dir(restore_dir.path())
.env("RELICARIO_TEST_BACKUP_PASSPHRASE", "definitely-wrong")
.args(["backup", "restore", backup_path.to_str().unwrap(), "."])
.output()
.unwrap();
assert!(!out.status.success());
let err = String::from_utf8(out.stderr).unwrap();
assert!(err.contains("wrong backup passphrase"), "stderr: {err}");
}

View File

@@ -78,6 +78,19 @@ impl TestVault {
cmd.output().unwrap()
}
pub fn run_with_backup_pass(&self, args: &[&str], backup_pass: &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)
.env("RELICARIO_TEST_BACKUP_PASSPHRASE", backup_pass)
.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())