mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-10-24 12:04:22 +08:00
6.7 KiB
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::