use std::fs; use std::path::Path; use std::process::Command; use tempfile::TempDir; // Base runner kept as the documented counterpart to relicario_with_git_identity // below (every test in this file needs the git identity, so only the _with_ // variant is currently called). #[allow(dead_code)] fn relicario(config_home: &Path, args: &[&str]) -> std::process::Output { Command::new(env!("CARGO_BIN_EXE_relicario")) .env("XDG_CONFIG_HOME", config_home) .env("HOME", config_home) // belt-and-suspenders for dirs on all platforms .args(args) .output() .expect("run relicario") } /// Like relicario() but also injects the git committer identity so that /// `git commit` inside `org init` doesn't fail with "Please tell me who you are." fn relicario_with_git_identity(config_home: &Path, args: &[&str]) -> std::process::Output { Command::new(env!("CARGO_BIN_EXE_relicario")) .env("XDG_CONFIG_HOME", config_home) .env("HOME", config_home) .env("GIT_AUTHOR_NAME", "Test Device") .env("GIT_AUTHOR_EMAIL", "test@relicario.test") .env("GIT_COMMITTER_NAME", "Test Device") .env("GIT_COMMITTER_EMAIL", "test@relicario.test") .args(args) .output() .expect("run relicario") } fn git(repo: &Path, args: &[&str]) -> std::process::Output { Command::new("git") .current_dir(repo) .args(args) .output() .expect("run git") } /// Lay out device keys directly under `/relicario/devices//` /// and set `devices/current` — mirrors the B2 seed_helper_tests approach. /// Returns the OpenSSH public key string so the caller can build an allowed_signers /// file for `git verify-commit`. fn seed_device(config_home: &Path, name: &str) -> String { let (priv_openssh, pub_openssh) = relicario_core::device::generate_keypair().expect("generate_keypair"); let dev_dir = config_home .join("relicario") .join("devices") .join(name); fs::create_dir_all(&dev_dir).expect("create device dir"); let signing_key_path = dev_dir.join("signing.key"); fs::write(&signing_key_path, priv_openssh.as_str()) .expect("write signing.key"); // ssh requires 0600 on private key files or it refuses to use them. #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; fs::set_permissions(&signing_key_path, fs::Permissions::from_mode(0o600)) .expect("chmod signing.key"); } fs::write(dev_dir.join("signing.pub"), &pub_openssh) .expect("write signing.pub"); // Also write stub deploy key files so configure_git_signing doesn't trip on // a missing deploy.key path (the git config value just points to the file; // the file itself is never read during org init). fs::write(dev_dir.join("deploy.key"), "").expect("write stub deploy.key"); fs::write(dev_dir.join("deploy.pub"), "").expect("write stub deploy.pub"); // Set this device as current. let devices_dir = config_home.join("relicario").join("devices"); fs::write(devices_dir.join("current"), format!("{name}\n")) .expect("write current"); pub_openssh } #[test] fn org_init_produces_a_signed_initial_commit() { let cfg = TempDir::new().unwrap(); let org = TempDir::new().unwrap(); // Lay out the device key directly (no `device add` needed — it requires Gitea). let pub_openssh = seed_device(cfg.path(), "test-dev"); // Initialize the org vault. `--dir` comes AFTER `org init` (B14 global). // Inject git identity so the commit doesn't fail "Please tell me who you are." let init = relicario_with_git_identity( cfg.path(), &["org", "init", "--dir", org.path().to_str().unwrap(), "--name", "Acme"], ); assert!( init.status.success(), "org init failed:\nstdout: {}\nstderr: {}", String::from_utf8_lossy(&init.stdout), String::from_utf8_lossy(&init.stderr) ); // The org repo must be configured to sign. let cfg_out = git(org.path(), &["config", "commit.gpgsign"]); assert_eq!( String::from_utf8_lossy(&cfg_out.stdout).trim(), "true", "org repo must have commit.gpgsign=true" ); // The HEAD commit object must carry a signature header. let head = git(org.path(), &["cat-file", "commit", "HEAD"]); let body = String::from_utf8_lossy(&head.stdout); assert!( body.contains("gpgsig "), "HEAD commit must be signed (no gpgsig header found):\n{body}" ); // Configure an allowed_signers file so `git verify-commit` can validate the // SSH signature. The principal must match the committer email injected above. let allowed_signers_path = cfg.path().join("allowed_signers"); let allowed_line = format!("test@relicario.test {}", pub_openssh.trim()); fs::write(&allowed_signers_path, format!("{allowed_line}\n")) .expect("write allowed_signers"); git( org.path(), &[ "config", "gpg.ssh.allowedSignersFile", allowed_signers_path.to_str().unwrap(), ], ); // Now verify-commit should succeed. let verify = git(org.path(), &["verify-commit", "HEAD"]); assert!( verify.status.success(), "git verify-commit HEAD failed:\nstdout: {}\nstderr: {}", String::from_utf8_lossy(&verify.stdout), String::from_utf8_lossy(&verify.stderr) ); // The commit body must carry the org-init action trailer. let log_out = git(org.path(), &["log", "-1", "--format=%B"]); let commit_body = String::from_utf8_lossy(&log_out.stdout); assert!( commit_body.contains("Relicario-Action: org-init"), "HEAD commit body must contain 'Relicario-Action: org-init' trailer:\n{commit_body}" ); }