fix: 移除已删除的 Qwen 和 IFlow provider 引用

- 移除测试文件中对 ProviderType::Qwen 和 ProviderType::IFlow 的引用
- 移除 providers/tests.rs 中的 IFlowProvider 相关测试
- 更新 failover 测试使用 OpenAI 替代 Qwen
- 添加 ChatMessage 缺失的 reasoning_content 字段
- 启用 macOS 签名证书配置

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
coso
2026-01-15 22:59:30 +08:00
co-authored by Claude Opus 4.5
parent 8ead6efdc6
commit cd2e4b19f8
20 changed files with 76 additions and 167 deletions
+32 -35
View File
@@ -79,29 +79,29 @@ jobs:
- name: Install frontend dependencies
run: pnpm install --no-frozen-lockfile
# macOS 签名证书设置(暂时禁用,证书未配置)
# - name: Import Apple Certificate
# if: matrix.platform == 'macos-latest'
# env:
# APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
# APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# run: |
# # 创建临时 keychain
# KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
#
# # 解码证书
# echo -n "$APPLE_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/certificate.p12
#
# # 创建 keychain
# security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
# security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
#
# # 导入证书
# security import $RUNNER_TEMP/certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
# security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# security list-keychain -d user -s $KEYCHAIN_PATH
# macOS 签名证书设置
- name: Import Apple Certificate
if: matrix.platform == 'macos-latest'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
# 创建临时 keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=${{ secrets.KEYCHAIN_PASSWORD }}
# 解码证书
echo -n "$APPLE_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/certificate.p12
# 创建 keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# 导入证书
security import $RUNNER_TEMP/certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0
@@ -109,13 +109,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_PROFILE_RELEASE_LTO: thin
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16
# Apple 签名和公证环境变量(暂时禁用)
# APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
# APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
# APPLE_ID: ${{ secrets.APPLE_ID }}
# APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
# APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Apple 签名环境变量
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
with:
tauriScript: npx tauri
tagName: ${{ github.event.inputs.version || github.ref_name }}
@@ -143,8 +140,8 @@ jobs:
prerelease: false
args: --target ${{ matrix.target }}
# 清理 keychain(暂时禁用)
# - name: Cleanup Keychain
# if: matrix.platform == 'macos-latest' && always()
# run: |
# security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
# 清理 keychain
- name: Cleanup Keychain
if: matrix.platform == 'macos-latest' && always()
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
-2
View File
@@ -17,7 +17,6 @@ fn arb_provider_type() -> impl Strategy<Value = ProviderType> {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
]
@@ -698,7 +697,6 @@ fn arb_sync_provider_type() -> impl Strategy<Value = PoolProviderType> {
prop_oneof![
Just(PoolProviderType::Kiro),
Just(PoolProviderType::Gemini),
Just(PoolProviderType::Qwen),
Just(PoolProviderType::OpenAI),
Just(PoolProviderType::Claude),
]
-1
View File
@@ -1157,7 +1157,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
]
@@ -704,7 +704,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
]
-1
View File
@@ -1492,7 +1492,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
Just(ProviderType::Antigravity),
-1
View File
@@ -1112,7 +1112,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
Just(ProviderType::Antigravity),
@@ -1275,7 +1275,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
Just(ProviderType::Antigravity),
@@ -1028,7 +1028,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
Just(ProviderType::Antigravity),
@@ -864,7 +864,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
Just(ProviderType::Antigravity),
@@ -872,7 +871,6 @@ mod property_tests {
Just(ProviderType::GeminiApiKey),
Just(ProviderType::Codex),
Just(ProviderType::ClaudeOAuth),
Just(ProviderType::IFlow),
]
}
-2
View File
@@ -957,7 +957,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
Just(ProviderType::Antigravity),
@@ -965,7 +964,6 @@ mod property_tests {
Just(ProviderType::GeminiApiKey),
Just(ProviderType::Codex),
Just(ProviderType::ClaudeOAuth),
Just(ProviderType::IFlow),
]
}
-1
View File
@@ -1789,7 +1789,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
Just(ProviderType::Antigravity),
@@ -1036,7 +1036,6 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
Just(ProviderType::Antigravity),
+1 -1
View File
@@ -240,7 +240,7 @@ mod property_tests {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
],
"[a-z0-9-]{5,20}", // model
)
+5 -1
View File
@@ -648,7 +648,11 @@ mod tests {
ctx.set_provider(ProviderType::Kiro);
let error = ProviderCallError::failover("Rate limit exceeded", Some(429));
let available = vec![ProviderType::Kiro, ProviderType::Gemini, ProviderType::Qwen];
let available = vec![
ProviderType::Kiro,
ProviderType::Gemini,
ProviderType::OpenAI,
];
let new_provider = step.handle_failover(&ctx, &error, &available);
-1
View File
@@ -175,7 +175,6 @@ fn arb_provider_type() -> impl Strategy<Value = ProviderType> {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
]
+5 -105
View File
@@ -6,7 +6,6 @@ use chrono::{Duration, Utc};
use proptest::prelude::*;
use crate::providers::codex::CodexProvider;
use crate::providers::iflow::IFlowProvider;
use crate::providers::vertex::VertexProvider;
/// Generate a random lead time in minutes (1 to 30 minutes)
@@ -79,10 +78,10 @@ proptest! {
}
/// **Feature: cliproxyapi-parity, Property 2: Token Refresh Timing**
/// Test that iFlow OAuth tokens trigger refresh before expiration
/// Test that Codex OAuth tokens trigger refresh before expiration (second test)
/// **Validates: Requirements 1.2, 2.2**
#[test]
fn test_iflow_token_refresh_timing(
fn test_codex_token_refresh_timing_second(
lead_time_mins in arb_lead_time_mins(),
time_offset_secs in -3600i64..7200i64,
) {
@@ -92,8 +91,7 @@ proptest! {
// 跳过边界条件,因为时间精度问题可能导致不确定行为
prop_assume!(time_offset_secs != lead_time_secs);
let mut provider = IFlowProvider::new();
provider.credentials.auth_type = "oauth".to_string();
let mut provider = CodexProvider::new();
provider.credentials.access_token = Some("test_token".to_string());
// Set expiration time relative to now
@@ -109,7 +107,7 @@ proptest! {
prop_assert_eq!(
needs_refresh,
expected_needs_refresh,
"iFlow: Token with expiry in {} seconds should {} refresh with lead time of {} minutes",
"Codex: Token with expiry in {} seconds should {} refresh with lead time of {} minutes",
time_offset_secs,
if expected_needs_refresh { "need" } else { "not need" },
lead_time_mins
@@ -157,33 +155,6 @@ proptest! {
);
}
/// **Feature: cliproxyapi-parity, Property 2: Token Refresh Timing**
/// Test that iFlow cookie auth type never needs OAuth refresh
/// **Validates: Requirements 2.2**
#[test]
fn test_iflow_cookie_auth_no_refresh(
lead_time_mins in arb_lead_time_mins(),
time_offset_secs in arb_time_offset_secs(),
) {
let lead_time = Duration::minutes(lead_time_mins);
let mut provider = IFlowProvider::new();
provider.credentials.auth_type = "cookie".to_string();
provider.credentials.cookies = Some("session=abc123".to_string());
// Even with expiry set, cookie auth should not trigger OAuth refresh
let now = Utc::now();
let expires_at = now + Duration::seconds(time_offset_secs);
provider.credentials.expires_at = Some(expires_at.to_rfc3339());
let needs_refresh = provider.needs_refresh(lead_time);
prop_assert!(
!needs_refresh,
"iFlow: Cookie auth type should never need OAuth token refresh"
);
}
/// **Feature: cliproxyapi-parity, Property 2: Token Refresh Timing**
/// Test that refresh is triggered strictly before expiration time
/// This ensures the invariant: if needs_refresh(lead_time) is false,
@@ -238,8 +209,7 @@ proptest! {
///
/// This test verifies that:
/// 1. Codex provider correctly identifies GPT models (gpt-*, o1*, o3*, o4*, *codex*)
/// 2. iFlow provider correctly identifies iFlow models (iflow*, *iflow*)
/// 3. Vertex provider correctly resolves model aliases
/// 2. Vertex provider correctly resolves model aliases
#[test]
fn test_codex_provider_routing_gpt_models(
model_suffix in "[a-z0-9\\-]{1,10}",
@@ -314,49 +284,6 @@ proptest! {
);
}
/// **Feature: cliproxyapi-parity, Property 3: Provider Routing Correctness**
/// Test that iFlow provider correctly identifies iFlow models
/// **Validates: Requirements 2.3**
#[test]
fn test_iflow_provider_routing_iflow_models(
suffix in "[a-z0-9\\-]{1,10}",
) {
// Models starting with "iflow" should be supported
let iflow_model = format!("iflow-{}", suffix);
prop_assert!(
IFlowProvider::supports_model(&iflow_model),
"iFlow should support model starting with 'iflow': {}",
iflow_model
);
// Models containing "iflow" should be supported
let containing_model = format!("my-iflow-{}", suffix);
prop_assert!(
IFlowProvider::supports_model(&containing_model),
"iFlow should support model containing 'iflow': {}",
containing_model
);
}
/// **Feature: cliproxyapi-parity, Property 3: Provider Routing Correctness**
/// Test that iFlow provider does NOT support non-iFlow models
/// **Validates: Requirements 2.3**
#[test]
fn test_iflow_provider_routing_non_iflow_models(
model in prop_oneof![
Just("gpt-4"),
Just("claude-3"),
Just("gemini-pro"),
Just("llama-2"),
],
) {
prop_assert!(
!IFlowProvider::supports_model(&model),
"iFlow should NOT support non-iFlow model: {}",
model
);
}
/// **Feature: cliproxyapi-parity, Property 3: Provider Routing Correctness**
/// Test that Vertex provider correctly resolves model aliases
/// **Validates: Requirements 3.2, 3.3**
@@ -972,31 +899,4 @@ mod unit_tests {
"Expired token should need refresh"
);
}
#[test]
fn test_iflow_needs_refresh_boundary() {
let mut provider = IFlowProvider::new();
provider.credentials.auth_type = "oauth".to_string();
provider.credentials.access_token = Some("test_token".to_string());
let lead_time = Duration::minutes(5);
// Token expiring well after lead_time - should NOT need refresh
let now = Utc::now();
let expires_at = now + Duration::minutes(10);
provider.credentials.expires_at = Some(expires_at.to_rfc3339());
assert!(
!provider.needs_refresh(lead_time),
"Token expiring in 10 mins should not need refresh with 5 min lead time"
);
// Token expiring well before lead_time - should need refresh
let now = Utc::now();
let expires_at = now + Duration::minutes(2);
provider.credentials.expires_at = Some(expires_at.to_rfc3339());
assert!(
provider.needs_refresh(lead_time),
"Token expiring in 2 mins should need refresh with 5 min lead time"
);
}
}
+29 -9
View File
@@ -490,7 +490,11 @@ mod unit_tests {
#[test]
fn test_handle_failure_quota_exceeded() {
let failover = Failover::with_defaults();
let available = vec![ProviderType::Kiro, ProviderType::Gemini, ProviderType::Qwen];
let available = vec![
ProviderType::Kiro,
ProviderType::Gemini,
ProviderType::OpenAI,
];
let result = failover.handle_failure(
ProviderType::Kiro,
@@ -569,7 +573,11 @@ mod unit_tests {
#[test]
fn test_select_alternative() {
let failover = Failover::with_defaults();
let available = vec![ProviderType::Kiro, ProviderType::Gemini, ProviderType::Qwen];
let available = vec![
ProviderType::Kiro,
ProviderType::Gemini,
ProviderType::OpenAI,
];
// 排除 Kiro,应该选择 Gemini
let result = failover.select_alternative(ProviderType::Kiro, &available);
@@ -583,14 +591,18 @@ mod unit_tests {
#[test]
fn test_select_alternative_excluding() {
let failover = Failover::with_defaults();
let available = vec![ProviderType::Kiro, ProviderType::Gemini, ProviderType::Qwen];
let available = vec![
ProviderType::Kiro,
ProviderType::Gemini,
ProviderType::OpenAI,
];
let mut excluded = HashSet::new();
excluded.insert(ProviderType::Kiro);
excluded.insert(ProviderType::Gemini);
let result = failover.select_alternative_excluding(&excluded, &available);
assert_eq!(result, Some(ProviderType::Qwen));
assert_eq!(result, Some(ProviderType::OpenAI));
}
#[test]
@@ -638,7 +650,11 @@ mod manager_tests {
#[test]
fn test_failover_manager_tracks_failed_providers() {
let mut manager = FailoverManager::with_defaults();
let available = vec![ProviderType::Kiro, ProviderType::Gemini, ProviderType::Qwen];
let available = vec![
ProviderType::Kiro,
ProviderType::Gemini,
ProviderType::OpenAI,
];
// 第一次失败
let result = manager.handle_failure_and_switch(
@@ -659,12 +675,12 @@ mod manager_tests {
&available,
);
assert!(result.switched);
assert_eq!(result.new_provider, Some(ProviderType::Qwen));
assert_eq!(result.new_provider, Some(ProviderType::OpenAI));
assert!(manager.is_provider_failed(ProviderType::Gemini));
// 第三次失败(所有 Provider 都失败了)
let result = manager.handle_failure_and_switch(
ProviderType::Qwen,
ProviderType::OpenAI,
Some(429),
"Rate limit",
&available,
@@ -676,7 +692,11 @@ mod manager_tests {
#[test]
fn test_failover_manager_switch_log() {
let mut manager = FailoverManager::with_defaults();
let available = vec![ProviderType::Kiro, ProviderType::Gemini, ProviderType::Qwen];
let available = vec![
ProviderType::Kiro,
ProviderType::Gemini,
ProviderType::OpenAI,
];
// 触发两次切换
manager.handle_failure_and_switch(ProviderType::Kiro, Some(429), "Rate limit", &available);
@@ -695,7 +715,7 @@ mod manager_tests {
assert_eq!(log[0].failure_type, FailureType::QuotaExceeded);
assert_eq!(log[1].from_provider, ProviderType::Gemini);
assert_eq!(log[1].to_provider, ProviderType::Qwen);
assert_eq!(log[1].to_provider, ProviderType::OpenAI);
assert_eq!(log[1].failure_type, FailureType::ServiceUnavailable);
}
+3
View File
@@ -168,6 +168,7 @@ mod tests {
)),
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
}],
temperature: None,
max_tokens: None,
@@ -199,6 +200,7 @@ mod tests {
)),
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
}],
temperature: None,
max_tokens: None,
@@ -218,6 +220,7 @@ mod tests {
)),
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
}],
temperature: None,
max_tokens: None,
-1
View File
@@ -15,7 +15,6 @@ fn arb_provider_type() -> impl Strategy<Value = ProviderType> {
prop_oneof![
Just(ProviderType::Kiro),
Just(ProviderType::Gemini),
Just(ProviderType::Qwen),
Just(ProviderType::OpenAI),
Just(ProviderType::Claude),
]
@@ -634,6 +634,7 @@ mod tests {
content: Some(MessageContent::Text("Hello".to_string())),
tool_calls: None,
tool_call_id: None,
reasoning_content: None,
}],
tools: None,
stream: false,