#![allow(
clippy::field_reassign_with_default,
clippy::assertions_on_constants,
clippy::overly_complex_bool_expr,
clippy::useless_vec
)]
use rusty_commit::auth::token_storage::{
delete_tokens, get_tokens, has_valid_token, store_tokens, TokenStorage,
};
use rusty_commit::config::Config;
use std::fs;
use std::sync::Mutex;
use tempfile::tempdir;
static TEST_MUTEX: Mutex<()> = Mutex::new(());
fn with_test_lock<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let _guard = TEST_MUTEX.lock().unwrap_or_else(|poisoned| {
poisoned.into_inner()
});
f()
}
fn setup_clean_env(test_name: &str) -> tempfile::TempDir {
std::env::remove_var("RCO_CONFIG_HOME");
std::env::remove_var("HOME");
let env_vars_to_clear = [
"RCO_AI_PROVIDER",
"RCO_API_KEY",
"RCO_MODEL",
"RCO_EMOJI",
"RCO_GITPUSH",
"RCO_LANGUAGE",
"RCO_TOKENS_MAX_OUTPUT",
"RCO_API_URL",
"RCO_TOKENS_MAX_INPUT",
"RCO_COMMIT_TYPE",
"RCO_DESCRIPTION",
"RCO_DESCRIPTION_CAPITALIZE",
"RCO_DESCRIPTION_ADD_PERIOD",
"RCO_DESCRIPTION_MAX_LENGTH",
"RCO_MESSAGE_TEMPLATE_PLACEHOLDER",
"RCO_PROMPT_MODULE",
"RCO_ONE_LINE_COMMIT",
"RCO_WHY",
"RCO_OMIT_SCOPE",
"RCO_ACTION_ENABLED",
"RCO_TEST_MOCK_TYPE",
"RCO_HOOK_AUTO_UNCOMMENT",
"RCO_COMMITLINT_CONFIG",
"RCO_CUSTOM_PROMPT",
];
for var in &env_vars_to_clear {
std::env::remove_var(var);
}
std::env::set_var("RCO_IGNORE_REPO_CONFIG", "1");
std::env::set_var("RCO_DISABLE_SECURE_STORAGE", "1");
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let unique_name = format!("{}_{}", test_name, timestamp);
let temp_dir = tempdir().unwrap();
let config_dir = temp_dir.path().join(&unique_name);
std::fs::create_dir_all(&config_dir).unwrap();
std::env::set_var("HOME", temp_dir.path());
std::env::set_var("RCO_CONFIG_HOME", &config_dir);
let _ = delete_tokens();
temp_dir
}
fn cleanup_env() {
let _ = delete_tokens();
std::env::remove_var("RCO_CONFIG_HOME");
std::env::remove_var("HOME");
std::env::remove_var("RCO_IGNORE_REPO_CONFIG");
std::env::remove_var("RCO_DISABLE_SECURE_STORAGE");
}
#[test]
fn test_token_storage_creation() {
let token = TokenStorage {
access_token: "test_access_token".to_string(),
refresh_token: Some("test_refresh_token".to_string()),
expires_at: Some(9999999999), token_type: "Bearer".to_string(),
scope: Some("test_scope".to_string()),
};
assert_eq!(token.access_token, "test_access_token");
assert_eq!(token.refresh_token.as_deref(), Some("test_refresh_token"));
assert_eq!(token.token_type, "Bearer");
assert!(!token.is_expired());
}
#[test]
fn test_token_expiry() {
let expired_token = TokenStorage {
access_token: "test_token".to_string(),
refresh_token: None,
expires_at: Some(1), token_type: "Bearer".to_string(),
scope: None,
};
assert!(expired_token.is_expired());
let future_token = TokenStorage {
access_token: "test_token".to_string(),
refresh_token: None,
expires_at: Some(9999999999), token_type: "Bearer".to_string(),
scope: None,
};
assert!(!future_token.is_expired());
let no_expiry_token = TokenStorage {
access_token: "test_token".to_string(),
refresh_token: None,
expires_at: None,
token_type: "Bearer".to_string(),
scope: None,
};
assert!(!no_expiry_token.is_expired());
}
#[test]
fn test_token_expires_soon() {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let soon_token = TokenStorage {
access_token: "test_token".to_string(),
refresh_token: None,
expires_at: Some(now + 120), token_type: "Bearer".to_string(),
scope: None,
};
assert!(soon_token.expires_soon());
let later_token = TokenStorage {
access_token: "test_token".to_string(),
refresh_token: None,
expires_at: Some(now + 600), token_type: "Bearer".to_string(),
scope: None,
};
assert!(!later_token.expires_soon());
}
#[test]
fn test_store_and_retrieve_tokens() {
with_test_lock(|| {
let _temp_dir = setup_clean_env("test_store_and_retrieve_tokens");
let result = store_tokens("access_123", Some("refresh_456"), Some(3600));
assert!(result.is_ok());
let tokens = get_tokens().unwrap();
assert!(tokens.is_some());
let tokens = tokens.unwrap();
assert_eq!(tokens.access_token, "access_123");
assert_eq!(tokens.refresh_token.as_deref(), Some("refresh_456"));
assert_eq!(tokens.token_type, "Bearer");
cleanup_env();
});
}
#[test]
fn test_has_valid_token() {
with_test_lock(|| {
let _temp_dir = setup_clean_env("test_has_valid_token");
assert!(!has_valid_token());
store_tokens("valid_token", None, Some(3600)).unwrap();
assert!(has_valid_token());
delete_tokens().unwrap();
assert!(!has_valid_token());
cleanup_env();
});
}
#[test]
fn test_delete_tokens() {
with_test_lock(|| {
let _temp_dir = setup_clean_env("test_delete_tokens");
store_tokens("test_token", None, Some(3600)).unwrap();
assert!(has_valid_token());
let result = delete_tokens();
assert!(result.is_ok());
assert!(!has_valid_token());
cleanup_env();
});
}
#[test]
fn test_config_with_different_providers() {
with_test_lock(|| {
let _temp_dir = setup_clean_env("test_config_provider");
std::env::remove_var("RCO_AI_PROVIDER");
std::env::remove_var("RCO_API_KEY");
let mut config = Config::default();
config.ai_provider = Some("anthropic".to_string());
config.api_key = Some("test_key".to_string());
assert_eq!(config.ai_provider.as_deref(), Some("anthropic"));
assert!(config.save().is_ok());
std::env::remove_var("RCO_AI_PROVIDER");
std::env::remove_var("RCO_API_KEY");
let loaded_config = Config::load().unwrap();
assert_eq!(loaded_config.ai_provider.as_deref(), Some("anthropic"));
assert_eq!(loaded_config.api_key.as_deref(), Some("test_key"));
cleanup_env();
});
}
#[test]
fn test_oauth_client_creation() {
use rusty_commit::auth::oauth::OAuthClient;
let _client = OAuthClient::new();
assert!(true); }
#[test]
fn test_provider_specific_configurations() {
with_test_lock(|| {
let _temp_dir1 = setup_clean_env("test_provider_specific_configurations_bedrock");
std::env::set_var("AWS_BEARER_TOKEN_BEDROCK", "test_bedrock_token");
let mut config = Config::default();
config.ai_provider = Some("amazon-bedrock".to_string());
assert!(config.save().is_ok());
std::env::remove_var("AWS_BEARER_TOKEN_BEDROCK");
cleanup_env();
let _temp_dir2 = setup_clean_env("test_provider_specific_configurations_ollama");
let mut ollama_config = Config::default();
ollama_config.ai_provider = Some("ollama".to_string());
ollama_config.api_url = Some("http://localhost:11434".to_string());
ollama_config.model = Some("mistral".to_string());
assert!(ollama_config.save().is_ok());
cleanup_env();
let _temp_dir3 = setup_clean_env("test_provider_specific_configurations_azure");
let mut azure_config = Config::default();
azure_config.ai_provider = Some("azure".to_string());
azure_config.api_key = Some("azure_key".to_string());
azure_config.api_url = Some("https://test.openai.azure.com".to_string());
azure_config.model = Some("gpt-35-turbo".to_string());
assert!(azure_config.save().is_ok());
let loaded_azure = Config::load().unwrap();
assert_eq!(loaded_azure.ai_provider.as_deref(), Some("azure"));
assert_eq!(loaded_azure.api_key.as_deref(), Some("azure_key"));
assert_eq!(
loaded_azure.api_url.as_deref(),
Some("https://test.openai.azure.com")
);
cleanup_env();
});
}
#[test]
fn test_environment_variable_auth() {
let env_vars = vec![
("ANTHROPIC_API_KEY", "anthropic_test"),
("OPENAI_API_KEY", "openai_test"),
("GROQ_API_KEY", "groq_test"),
("DEEPSEEK_API_KEY", "deepseek_test"),
("MISTRAL_API_KEY", "mistral_test"),
("TOGETHER_API_KEY", "together_test"),
("HF_API_KEY", "hf_test"),
("GOOGLE_API_KEY", "google_test"),
("AWS_BEARER_TOKEN_BEDROCK", "bedrock_test"),
("AWS_ACCESS_KEY_ID", "aws_access_test"),
("AWS_SECRET_ACCESS_KEY", "aws_secret_test"),
];
for (env_var, test_value) in env_vars {
std::env::set_var(env_var, test_value);
let retrieved = std::env::var(env_var).unwrap();
assert_eq!(retrieved, test_value);
std::env::remove_var(env_var);
}
}
#[test]
fn test_config_validation() {
let temp_dir = tempdir().unwrap();
std::env::set_var("HOME", temp_dir.path());
let mut config = Config::default();
assert!(config.set("RCO_AI_PROVIDER", "openai").is_ok());
assert!(config.set("RCO_EMOJI", "true").is_ok());
assert!(config.set("RCO_TOKENS_MAX_INPUT", "8192").is_ok());
assert!(config.set("RCO_EMOJI", "invalid_bool").is_err());
assert!(config.set("RCO_TOKENS_MAX_INPUT", "not_a_number").is_err());
assert!(config.set("UNKNOWN_KEY", "value").is_err());
}
#[test]
fn test_secure_vs_file_storage() {
with_test_lock(|| {
let _temp_dir = setup_clean_env("test_secure_vs_file_storage");
let config_dir = std::env::var("RCO_CONFIG_HOME").unwrap();
store_tokens("test_token", Some("refresh_token"), Some(3600)).unwrap();
let auth_file = std::path::PathBuf::from(&config_dir).join("auth.json");
assert!(auth_file.exists());
let content = fs::read_to_string(&auth_file).unwrap();
assert!(content.contains("test_token"));
assert!(content.contains("refresh_token"));
assert!(content.contains("Bearer"));
cleanup_env();
});
}