terraphim_service 1.11.0

Terraphim service for handling user requests and responses.
Documentation
//! Tests for OpenRouter service z.ai proxy integration
//!
//! These tests verify that the OpenRouter service correctly uses the z.ai proxy
//! when ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN environment variables are set.

use std::env;
use terraphim_service::openrouter::OpenRouterService;

/// Test environment variable cleanup utility
struct TestEnv {
    original_vars: Vec<(String, Option<String>)>,
}

impl TestEnv {
    fn new() -> Self {
        Self {
            original_vars: Vec::new(),
        }
    }

    fn set_var(&mut self, key: &str, value: &str) {
        // Store original value if it exists
        self.original_vars
            .push((key.to_string(), env::var(key).ok()));
        env::set_var(key, value);
    }

    fn remove_var(&mut self, key: &str) {
        self.original_vars
            .push((key.to_string(), env::var(key).ok()));
        env::remove_var(key);
    }
}

impl Drop for TestEnv {
    fn drop(&mut self) {
        // Restore original environment variables
        for (key, original_value) in self.original_vars.drain(..) {
            match original_value {
                Some(value) => env::set_var(&key, value),
                None => env::remove_var(&key),
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_anthropic_model_with_z_ai_proxy() {
        let mut test_env = TestEnv::new();

        // Set up z.ai proxy environment variables
        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("ANTHROPIC_AUTH_TOKEN", "z-ai-auth-token-123");

        // Test Anthropic model detection
        let service = OpenRouterService::new("test-key", "anthropic/claude-3-sonnet-20240229");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Verify the service uses the z.ai proxy URL
        assert_eq!(service.base_url, "https://api.z.ai/api/anthropic");
    }

    #[test]
    fn test_claude_model_with_z_ai_proxy() {
        let mut test_env = TestEnv::new();

        // Set up z.ai proxy environment variables
        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("ANTHROPIC_AUTH_TOKEN", "z-ai-auth-token-456");

        // Test Claude model detection (different naming)
        let service = OpenRouterService::new("test-key", "claude-3-haiku-20240307");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Verify the service uses the z.ai proxy URL
        assert_eq!(service.base_url, "https://api.z.ai/api/anthropic");
    }

    #[test]
    fn test_non_anthropic_model_ignores_z_ai_proxy() {
        let mut test_env = TestEnv::new();

        // Set up z.ai proxy environment variables
        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("ANTHROPIC_AUTH_TOKEN", "z-ai-auth-token-789");

        // Test non-Anthropic model (should use OpenRouter)
        let service = OpenRouterService::new("test-key", "openai/gpt-4");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Should use default OpenRouter URL, not z.ai proxy
        assert_eq!(service.base_url, "https://openrouter.ai/api/v1");
    }

    #[test]
    fn test_openrouter_base_url_override() {
        let mut test_env = TestEnv::new();

        // Set OpenRouter base URL override
        test_env.set_var(
            "OPENROUTER_BASE_URL",
            "https://custom-openrouter.example.com/api/v1",
        );

        let service = OpenRouterService::new("test-key", "openai/gpt-3.5-turbo");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Should use the custom OpenRouter URL
        assert_eq!(
            service.base_url,
            "https://custom-openrouter.example.com/api/v1"
        );
    }

    #[test]
    fn test_z_ai_proxy_priority_over_openrouter() {
        let mut test_env = TestEnv::new();

        // Set both environment variables
        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1");

        // Test Anthropic model - should prefer z.ai proxy
        let service = OpenRouterService::new("test-key", "anthropic/claude-3-sonnet-20240229");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Should use z.ai proxy for Anthropic models
        assert_eq!(service.base_url, "https://api.z.ai/api/anthropic");

        // Test non-Anthropic model - should use OpenRouter
        let service2 = OpenRouterService::new("test-key", "openai/gpt-4");
        assert!(service2.is_ok());

        let service2 = service2.unwrap();

        // Should use OpenRouter for non-Anthropic models
        assert_eq!(service2.base_url, "https://openrouter.ai/api/v1");
    }

    #[test]
    fn test_api_key_selection_with_z_ai_proxy() {
        let mut test_env = TestEnv::new();

        // Set up z.ai proxy environment variables
        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("ANTHROPIC_AUTH_TOKEN", "z-ai-special-token");

        let service = OpenRouterService::new("fallback-key", "anthropic/claude-3-sonnet-20240229");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Test that the service would use ANTHROPIC_AUTH_TOKEN for z.ai proxy
        // We can't directly test the private method, but we can verify the URL detection
        assert!(service.base_url.contains("z.ai"));
    }

    #[test]
    fn test_no_environment_variables() {
        let mut test_env = TestEnv::new();

        // Ensure no proxy environment variables are set
        test_env.remove_var("ANTHROPIC_BASE_URL");
        test_env.remove_var("ANTHROPIC_AUTH_TOKEN");
        test_env.remove_var("OPENROUTER_BASE_URL");

        // Test default behavior
        let service = OpenRouterService::new("test-key", "anthropic/claude-3-sonnet-20240229");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Should use default OpenRouter URL when no proxy is configured
        assert_eq!(service.base_url, "https://openrouter.ai/api/v1");
    }

    #[test]
    fn test_environment_variable_validation() {
        let mut test_env = TestEnv::new();

        // Test empty ANTHROPIC_BASE_URL
        test_env.set_var("ANTHROPIC_BASE_URL", "");
        test_env.remove_var("ANTHROPIC_AUTH_TOKEN");

        let service = OpenRouterService::new("test-key", "anthropic/claude-3-sonnet-20240229");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Should fall back to default when ANTHROPIC_BASE_URL is empty
        assert_eq!(service.base_url, "https://openrouter.ai/api/v1");
    }

    #[test]
    fn test_model_name_patterns() {
        let mut test_env = TestEnv::new();

        // Set up z.ai proxy
        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("ANTHROPIC_AUTH_TOKEN", "test-token");

        // Test various Anthropic model patterns
        let test_cases = vec![
            "anthropic/claude-3-sonnet-20240229",
            "anthropic/claude-3-haiku-20240307",
            "anthropic/claude-3-opus-20240229",
            "claude-3-sonnet-20240229",
            "claude-3-haiku-20240307",
            "claude-instant-1.2",
        ];

        for model in test_cases {
            let service = OpenRouterService::new("test-key", model);
            assert!(
                service.is_ok(),
                "Failed to create service for model: {}",
                model
            );

            let service = service.unwrap();
            assert_eq!(
                service.base_url, "https://api.z.ai/api/anthropic",
                "Model {} should use z.ai proxy",
                model
            );
        }
    }

    #[test]
    fn test_non_anthropic_model_patterns() {
        let mut test_env = TestEnv::new();

        // Set up z.ai proxy
        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("ANTHROPIC_AUTH_TOKEN", "test-token");

        // Test various non-Anthropic model patterns
        let test_cases = vec![
            "openai/gpt-4",
            "openai/gpt-3.5-turbo",
            "google/gemini-pro",
            "meta-llama/llama-3-70b-instruct",
            "mistralai/mixtral-8x7b-instruct",
        ];

        for model in test_cases {
            let service = OpenRouterService::new("test-key", model);
            assert!(
                service.is_ok(),
                "Failed to create service for model: {}",
                model
            );

            let service = service.unwrap();
            assert_eq!(
                service.base_url, "https://openrouter.ai/api/v1",
                "Model {} should use OpenRouter, not z.ai proxy",
                model
            );
        }
    }

    #[test]
    fn test_service_configuration_methods() {
        let mut test_env = TestEnv::new();

        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("ANTHROPIC_AUTH_TOKEN", "test-token");

        let service = OpenRouterService::new("test-key", "anthropic/claude-3-sonnet-20240229");
        assert!(service.is_ok());

        let service = service.unwrap();

        // Test configuration methods
        assert!(service.is_configured());
        assert_eq!(service.get_model(), "anthropic/claude-3-sonnet-20240229");
    }

    #[test]
    fn test_error_cases() {
        // Test empty API key
        let service = OpenRouterService::new("", "anthropic/claude-3-sonnet-20240229");
        assert!(service.is_err());

        // Test empty model name
        let service = OpenRouterService::new("test-key", "");
        assert!(service.is_err());
    }

    #[cfg(feature = "openrouter")]
    #[tokio::test]
    async fn test_z_ai_proxy_url_detection() {
        let mut test_env = TestEnv::new();

        test_env.set_var("ANTHROPIC_BASE_URL", "https://api.z.ai/api/anthropic");
        test_env.set_var("ANTHROPIC_AUTH_TOKEN", "test-token");

        let service =
            OpenRouterService::new("test-key", "anthropic/claude-3-sonnet-20240229").unwrap();

        // We can't directly test private methods, but we can verify the URL
        assert!(service.base_url.contains("z.ai"));

        // Verify the URL format is correct for z.ai proxy
        assert!(!service.base_url.ends_with("/"));
        assert!(service.base_url.starts_with("https://"));
    }
}