use std::collections::HashMap;
use apcore_cli::config::ConfigResolver;
use apcore_cli::security::auth::{AuthProvider, AuthenticationError};
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn make_empty_resolver() -> ConfigResolver {
ConfigResolver::new(None, None)
}
#[test]
fn test_get_api_key_from_env_var() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::set_var("APCORE_AUTH_API_KEY", "env-key-abc") };
let provider = AuthProvider::new(make_empty_resolver());
let key = provider.get_api_key();
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
assert_eq!(key.unwrap(), Some("env-key-abc".to_string()));
}
#[test]
fn test_get_api_key_missing_returns_error() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
let provider = AuthProvider::new(make_empty_resolver());
assert_eq!(provider.get_api_key().unwrap(), None);
}
#[test]
fn test_authenticate_request_adds_bearer_header() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::set_var("APCORE_AUTH_API_KEY", "bearer-test-key") };
let provider = AuthProvider::new(make_empty_resolver());
let result = provider.authenticate_request(HashMap::new());
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
let headers = result.expect("authenticate_request must succeed when key is set");
assert_eq!(
headers.get("Authorization").map(|s| s.as_str()),
Some("Bearer bearer-test-key"),
"Authorization header must be set"
);
}
#[test]
fn test_apply_to_reqwest_injects_bearer_header() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::set_var("APCORE_AUTH_API_KEY", "reqwest-key") };
let provider = AuthProvider::new(make_empty_resolver());
let client = reqwest::Client::new();
let builder = client.get("https://example.com");
let result = provider.apply_to_reqwest(builder);
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
assert!(
result.is_ok(),
"apply_to_reqwest must succeed when key is set"
);
}
#[test]
fn test_check_status_code_200_ok() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
let provider = AuthProvider::new(make_empty_resolver());
assert!(provider.check_status_code(200).is_ok());
}
#[test]
fn test_check_status_code_401_returns_invalid_key() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
let provider = AuthProvider::new(make_empty_resolver());
let err = provider.check_status_code(401).unwrap_err();
assert!(matches!(err, AuthenticationError::InvalidApiKey));
}
#[test]
fn test_check_status_code_403_returns_invalid_key() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
let provider = AuthProvider::new(make_empty_resolver());
let err = provider.check_status_code(403).unwrap_err();
assert!(matches!(err, AuthenticationError::InvalidApiKey));
}
#[test]
fn test_check_status_code_500_passes_through() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
let provider = AuthProvider::new(make_empty_resolver());
assert!(provider.check_status_code(500).is_ok());
}
#[test]
fn test_error_messages_match_spec() {
let missing = AuthenticationError::MissingApiKey;
assert!(
missing.to_string().contains("APCORE_AUTH_API_KEY"),
"MissingApiKey message must mention the env var"
);
let invalid = AuthenticationError::InvalidApiKey;
assert!(
invalid.to_string().contains("Authentication failed"),
"InvalidApiKey message must say 'Authentication failed', got: {}",
invalid
);
}
#[test]
fn test_authenticate_request_returns_augmented_headers_map() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::set_var("APCORE_AUTH_API_KEY", "owned-return-key") };
let provider = AuthProvider::new(make_empty_resolver());
let mut headers = HashMap::new();
headers.insert("X-Trace-Id".to_string(), "abc-123".to_string());
let returned: HashMap<String, String> = provider
.authenticate_request(headers)
.expect("authenticate_request must succeed when key is set");
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
assert_eq!(
returned.get("Authorization").map(String::as_str),
Some("Bearer owned-return-key"),
"returned map must contain Authorization header"
);
assert_eq!(
returned.get("X-Trace-Id").map(String::as_str),
Some("abc-123"),
"returned map must preserve pre-existing entries (mutate-and-return semantics)"
);
}
#[test]
fn test_cli_flag_takes_precedence_over_env_var() {
let _guard = ENV_LOCK.lock().unwrap();
unsafe { std::env::set_var("APCORE_AUTH_API_KEY", "env-key") };
let mut flags = HashMap::new();
flags.insert("--api-key".to_string(), Some("cli-key".to_string()));
let resolver = ConfigResolver::new(Some(flags), None);
let provider = AuthProvider::new(resolver);
let result = provider.get_api_key();
unsafe { std::env::remove_var("APCORE_AUTH_API_KEY") };
assert_eq!(
result.unwrap(),
Some("cli-key".to_string()),
"CLI --api-key flag must take precedence over APCORE_AUTH_API_KEY env var"
);
}