use assert_cmd::Command; use predicates::prelude::*; use std::fs; use std::path::PathBuf; use tempfile::TempDir; fn create_git_repo(dir: &PathBuf) -> std::process::Output { std::process::Command::new("git") .args(&["init"]) .current_dir(dir) .output() .expect("Failed to init git repo") } fn configure_git_user(dir: &PathBuf) { std::process::Command::new("git") .args(&["config", "user.name", "Test User"]) .current_dir(dir) .output() .expect("Failed to configure git user name"); std::process::Command::new("git") .args(&["config", "user.email", "test@example.com"]) .current_dir(dir) .output() .expect("Failed to configure git user email"); } fn setup_git_repo(dir: &PathBuf) { create_git_repo(dir); configure_git_user(dir); } fn init_quicommit(config_path: &PathBuf) { let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&["init", "--yes", "--config", config_path.to_str().unwrap()]); cmd.assert().success(); } mod config_export { use super::*; #[test] fn test_export_to_stdout() { let temp_dir = TempDir::new().unwrap(); let config_path = temp_dir.path().join("config.toml"); init_quicommit(&config_path); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&["config", "export", "--config", config_path.to_str().unwrap()]); cmd.assert() .success() .stdout(predicate::str::contains("version")) .stdout(predicate::str::contains("[llm]")); } #[test] fn test_export_to_file() { let temp_dir = TempDir::new().unwrap(); let config_path = temp_dir.path().join("config.toml"); let export_path = temp_dir.path().join("exported.toml"); init_quicommit(&config_path); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "export", "--config", config_path.to_str().unwrap(), "--output", export_path.to_str().unwrap(), "--password", "" ]); cmd.assert() .success() .stdout(predicate::str::contains("Configuration exported")); assert!(export_path.exists(), "Export file should be created"); let content = fs::read_to_string(&export_path).unwrap(); assert!(content.contains("version"), "Export should contain version"); assert!(content.contains("[llm]"), "Export should contain LLM config"); } #[test] fn test_export_encrypted() { let temp_dir = TempDir::new().unwrap(); let config_path = temp_dir.path().join("config.toml"); let export_path = temp_dir.path().join("encrypted.toml"); init_quicommit(&config_path); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "export", "--config", config_path.to_str().unwrap(), "--output", export_path.to_str().unwrap(), "--password", "test_password_123" ]); cmd.assert() .success() .stdout(predicate::str::contains("encrypted and exported")); assert!(export_path.exists(), "Export file should be created"); let content = fs::read_to_string(&export_path).unwrap(); assert!(content.starts_with("ENCRYPTED:"), "Encrypted file should start with ENCRYPTED:"); assert!(!content.contains("[llm]"), "Encrypted content should not be readable"); } } mod config_import { use super::*; #[test] fn test_import_plain_config() { let temp_dir = TempDir::new().unwrap(); let config_path = temp_dir.path().join("config.toml"); let import_path = temp_dir.path().join("import.toml"); let plain_config = r#" version = "1" [llm] provider = "openai" model = "gpt-4" max_tokens = 1000 temperature = 0.7 timeout = 60 api_key_storage = "keyring" [commit] format = "conventional" auto_generate = true allow_empty = false gpg_sign = false max_subject_length = 100 require_scope = false require_body = false body_required_types = ["feat", "fix"] [tag] version_prefix = "v" auto_generate = true gpg_sign = false include_changelog = true [changelog] path = "CHANGELOG.md" auto_generate = true format = "keep-a-changelog" include_hashes = false include_authors = false group_by_type = true [theme] colors = true icons = true date_format = "%Y-%m-%d" [language] output_language = "en" keep_types_english = true keep_changelog_types_english = true "#; fs::write(&import_path, plain_config).unwrap(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "import", "--config", config_path.to_str().unwrap(), "--file", import_path.to_str().unwrap() ]); cmd.assert() .success() .stdout(predicate::str::contains("Configuration imported")); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&["config", "get", "llm.provider", "--config", config_path.to_str().unwrap()]); cmd.assert() .success() .stdout(predicate::str::contains("openai")); } #[test] fn test_import_encrypted_config() { let temp_dir = TempDir::new().unwrap(); let config_path1 = temp_dir.path().join("config1.toml"); let config_path2 = temp_dir.path().join("config2.toml"); let export_path = temp_dir.path().join("encrypted.toml"); init_quicommit(&config_path1); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "set", "llm.provider", "anthropic", "--config", config_path1.to_str().unwrap() ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "export", "--config", config_path1.to_str().unwrap(), "--output", export_path.to_str().unwrap(), "--password", "secure_password" ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "import", "--config", config_path2.to_str().unwrap(), "--file", export_path.to_str().unwrap(), "--password", "secure_password" ]); cmd.assert() .success() .stdout(predicate::str::contains("Configuration imported")); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&["config", "get", "llm.provider", "--config", config_path2.to_str().unwrap()]); cmd.assert() .success() .stdout(predicate::str::contains("anthropic")); } #[test] fn test_import_encrypted_wrong_password() { let temp_dir = TempDir::new().unwrap(); let config_path = temp_dir.path().join("config.toml"); let export_path = temp_dir.path().join("encrypted.toml"); init_quicommit(&config_path); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "export", "--config", config_path.to_str().unwrap(), "--output", export_path.to_str().unwrap(), "--password", "correct_password" ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "import", "--config", config_path.to_str().unwrap(), "--file", export_path.to_str().unwrap(), "--password", "wrong_password" ]); cmd.assert() .failure() .stderr(predicate::str::contains("Failed to decrypt")); } } mod config_export_import_roundtrip { use super::*; #[test] fn test_roundtrip_plain() { let temp_dir = TempDir::new().unwrap(); let config_path1 = temp_dir.path().join("config1.toml"); let config_path2 = temp_dir.path().join("config2.toml"); let export_path = temp_dir.path().join("export.toml"); init_quicommit(&config_path1); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "set", "llm.model", "gpt-4-turbo", "--config", config_path1.to_str().unwrap() ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "export", "--config", config_path1.to_str().unwrap(), "--output", export_path.to_str().unwrap(), "--password", "" ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "import", "--config", config_path2.to_str().unwrap(), "--file", export_path.to_str().unwrap() ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&["config", "get", "llm.model", "--config", config_path2.to_str().unwrap()]); cmd.assert() .success() .stdout(predicate::str::contains("gpt-4-turbo")); } #[test] fn test_roundtrip_encrypted() { let temp_dir = TempDir::new().unwrap(); let config_path1 = temp_dir.path().join("config1.toml"); let config_path2 = temp_dir.path().join("config2.toml"); let export_path = temp_dir.path().join("encrypted.toml"); let password = "my_secure_password_123"; init_quicommit(&config_path1); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "set", "llm.provider", "deepseek", "--config", config_path1.to_str().unwrap() ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "set", "llm.model", "deepseek-chat", "--config", config_path1.to_str().unwrap() ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "export", "--config", config_path1.to_str().unwrap(), "--output", export_path.to_str().unwrap(), "--password", password ]); cmd.assert().success(); let exported_content = fs::read_to_string(&export_path).unwrap(); assert!(exported_content.starts_with("ENCRYPTED:")); assert!(!exported_content.contains("deepseek")); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&[ "config", "import", "--config", config_path2.to_str().unwrap(), "--file", export_path.to_str().unwrap(), "--password", password ]); cmd.assert().success(); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&["config", "get", "llm.provider", "--config", config_path2.to_str().unwrap()]); cmd.assert() .success() .stdout(predicate::str::contains("deepseek")); let mut cmd = Command::cargo_bin("quicommit").unwrap(); cmd.args(&["config", "get", "llm.model", "--config", config_path2.to_str().unwrap()]); cmd.assert() .success() .stdout(predicate::str::contains("deepseek-chat")); } }