jwt-verify 0.1.0

JWT verification library for AWS Cognito tokens and any OIDC-compatible IDP
Documentation
#[cfg(test)]
mod tests {
    use crate::common::error::JwtError;
    use crate::oidc::discovery::{DiscoveryDocument, OidcDiscovery};
    use mockito::Server;
    use reqwest::Client;
    use std::time::Duration;

    #[tokio::test]
    async fn test_discover_with_fallback_success() {
        // Set up a mock server
        let mut server = Server::new_async().await;
        let mock_server = server.mock("GET", "/.well-known/openid-configuration")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(
                format!(r#"{{
                "issuer": "{}",
                "jwks_uri": "{}/.well-known/jwks.json",
                "authorization_endpoint": "{}/oauth2/authorize",
                "token_endpoint": "{}/oauth2/token"
            }}"#, server.url(), server.url(), server.url(), server.url()),
            )
            .create();

        // Create a discovery service
        let discovery = OidcDiscovery::new(Duration::from_secs(3600));

        // Discover with fallback
        let document = discovery
            .discover_with_fallback(&server.url(), Some("http://fallback-url/jwks.json"))
            .await
            .unwrap();

        // Verify the document (should be from the successful discovery, not fallback)
        assert_eq!(document.issuer, server.url());
        assert_eq!(
            document.jwks_uri,
            format!("{}/.well-known/jwks.json", server.url())
        );

        // Verify that the mock was called
        mock_server.assert();
    }

    #[tokio::test]
    async fn test_discover_with_fallback_failure() {
        // Set up a mock server that returns an error
        let mut server = Server::new_async().await;
        let mock_server = server.mock("GET", "/.well-known/openid-configuration")
            .with_status(404)
            .create();

        // Create a discovery service
        let discovery = OidcDiscovery::new(Duration::from_secs(3600));

        // Discover with fallback
        let document = discovery
            .discover_with_fallback(&server.url(), Some("http://fallback-url/jwks.json"))
            .await
            .unwrap();

        // Verify the document (should be from fallback)
        assert_eq!(document.issuer, server.url());
        assert_eq!(document.jwks_uri, "http://fallback-url/jwks.json");

        // Verify that the mock was called
        mock_server.assert();
    }

    #[tokio::test]
    async fn test_discover_with_fallback_no_fallback() {
        // Set up a mock server that returns an error
        let mut server = Server::new_async().await;
        let mock_server = server.mock("GET", "/.well-known/openid-configuration")
            .with_status(404)
            .create();

        // Create a discovery service
        let discovery = OidcDiscovery::new(Duration::from_secs(3600));

        // Discover with no fallback
        let result = discovery.discover_with_fallback(&server.url(), None).await;

        // Verify that the discovery failed
        assert!(result.is_err());

        // Verify that the mock was called
        mock_server.assert();
    }

    #[test]
    fn test_discovery_document_new() {
        let document = DiscoveryDocument::new("https://example.com", "https://example.com/jwks.json");
        assert_eq!(document.issuer, "https://example.com");
        assert_eq!(document.jwks_uri, "https://example.com/jwks.json");
        assert_eq!(document.authorization_endpoint, None);
        assert_eq!(document.token_endpoint, None);
        assert_eq!(document.userinfo_endpoint, None);
    }

    #[test]
    fn test_discovery_document_with_endpoints() {
        let document = DiscoveryDocument::with_endpoints(
            "https://example.com",
            "https://example.com/jwks.json",
            Some("https://example.com/auth"),
            Some("https://example.com/token"),
            Some("https://example.com/userinfo"),
        );
        assert_eq!(document.issuer, "https://example.com");
        assert_eq!(document.jwks_uri, "https://example.com/jwks.json");
        assert_eq!(document.authorization_endpoint, Some("https://example.com/auth".to_string()));
        assert_eq!(document.token_endpoint, Some("https://example.com/token".to_string()));
        assert_eq!(document.userinfo_endpoint, Some("https://example.com/userinfo".to_string()));
    }

    #[test]
    fn test_discovery_document_validate_success() {
        let document = DiscoveryDocument::new("https://example.com", "https://example.com/jwks.json");
        let result = document.validate("https://example.com");
        assert!(result.is_ok());
    }

    #[test]
    fn test_discovery_document_validate_issuer_mismatch() {
        let document = DiscoveryDocument::new("https://example.com", "https://example.com/jwks.json");
        let result = document.validate("https://wrong-issuer.com");
        assert!(result.is_err());
        match result.unwrap_err() {
            JwtError::ConfigurationError { parameter, error } => {
                assert_eq!(parameter, Some("issuer".to_string()));
                assert!(error.contains("Issuer mismatch"));
            }
            _ => panic!("Expected ConfigurationError"),
        }
    }

    #[test]
    fn test_discovery_document_validate_empty_jwks_uri() {
        let mut document = DiscoveryDocument::new("https://example.com", "https://example.com/jwks.json");
        document.jwks_uri = "".to_string();
        let result = document.validate("https://example.com");
        assert!(result.is_err());
        match result.unwrap_err() {
            JwtError::ConfigurationError { parameter, error } => {
                assert_eq!(parameter, Some("jwks_uri".to_string()));
                assert!(error.contains("JWKS URI is empty"));
            }
            _ => panic!("Expected ConfigurationError"),
        }
    }

    #[test]
    fn test_discovery_document_validate_invalid_jwks_uri() {
        let mut document = DiscoveryDocument::new("https://example.com", "https://example.com/jwks.json");
        document.jwks_uri = "invalid-url".to_string();
        let result = document.validate("https://example.com");
        assert!(result.is_err());
        match result.unwrap_err() {
            JwtError::ConfigurationError { parameter, error } => {
                assert_eq!(parameter, Some("jwks_uri".to_string()));
                assert!(error.contains("JWKS URI must start with http:// or https://"));
            }
            _ => panic!("Expected ConfigurationError"),
        }
    }

    #[test]
    fn test_cache_operations() {
        let discovery = OidcDiscovery::new(Duration::from_secs(3600));
        let document = DiscoveryDocument::new("https://example.com", "https://example.com/jwks.json");
        
        // Add to cache
        discovery.add_to_cache("https://example.com", document.clone());
        
        // Check if cached
        assert!(discovery.is_cached("https://example.com"));
        assert!(!discovery.is_cached("https://other.com"));
        
        // Get cached issuers
        let issuers = discovery.get_cached_issuers();
        assert_eq!(issuers.len(), 1);
        assert_eq!(issuers[0], "https://example.com");
        
        // Clear specific cache
        discovery.clear_cache("https://example.com");
        assert!(!discovery.is_cached("https://example.com"));
        
        // Add back and clear all cache
        discovery.add_to_cache("https://example.com", document);
        discovery.add_to_cache("https://other.com", DiscoveryDocument::new("https://other.com", "https://other.com/jwks.json"));
        assert_eq!(discovery.get_cached_issuers().len(), 2);
        
        discovery.clear_all_cache();
        assert_eq!(discovery.get_cached_issuers().len(), 0);
    }

    #[test]
    fn test_cache_duration() {
        let mut discovery = OidcDiscovery::new(Duration::from_secs(3600));
        assert_eq!(discovery.get_cache_duration(), Duration::from_secs(3600));
        
        discovery.set_cache_duration(Duration::from_secs(7200));
        assert_eq!(discovery.get_cache_duration(), Duration::from_secs(7200));
    }

    #[test]
    fn test_with_client() {
        let client = Client::builder()
            .timeout(Duration::from_secs(10))
            .build()
            .unwrap();
        
        let discovery = OidcDiscovery::with_client(client, Duration::from_secs(3600));
        assert_eq!(discovery.get_cache_duration(), Duration::from_secs(3600));
    }
}