mod common;
use common::{create_git_repo, git_crypt_cmd};
use predicates::prelude::*;
use std::fs;
use std::process::Command as StdCommand;
use tempfile::TempDir;
#[test]
fn test_init_command() {
let temp = create_git_repo();
let mut cmd = git_crypt_cmd();
cmd.arg("init")
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("Initializing git-crypt"))
.stdout(predicate::str::contains("Generated new encryption key"));
assert!(temp.path().join(".git/git-crypt").exists());
assert!(temp.path().join(".git/git-crypt/keys").exists());
assert!(temp.path().join(".git/git-crypt/keys/default").exists());
}
#[test]
fn test_init_twice_succeeds_but_warns() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("already initialized"));
}
#[test]
fn test_init_outside_git_repo_fails() {
let temp = TempDir::new().unwrap();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.failure()
.stderr(
predicate::str::contains("not in a git repository")
.or(predicate::str::contains("Not in a git repository")),
);
}
#[test]
fn test_export_and_import_key() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let key_file = temp.path().join("exported.key");
git_crypt_cmd()
.args(["export-key", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("Key exported successfully"));
assert!(key_file.exists());
assert_eq!(fs::metadata(&key_file).unwrap().len(), 32);
let temp2 = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp2.path())
.assert()
.success();
git_crypt_cmd()
.args(["import-key", key_file.to_str().unwrap()])
.current_dir(temp2.path())
.assert()
.success()
.stdout(predicate::str::contains("Key imported successfully"));
}
#[test]
fn test_export_key_before_init_fails() {
let temp = create_git_repo();
let key_file = temp.path().join("exported.key");
git_crypt_cmd()
.args(["export-key", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.failure()
.stderr(predicate::str::contains("not initialized"));
}
#[test]
fn test_unlock_with_key_file() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let key_file = temp.path().join("key.bin");
git_crypt_cmd()
.args(["export-key", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.success();
git_crypt_cmd()
.arg("lock")
.current_dir(temp.path())
.assert()
.success();
git_crypt_cmd()
.args(["unlock", "--key-file", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("Repository unlocked successfully"));
}
#[test]
fn test_lock_and_unlock() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
git_crypt_cmd()
.arg("lock")
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("Repository locked"));
git_crypt_cmd()
.arg("unlock")
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("Repository unlocked"));
}
#[test]
fn test_help_command() {
git_crypt_cmd()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("Transparent file encryption"))
.stdout(predicate::str::contains("init"))
.stdout(predicate::str::contains("unlock"))
.stdout(predicate::str::contains("lock"));
}
#[test]
fn test_version_command() {
git_crypt_cmd()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains("0.1.0"));
}
#[test]
fn test_add_gpg_user_without_gpg_feature() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let result = git_crypt_cmd()
.args(["add-gpg-user", "test@example.com"])
.current_dir(temp.path())
.assert();
if cfg!(not(feature = "gpg")) {
result.failure();
}
}
#[test]
fn test_full_workflow_with_encryption() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let gitattributes = temp.path().join(".gitattributes");
fs::write(&gitattributes, "*.secret filter=git-crypt diff=git-crypt\n").unwrap();
StdCommand::new("git")
.args(["add", ".gitattributes"])
.current_dir(temp.path())
.output()
.unwrap();
StdCommand::new("git")
.args(["commit", "-m", "Add gitattributes"])
.current_dir(temp.path())
.output()
.unwrap();
let secret_file = temp.path().join("test.secret");
fs::write(&secret_file, b"my secret data").unwrap();
let key_file = temp.path().join("shared.key");
git_crypt_cmd()
.args(["export-key", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.success();
assert!(key_file.exists());
}
#[test]
fn test_status_command_placeholder() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
git_crypt_cmd()
.arg("status")
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("not yet implemented"));
}
#[test]
fn test_import_invalid_key_file() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let invalid_key = temp.path().join("invalid.key");
fs::write(&invalid_key, b"not a valid key").unwrap();
git_crypt_cmd()
.args(["import-key", invalid_key.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.failure();
}
#[test]
fn test_key_file_is_32_bytes() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let key_path = temp.path().join(".git/git-crypt/keys/default");
let metadata = fs::metadata(&key_path).unwrap();
assert_eq!(metadata.len(), 32);
}
#[cfg(unix)]
#[test]
fn test_key_file_permissions() {
use std::os::unix::fs::PermissionsExt;
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let key_path = temp.path().join(".git/git-crypt/keys/default");
let metadata = fs::metadata(&key_path).unwrap();
let permissions = metadata.permissions();
assert_eq!(permissions.mode() & 0o777, 0o600);
}
#[test]
fn test_multiple_repos_independent_keys() {
let temp1 = create_git_repo();
let temp2 = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp1.path())
.assert()
.success();
git_crypt_cmd()
.arg("init")
.current_dir(temp2.path())
.assert()
.success();
let key1 = temp1.path().join("key1.bin");
let key2 = temp2.path().join("key2.bin");
git_crypt_cmd()
.args(["export-key", key1.to_str().unwrap()])
.current_dir(temp1.path())
.assert()
.success();
git_crypt_cmd()
.args(["export-key", key2.to_str().unwrap()])
.current_dir(temp2.path())
.assert()
.success();
let key1_bytes = fs::read(&key1).unwrap();
let key2_bytes = fs::read(&key2).unwrap();
assert_ne!(key1_bytes, key2_bytes);
}