diff --git a/openless-all/app/src-tauri/src/commands.rs b/openless-all/app/src-tauri/src/commands.rs index c5b76dcc..0d37f1e7 100644 --- a/openless-all/app/src-tauri/src/commands.rs +++ b/openless-all/app/src-tauri/src/commands.rs @@ -225,18 +225,17 @@ async fn fetch_provider_models(config: &ProviderConfig) -> Result, S .timeout(Duration::from_secs(15)) .build() .map_err(|e| format!("HTTP client 初始化失败: {e}"))?; - let response = client - .get(&url) - .header("Authorization", format!("Bearer {}", config.api_key)) - .send() - .await - .map_err(|e| { - if e.is_timeout() { - "请求超时".to_string() - } else { - format!("网络错误: {e}") - } - })?; + let mut request = client.get(&url); + if !config.api_key.trim().is_empty() { + request = request.header("Authorization", format!("Bearer {}", config.api_key)); + } + let response = request.send().await.map_err(|e| { + if e.is_timeout() { + "请求超时".to_string() + } else { + format!("网络错误: {e}") + } + })?; let status = response.status(); let body = response .text() @@ -558,11 +557,17 @@ fn _ensure_snapshot_used(_: CredentialsSnapshot) {} #[cfg(test)] mod tests { - use super::{models_url, parse_model_ids, persist_settings, SettingsWriter}; + use super::{ + fetch_provider_models, models_url, parse_model_ids, persist_settings, ProviderConfig, + SettingsWriter, + }; use crate::types::{ HotkeyBinding, HotkeyMode, HotkeyTrigger, QaHotkeyBinding, UserPreferences, }; + use std::io::{Read, Write}; + use std::net::TcpListener; use std::sync::Mutex; + use std::thread; #[derive(Default)] struct FakeSettingsWriter { @@ -638,4 +643,46 @@ mod tests { assert_eq!(*writer.dictation_refreshes.lock().unwrap(), 1); assert_eq!(*writer.qa_refreshes.lock().unwrap(), 1); } + + #[tokio::test] + async fn fetch_provider_models_omits_authorization_when_api_key_is_empty() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + + let server = thread::spawn(move || { + let (mut stream, _) = listener.accept().unwrap(); + let mut buf = [0u8; 8192]; + let mut request = Vec::new(); + loop { + let n = stream.read(&mut buf).unwrap(); + if n == 0 { + break; + } + request.extend_from_slice(&buf[..n]); + if request.windows(4).any(|w| w == b"\r\n\r\n") { + break; + } + } + let request_text = String::from_utf8_lossy(&request); + assert!(!request_text.contains("Authorization: Bearer")); + + let body = r#"{"data":[{"id":"m1"},{"id":"m2"}]}"#; + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body + ); + stream.write_all(response.as_bytes()).unwrap(); + }); + + let models = fetch_provider_models(&ProviderConfig { + base_url: format!("http://{}", addr), + api_key: String::new(), + }) + .await + .unwrap(); + + assert_eq!(models, vec!["m1".to_string(), "m2".to_string()]); + server.join().unwrap(); + } }