use assert_cmd::cargo::cargo_bin_cmd; use predicates::prelude::*; use std::fs; use std::path::PathBuf; use tempfile::TempDir; fn init_quicommit(config_path: &PathBuf) { let mut cmd = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); 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 [tag] version_prefix = "v" auto_generate = true [changelog] path = "CHANGELOG.md" auto_generate = true [language] output_language = "en" keep_types_english = true keep_changelog_types_english = true "#; fs::write(&import_path, plain_config).unwrap(); let mut cmd = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "set", "llm.provider", "anthropic", "--config", config_path1.to_str().unwrap(), ]); cmd.assert().success(); let mut cmd = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "set", "llm.model", "gpt-4-turbo", "--config", config_path1.to_str().unwrap(), ]); cmd.assert().success(); let mut cmd = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "export", "--config", config_path1.to_str().unwrap(), "--output", export_path.to_str().unwrap(), "--password", "", ]); cmd.assert().success(); let mut cmd = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "import", "--config", config_path2.to_str().unwrap(), "--file", export_path.to_str().unwrap(), ]); cmd.assert().success(); let mut cmd = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "set", "llm.provider", "deepseek", "--config", config_path1.to_str().unwrap(), ]); cmd.assert().success(); let mut cmd = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "set", "llm.model", "deepseek-chat", "--config", config_path1.to_str().unwrap(), ]); cmd.assert().success(); let mut cmd = cargo_bin_cmd!("quicommit"); 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 = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "import", "--config", config_path2.to_str().unwrap(), "--file", export_path.to_str().unwrap(), "--password", password, ]); cmd.assert().success(); let mut cmd = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "get", "llm.provider", "--config", config_path2.to_str().unwrap(), ]); cmd.assert() .success() .stdout(predicate::str::contains("deepseek")); let mut cmd = cargo_bin_cmd!("quicommit"); cmd.args(&[ "config", "get", "llm.model", "--config", config_path2.to_str().unwrap(), ]); cmd.assert() .success() .stdout(predicate::str::contains("deepseek-chat")); } }