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::jwk::provider::JwkProvider;
    use mockito::Server;
    use std::time::Duration;

    #[tokio::test]
    async fn test_jwk_provider_new_valid_params() {
        let provider =
            JwkProvider::new("us-east-1", "us-east-1_example", Duration::from_secs(3600));
        assert!(provider.is_ok());

        let provider = provider.unwrap();
        assert_eq!(
            provider.get_issuer(),
            "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example"
        );
    }

    #[tokio::test]
    async fn test_jwk_provider_new_empty_region() {
        let provider = JwkProvider::new("", "us-east-1_example", Duration::from_secs(3600));
        assert!(provider.is_err());

        if let Err(JwtError::ConfigurationError {
            parameter,
            error: _,
        }) = provider
        {
            assert_eq!(parameter, Some("region".to_string()));
        } else {
            panic!("Expected ConfigurationError with parameter 'region'");
        }
    }

    #[tokio::test]
    async fn test_jwk_provider_new_empty_user_pool_id() {
        let provider = JwkProvider::new("us-east-1", "", Duration::from_secs(3600));
        assert!(provider.is_err());

        if let Err(JwtError::ConfigurationError {
            parameter,
            error: _,
        }) = provider
        {
            assert_eq!(parameter, Some("user_pool_id".to_string()));
        } else {
            panic!("Expected ConfigurationError with parameter 'user_pool_id'");
        }
    }

    #[tokio::test]
    async fn test_jwk_provider_new_invalid_user_pool_id() {
        let provider = JwkProvider::new("us-east-1", "invalid-format", Duration::from_secs(3600));
        assert!(provider.is_err());

        if let Err(JwtError::ConfigurationError {
            parameter,
            error: _,
        }) = provider
        {
            assert_eq!(parameter, Some("user_pool_id".to_string()));
        } else {
            panic!("Expected ConfigurationError with parameter 'user_pool_id'");
        }
    }

    #[tokio::test]
    async fn test_jwk_provider_fetch_success() {
        // Setup mock server - use start_async instead of new() to avoid runtime conflict
        let mut server = Server::new_async().await;
        let mock_server = server.mock("GET", "/.well-known/jwks.json")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{
                "keys": [
                    {
                        "kid": "test-key-1",
                        "alg": "RS256",
                        "use": "sig",
                        "n": "xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHLUXpq15Jw5E4oaxt_yiMiLaDs2KwUDzRda5idsOmMTllD05HG7YAV5EB7mXNX6yfQxRVm1ThE0wLi32XR-8dzUQsYATpspAGXql5iOPxW8bFQwQ",
                        "e": "AQAB"
                    },
                    {
                        "kid": "test-key-2",
                        "alg": "RS256",
                        "use": "sig",
                        "n": "vQJaJITRIP2TOYZLfIX8UQHl5XF-o2MQHJyVpX0hDSiIzUDQEQJd1hkjOYbEL1Y7HFPJiIZPI5ZL-vHR6L6HJ6HKKLMoSZ9i43MDgJ1j-9Bx5YID3kFtGi9aeW4gL9ZEUy-5OLTmOm8P-f3JeCKYwZWCw3JnLc0-9iLfRfMj6eDyPOUCF7g5rnTYH9PL9h6L1QkXMhCkG_dqggf_NHlmHHU7AkWUUyImeCes0TP7GyPL4_eXec3ZBf0jUtMOQAIy4PhRfLMPCmcTpLqBBPJmBW4WcHzMd4L_1ao8TZEjXQNjyNYKPBre1YeAEGhjK5_iw_hGqcUgnsSsGPzxHw",
                        "e": "AQAB"
                    }
                ]
            }"#)
            .create();

        // Create JwkProvider with mock server URL
        let provider =
            JwkProvider::new_with_base_url(&server.url(), "test-issuer", Duration::from_secs(3600))
                .expect("Failed to create JwkProvider");

        // Prefetch keys
        let result = provider.prefetch_keys().await;
        assert!(result.is_ok());

        // Get a key
        let key = provider.get_key("test-key-1").await;
        assert!(key.is_ok());

        // Get another key
        let key = provider.get_key("test-key-2").await;
        assert!(key.is_ok());

        // Get a non-existent key
        let key = provider.get_key("non-existent").await;
        assert!(key.is_err());
        if let Err(JwtError::KeyNotFound(kid)) = key {
            assert_eq!(kid, "non-existent");
        } else {
            panic!("Expected KeyNotFound error");
        }

        mock_server.assert();
    }

    #[tokio::test]
    async fn test_jwk_provider_fetch_error() {
        // Setup mock server with error response
        let mut server = Server::new_async().await;
        let mock_server = server
            .mock("GET", "/.well-known/jwks.json")
            .with_status(500)
            .with_body("Internal Server Error")
            .expect(3)  // Expect 3 requests due to retry mechanism
            .create();

        // Create JwkProvider with mock server URL
        let provider =
            JwkProvider::new_with_base_url(&server.url(), "test-issuer", Duration::from_secs(3600))
                .expect("Failed to create JwkProvider");

        // Prefetch keys
        let result = provider.prefetch_keys().await;
        assert!(result.is_err());
        if let Err(JwtError::JwksFetchError { url: _, error: _ }) = result {
            // Expected error
        } else {
            panic!("Expected JwksFetchError");
        }

        mock_server.assert();
    }

    #[tokio::test]
    async fn test_jwk_provider_fetch_invalid_json() {
        // Setup mock server with invalid JSON response
        let mut server = Server::new_async().await;
        let mock_server = server
            .mock("GET", "/.well-known/jwks.json")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body("invalid json")
            .create();

        // Create JwkProvider with mock server URL
        let provider =
            JwkProvider::new_with_base_url(&server.url(), "test-issuer", Duration::from_secs(3600))
                .expect("Failed to create JwkProvider");

        // Prefetch keys
        let result = provider.prefetch_keys().await;
        assert!(result.is_err());
        if let Err(JwtError::ParseError { part: _, error: _ }) = result {
            // Expected error
        } else {
            panic!("Expected ParseError");
        }

        mock_server.assert();
    }

    #[tokio::test]
    async fn test_jwk_provider_fetch_empty_keys() {
        // Setup mock server with empty keys response
        let mut server = Server::new_async().await;
        let mock_server = server
            .mock("GET", "/.well-known/jwks.json")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{"keys": []}"#)
            .expect(3) // Expect 3 requests due to retry mechanism
            .create();

        // Create JwkProvider with mock server URL
        let provider =
            JwkProvider::new_with_base_url(&server.url(), "test-issuer", Duration::from_secs(3600))
                .expect("Failed to create JwkProvider");

        // Prefetch keys
        let result = provider.prefetch_keys().await;
        assert!(result.is_err());
        if let Err(JwtError::JwksFetchError { url: _, error }) = &result {
            assert!(error.contains("empty"));
        } else {
            panic!("Expected JwksFetchError with empty keys message");
        }

        mock_server.assert();
    }

    #[tokio::test]
    async fn test_jwk_provider_cache_expiration() {
        // Setup mock server - use start_async to avoid runtime conflict
        let mut server = Server::new_async().await;
        let mock_server = server.mock("GET", "/.well-known/jwks.json")
            .with_status(200)
            .with_header("content-type", "application/json")
            .with_body(r#"{
                "keys": [
                    {
                        "kid": "test-key-1",
                        "alg": "RS256",
                        "use": "sig",
                        "n": "xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHLUXpq15Jw5E4oaxt_yiMiLaDs2KwUDzRda5idsOmMTllD05HG7YAV5EB7mXNX6yfQxRVm1ThE0wLi32XR-8dzUQsYATpspAGXql5iOPxW8bFQwQ",
                        "e": "AQAB"
                    }
                ]
            }"#)
            .expect(2)  // We expect this endpoint to be called twice
            .create();

        // Create JwkProvider with a very short cache duration
        let provider = JwkProvider::new_with_base_url_and_refresh_interval(
            &server.url(),
            "test-issuer",
            Duration::from_millis(100), // Very short cache duration for testing
            Duration::from_secs(1),
        )
        .expect("Failed to create JwkProvider");

        // Prefetch keys
        let result = provider.prefetch_keys().await;
        assert!(result.is_ok());

        // Get a key (should be cached)
        let key1 = provider.get_key("test-key-1").await;
        assert!(key1.is_ok());

        // Wait for cache to expire
        tokio::time::sleep(Duration::from_millis(1200)).await;

        // Get the key again (should trigger a refresh)
        let key2 = provider.get_key("test-key-1").await;
        assert!(key2.is_ok());

        // Since DecodingKey doesn't implement Debug, we can't directly compare them
        // Just check that both operations succeeded
        assert!(key1.is_ok() && key2.is_ok());

        mock_server.assert();
    }
}