use api_keys_simplified::{ApiKeyManagerV0, Environment, HashConfig, KeyConfig, KeyStatus};
use api_keys_simplified::{ExposeSecret, SecureStringExt};
use std::collections::HashSet;
#[test]
fn test_verification_with_invalid_hash() {
let generator = ApiKeyManagerV0::init_default_config("sk").unwrap();
let any_key = api_keys_simplified::SecureString::from("any_key".to_string());
let result = generator.verify(&any_key, "invalid_hash_format");
assert!(result.is_ok());
assert_eq!(result.unwrap(), KeyStatus::Invalid);
}
#[test]
fn test_different_keys_same_hash() {
let generator = ApiKeyManagerV0::init_default_config("sk").unwrap();
let key1 = generator.generate(Environment::production()).unwrap();
let key2 = generator.generate(Environment::production()).unwrap();
assert_eq!(
generator
.verify(key2.key(), key1.expose_hash().hash())
.unwrap(),
KeyStatus::Invalid
);
assert_eq!(
generator
.verify(key1.key(), key2.expose_hash().hash())
.unwrap(),
KeyStatus::Invalid
);
}
#[test]
fn test_checksum_validation() {
let config = KeyConfig::default();
let generator = ApiKeyManagerV0::init(
"chk",
config,
HashConfig::default(),
std::time::Duration::ZERO,
)
.unwrap();
let with_checksum = generator.generate(Environment::test()).unwrap();
assert!(generator.verify_checksum(with_checksum.key()).unwrap());
let corrupted = format!(
"{}_corrupt",
&with_checksum.key().expose_secret()[..with_checksum.key().len() - 8]
);
let corrupted_key = api_keys_simplified::SecureString::from(corrupted);
assert!(!generator.verify_checksum(&corrupted_key).unwrap());
}
#[test]
fn test_hash_uniqueness_with_same_key() {
let generator = ApiKeyManagerV0::init_default_config("sk").unwrap();
let hash1 = generator.generate(Environment::production()).unwrap();
let hash2 = generator.generate(Environment::production()).unwrap();
assert_ne!(hash1.expose_hash(), hash2.expose_hash());
}
#[test]
#[cfg_attr(not(feature = "expensive_tests"), ignore)]
fn test_collision_resistance() {
let mut keys = HashSet::new();
let count = 1000;
let generator = ApiKeyManagerV0::init_default_config("text").unwrap();
for _ in 0..count {
let key = generator.generate(Environment::test()).unwrap();
keys.insert(key.key().expose_secret().to_string());
}
assert_eq!(keys.len(), count);
}
#[test]
fn test_key_format_consistency() {
let config = KeyConfig::default();
let generator = ApiKeyManagerV0::init(
"format",
config,
HashConfig::default(),
std::time::Duration::ZERO,
)
.unwrap();
let key = generator.generate(Environment::test()).unwrap();
let key_str = key.key().expose_secret();
assert_eq!(key_str.matches('.').count(), 1);
assert!(key_str
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.'));
}
#[test]
fn test_argon2_phc_format() {
let generator = ApiKeyManagerV0::init_default_config("phc").unwrap();
let key = generator.generate(Environment::test()).unwrap();
let hash = key.expose_hash().hash();
assert!(hash.starts_with("$argon2id$"));
assert!(hash.contains("$v=19$"));
assert!(hash.contains("$m="));
assert!(hash.contains(",t="));
assert!(hash.contains(",p="));
}
#[test]
fn test_error_messages_dont_leak_info() {
let generator = ApiKeyManagerV0::init_default_config("sk").unwrap();
let oversized_key = api_keys_simplified::SecureString::from("a".repeat(1000));
let result = generator.verify(&oversized_key, "some_hash");
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(!err_msg.contains("argon2"));
assert!(!err_msg.contains("salt"));
assert!(!err_msg.contains("parameter"));
assert!(!err_msg.contains("PHC"));
assert!(err_msg == "Operation failed" || err_msg == "Invalid input");
}
#[test]
fn test_oversized_input_error_is_generic() {
let generator = ApiKeyManagerV0::init_default_config("sk").unwrap();
let oversized_key = api_keys_simplified::SecureString::from("a".repeat(1000));
let result = generator.verify(&oversized_key, "some_hash");
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.to_string(), "Invalid input");
assert!(!err.to_string().contains("512"));
assert!(!err.to_string().contains("length"));
assert!(!err.to_string().contains("DoS"));
}