#[cfg(test)]
mod tests {
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::claims::validator::ClaimsValidator;
use crate::claims::{ClaimValidator, CognitoJwtClaims};
use crate::cognito::config::{TokenUse, VerifierConfig};
use crate::common::error::JwtError;
fn create_test_claims() -> CognitoJwtClaims {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
CognitoJwtClaims {
sub: "user123".to_string(),
iss: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example".to_string(),
client_id: "client1".to_string(),
origin_jti: Some("origin123".to_string()),
event_id: Some("event123".to_string()),
token_use: "id".to_string(),
scope: Some("openid profile".to_string()),
auth_time: now - 3600,
exp: now + 3600,
iat: now - 3600,
jti: "jti123".to_string(),
username: Some("testuser".to_string()),
custom_claims: {
let mut map = HashMap::new();
map.insert("aud".to_string(), Value::String("client1".to_string()));
map
},
}
}
fn create_test_config() -> Arc<VerifierConfig> {
Arc::new(VerifierConfig {
region: "us-east-1".to_string(),
user_pool_id: "us-east-1_example".to_string(),
client_ids: vec!["client1".to_string(), "client2".to_string()],
allowed_token_uses: vec![TokenUse::Id, TokenUse::Access],
clock_skew: Duration::from_secs(60),
jwk_cache_duration: Duration::from_secs(3600),
required_claims: {
let mut set = HashSet::new();
set.insert("sub".to_string());
set.insert("iss".to_string());
set.insert("client_id".to_string());
set
},
custom_validators: Vec::new(),
error_verbosity: crate::common::error::ErrorVerbosity::Standard,
})
}
#[test]
fn test_validate_claims_valid() {
let config = create_test_config();
let validator = ClaimsValidator::new(config);
let claims = create_test_claims();
let result = validator.validate_claims(&claims);
assert!(result.is_ok());
}
#[test]
fn test_validate_claims_missing_required_claim() {
let config = create_test_config();
let validator = ClaimsValidator::new(config);
let mut claims = create_test_claims();
claims.sub = "".to_string();
let result = validator.validate_claims(&claims);
assert!(result.is_err());
if let Err(JwtError::InvalidClaim {
claim,
reason: _,
value: _,
}) = result
{
assert_eq!(claim, "sub");
} else {
panic!("Expected InvalidClaim error for 'sub'");
}
let mut claims = create_test_claims();
claims.iss = "".to_string();
let result = validator.validate_claims(&claims);
assert!(result.is_err());
if let Err(JwtError::InvalidClaim {
claim,
reason: _,
value: _,
}) = result
{
assert_eq!(claim, "iss");
} else {
panic!("Expected InvalidClaim error for 'iss'");
}
let mut claims = create_test_claims();
claims.client_id = "".to_string();
let result = validator.validate_claims(&claims);
assert!(result.is_err());
if let Err(JwtError::InvalidClaim {
claim,
reason: _,
value: _,
}) = result
{
assert_eq!(claim, "client_id");
} else {
panic!("Expected InvalidClaim error for 'client_id'");
}
}
#[test]
fn test_validate_token_use() {
let config = create_test_config();
let validator = ClaimsValidator::new(config);
let result = validator.validate_token_use("id");
assert!(result.is_ok());
assert_eq!(result.unwrap(), TokenUse::Id);
let result = validator.validate_token_use("access");
assert!(result.is_ok());
assert_eq!(result.unwrap(), TokenUse::Access);
let result = validator.validate_token_use("");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), JwtError::InvalidClaim { .. }));
let result = validator.validate_token_use("refresh");
assert!(result.is_err());
if let Err(JwtError::InvalidTokenUse {
expected: _,
actual,
}) = result
{
assert_eq!(actual, "refresh");
} else {
panic!("Expected InvalidTokenUse error");
}
}
#[test]
fn test_validate_expiration() {
let config = create_test_config();
let validator = ClaimsValidator::new(config);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let result = validator.validate_expiration(now + 3600);
assert!(result.is_ok());
let result = validator.validate_expiration(now - 30);
assert!(result.is_ok());
let result = validator.validate_expiration(now - 120);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), JwtError::ExpiredToken { .. }));
}
#[test]
fn test_validate_issued_at() {
let config = create_test_config();
let validator = ClaimsValidator::new(config);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let result = validator.validate_issued_at(now - 3600);
assert!(result.is_ok());
let result = validator.validate_issued_at(now + 30);
assert!(result.is_ok());
let result = validator.validate_issued_at(now + 120);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), JwtError::InvalidClaim { .. }));
}
#[test]
fn test_validate_not_before() {
let config = create_test_config();
let validator = ClaimsValidator::new(config);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let result = validator.validate_not_before(Some(now - 3600));
assert!(result.is_ok());
let result = validator.validate_not_before(Some(now + 30));
assert!(result.is_ok());
let result = validator.validate_not_before(Some(now + 120));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
JwtError::TokenNotYetValid { .. }
));
let result = validator.validate_not_before(None);
assert!(result.is_ok());
}
#[test]
fn test_validate_issuer() {
let config = create_test_config();
let validator = ClaimsValidator::new(config);
let result = validator
.validate_issuer("https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example");
assert!(result.is_ok());
let result = validator
.validate_issuer("https://cognito-idp.us-west-2.amazonaws.com/us-west-2_example");
assert!(result.is_err());
if let Err(JwtError::InvalidIssuer { expected, actual }) = result {
assert_eq!(
expected,
"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example"
);
assert_eq!(
actual,
"https://cognito-idp.us-west-2.amazonaws.com/us-west-2_example"
);
} else {
panic!("Expected InvalidIssuer error");
}
}
#[test]
fn test_validate_client_id() {
let config = create_test_config();
let validator = ClaimsValidator::new(config);
let result = validator.validate_client_id("client1");
assert!(result.is_ok());
let result = validator.validate_client_id("client2");
assert!(result.is_ok());
let result = validator.validate_client_id("client3");
assert!(result.is_err());
if let Err(JwtError::InvalidClientId { expected, actual }) = result {
assert_eq!(expected, vec!["client1".to_string(), "client2".to_string()]);
assert_eq!(actual, "client3");
} else {
panic!("Expected InvalidClientId error");
}
let config = Arc::new(VerifierConfig {
client_ids: vec![],
..(*create_test_config()).clone()
});
let validator = ClaimsValidator::new(config);
let result = validator.validate_client_id("any_client");
assert!(result.is_ok());
}
#[test]
fn test_custom_validators() {
struct TestValidator;
impl ClaimValidator for TestValidator {
fn validate(&self, claims: &CognitoJwtClaims) -> Result<(), String> {
if claims.username.as_deref() != Some("testuser") {
return Err("Username must be 'testuser'".to_string());
}
Ok(())
}
}
let mut config = (*create_test_config()).clone();
config.custom_validators.push(Box::new(TestValidator));
let config = Arc::new(config);
let validator = ClaimsValidator::new(config);
let claims = create_test_claims();
let result = validator.validate_claims(&claims);
assert!(result.is_ok());
let mut claims = create_test_claims();
claims.username = Some("otheruser".to_string());
let result = validator.validate_claims(&claims);
assert!(result.is_err());
if let Err(JwtError::InvalidClaim {
claim,
reason,
value: _,
}) = result
{
assert_eq!(claim, "custom");
assert_eq!(reason, "Username must be 'testuser'");
} else {
panic!("Expected InvalidClaim error from custom validator");
}
}
}