Files
proxycast/docs/test/test-cases/provider-tests.md
T
2026-01-30 01:31:09 +08:00

6.7 KiB

Provider 测试用例

OAuth 认证和 API 调用的测试用例

概述

Provider 模块负责与各个 AI 服务提供商的交互,包括:

  • OAuth 认证流程
  • Token 刷新
  • API 调用
  • 错误处理

测试用例

1. Kiro Provider

TC-KIRO-001: 凭证加载

#[test]
fn test_kiro_credential_loading() {
    let credential_json = r#"{
        "access_token": "test-access",
        "refresh_token": "test-refresh",
        "expires_at": "2026-01-30T12:00:00Z"
    }"#;
    
    let cred = KiroCredential::from_json(credential_json).unwrap();
    
    assert_eq!(cred.access_token, "test-access");
    assert_eq!(cred.refresh_token, "test-refresh");
}

TC-KIRO-002: Token 刷新

#[tokio::test]
async fn test_kiro_token_refresh() {
    let mock_server = setup_mock_oauth_server().await;
    let provider = KiroProvider::new_with_endpoint(&mock_server.uri());
    
    let new_token = provider.refresh_token("old-refresh-token").await.unwrap();
    
    assert!(!new_token.access_token.is_empty());
    assert!(new_token.expires_at > Utc::now());
}

TC-KIRO-003: 过期 Token 检测

#[test]
fn test_kiro_token_expiry_check() {
    let expired_cred = KiroCredential {
        access_token: "test".into(),
        refresh_token: "test".into(),
        expires_at: Utc::now() - Duration::hours(1),
    };
    
    assert!(expired_cred.is_expired());
    
    let valid_cred = KiroCredential {
        access_token: "test".into(),
        refresh_token: "test".into(),
        expires_at: Utc::now() + Duration::hours(1),
    };
    
    assert!(!valid_cred.is_expired());
}

2. Gemini Provider

TC-GEMINI-001: OAuth 流程

#[tokio::test]
async fn test_gemini_oauth_flow() {
    let mock_server = setup_mock_google_oauth().await;
    let provider = GeminiProvider::new_with_endpoint(&mock_server.uri());
    
    let auth_url = provider.get_auth_url();
    
    assert!(auth_url.contains("accounts.google.com"));
    assert!(auth_url.contains("scope="));
}

TC-GEMINI-002: API 调用

#[tokio::test]
async fn test_gemini_api_call() {
    let mock_server = setup_mock_gemini_api().await;
    let provider = GeminiProvider::new_with_endpoint(&mock_server.uri());
    
    let response = provider.chat(&[
        Message { role: "user".into(), content: "Hello".into() }
    ]).await.unwrap();
    
    assert!(!response.content.is_empty());
}

3. OpenAI Provider

TC-OPENAI-001: API Key 验证

#[test]
fn test_openai_api_key_validation() {
    // 有效的 API Key
    assert!(OpenAIProvider::validate_api_key("sk-1234567890abcdef"));
    
    // 无效的 API Key
    assert!(!OpenAIProvider::validate_api_key("invalid"));
    assert!(!OpenAIProvider::validate_api_key(""));
}

TC-OPENAI-002: 流式响应处理

#[tokio::test]
async fn test_openai_streaming() {
    let mock_server = setup_mock_openai_streaming().await;
    let provider = OpenAIProvider::new_with_endpoint(&mock_server.uri());
    
    let mut stream = provider.chat_stream(&[
        Message { role: "user".into(), content: "Hello".into() }
    ]).await.unwrap();
    
    let mut chunks = Vec::new();
    while let Some(chunk) = stream.next().await {
        chunks.push(chunk);
    }
    
    assert!(!chunks.is_empty());
}

4. 错误处理

TC-PROV-ERR-001: 网络错误

#[tokio::test]
async fn test_network_error_handling() {
    let provider = KiroProvider::new_with_endpoint("http://invalid-host:9999");
    
    let result = provider.refresh_token("test").await;
    
    assert!(result.is_err());
    assert!(matches!(result.unwrap_err(), ProviderError::NetworkError(_)));
}

TC-PROV-ERR-002: 认证错误

#[tokio::test]
async fn test_auth_error_handling() {
    let mock_server = setup_mock_oauth_error(401).await;
    let provider = KiroProvider::new_with_endpoint(&mock_server.uri());
    
    let result = provider.refresh_token("invalid-token").await;
    
    assert!(result.is_err());
    assert!(matches!(result.unwrap_err(), ProviderError::AuthError(_)));
}

TC-PROV-ERR-003: 速率限制

#[tokio::test]
async fn test_rate_limit_handling() {
    let mock_server = setup_mock_rate_limit().await;
    let provider = OpenAIProvider::new_with_endpoint(&mock_server.uri());
    
    let result = provider.chat(&[
        Message { role: "user".into(), content: "Hello".into() }
    ]).await;
    
    assert!(result.is_err());
    assert!(matches!(result.unwrap_err(), ProviderError::RateLimited(_)));
}

5. 凭证池集成

TC-POOL-001: 凭证轮询

#[tokio::test]
async fn test_credential_rotation() {
    let pool = CredentialPool::new();
    
    pool.add(create_credential("cred1")).await;
    pool.add(create_credential("cred2")).await;
    
    let first = pool.get_next().await.unwrap();
    let second = pool.get_next().await.unwrap();
    let third = pool.get_next().await.unwrap();
    
    assert_ne!(first.id, second.id);
    assert_eq!(first.id, third.id); // 回到第一个
}

TC-POOL-002: 健康检查

#[tokio::test]
async fn test_health_check() {
    let pool = CredentialPool::new();
    let cred = create_credential("test");
    
    pool.add(cred.clone()).await;
    
    // 标记为不健康
    pool.mark_unhealthy(&cred.id).await;
    
    // 不应该返回不健康的凭证
    let result = pool.get_next().await;
    assert!(result.is_none());
}

测试矩阵

测试 ID Provider 场景 优先级
TC-KIRO-001 Kiro 凭证加载 P0
TC-KIRO-002 Kiro Token 刷新 P0
TC-GEMINI-001 Gemini OAuth 流程 P0
TC-OPENAI-001 OpenAI API Key 验证 P0
TC-PROV-ERR-001 通用 网络错误 P1
TC-POOL-001 凭证池 轮询 P0

Mock 服务设置

async fn setup_mock_oauth_server() -> MockServer {
    let server = MockServer::start().await;
    
    Mock::given(method("POST"))
        .and(path("/oauth/token"))
        .respond_with(ResponseTemplate::new(200)
            .set_body_json(json!({
                "access_token": "new-access-token",
                "refresh_token": "new-refresh-token",
                "expires_in": 3600
            })))
        .mount(&server)
        .await;
    
    server
}

async fn setup_mock_oauth_error(status: u16) -> MockServer {
    let server = MockServer::start().await;
    
    Mock::given(method("POST"))
        .and(path("/oauth/token"))
        .respond_with(ResponseTemplate::new(status)
            .set_body_json(json!({
                "error": "invalid_grant",
                "error_description": "Token expired"
            })))
        .mount(&server)
        .await;
    
    server
}

运行测试

cd src-tauri && cargo test provider::