|
1 | 1 | //! ai_client.rs 单测(AAA 模式;payload/解析纯函数——网络路径不单测)。 |
2 | 2 |
|
| 3 | +use std::sync::Mutex; |
| 4 | + |
3 | 5 | use crate::ai_client::{ |
4 | 6 | build_chat_payload, chat_completions_url, extract_content, parse_json_object, AiClient, |
5 | 7 | AiClientConfig, AiClientError, |
6 | 8 | }; |
7 | 9 | use crate::ai_settings::AiSettings; |
8 | 10 |
|
| 11 | +/// env 操作互斥(防并行测试互相覆盖 SILICONFLOW_*——与 ai_cost_tests 同模式)。 |
| 12 | +static ENV_LOCK: Mutex<()> = Mutex::new(()); |
| 13 | + |
| 14 | +/// 在锁内执行 env 敏感操作(清除后执行闭包;保存/还原宿主环境)。 |
| 15 | +fn with_env_locked(f: impl FnOnce()) { |
| 16 | + let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); |
| 17 | + let keys = [ |
| 18 | + "SILICONFLOW_API_KEY", |
| 19 | + "SILICONFLOW_BASE_URL", |
| 20 | + "SILICONFLOW_MODEL", |
| 21 | + "SILICONFLOW_TIMEOUT_SECS", |
| 22 | + "SILICONFLOW_RETRIES", |
| 23 | + "SILICONFLOW_MAX_TOKENS", |
| 24 | + ]; |
| 25 | + let saved: Vec<(String, Option<String>)> = |
| 26 | + keys.iter().map(|k| (k.to_string(), std::env::var(k).ok())).collect(); |
| 27 | + // 清除所有相关 env(防宿主机泄漏干扰测试——闭包内 env 操作纯净) |
| 28 | + for k in &keys { |
| 29 | + std::env::remove_var(k); |
| 30 | + } |
| 31 | + f(); |
| 32 | + for (k, v) in saved { |
| 33 | + match v { |
| 34 | + Some(val) => std::env::set_var(k, val), |
| 35 | + None => std::env::remove_var(k), |
| 36 | + } |
| 37 | + } |
| 38 | +} |
| 39 | + |
9 | 40 | #[test] |
10 | 41 | fn chat_url_preserves_v1_segment() { |
11 | 42 | // 审查回归(2026-08-21):/v1 不得被 trim——默认端点 + 自定义端点均保留 |
@@ -82,50 +113,35 @@ fn parse_json_object_invalid_is_parse_error() { |
82 | 113 |
|
83 | 114 | #[test] |
84 | 115 | fn from_settings_resolves_defaults() { |
85 | | - // Arrange:清除相关环境变量(防宿主机泄漏——本机 User 级已设 DeepSeek |
86 | | - // 配置;沿用 ai_text_filter_tests 的 save/remove/restore 模式,末尾还原) |
87 | | - let keys = [ |
88 | | - "SILICONFLOW_API_KEY", |
89 | | - "SILICONFLOW_BASE_URL", |
90 | | - "SILICONFLOW_MODEL", |
91 | | - "SILICONFLOW_TIMEOUT_SECS", |
92 | | - "SILICONFLOW_RETRIES", |
93 | | - "SILICONFLOW_MAX_TOKENS", |
94 | | - ]; |
95 | | - let saved: Vec<(String, Option<String>)> = |
96 | | - keys.iter().map(|k| (k.to_string(), std::env::var(k).ok())).collect(); |
97 | | - for k in &keys { |
98 | | - std::env::remove_var(k); |
99 | | - } |
100 | | - // Act:无 env/凭据 → 设置值;密钥为空 |
101 | | - let s = AiSettings::default(); |
102 | | - let c = AiClient::from_settings(&s, None).config; |
103 | | - // Assert:2026-08-21 真机排查后默认值——长生成超时 300s + 输出上限 20000 token |
104 | | - assert_eq!(c.base_url, s.base_url); |
105 | | - assert_eq!(c.model, s.model); |
106 | | - assert!(c.api_key.is_empty()); |
107 | | - assert_eq!(c.timeout_secs, 300); |
108 | | - assert_eq!(c.max_retries, 2); |
109 | | - assert_eq!(c.max_tokens, 20000); |
110 | | - // env 覆盖 max_tokens(顺序执行,无并行竞争) |
111 | | - std::env::set_var("SILICONFLOW_MAX_TOKENS", "4096"); |
112 | | - let c2 = AiClient::from_settings(&AiSettings::default(), None).config; |
113 | | - assert_eq!(c2.max_tokens, 4096); |
114 | | - // 清理(还原宿主环境,防污染其他测试) |
115 | | - for (k, v) in saved { |
116 | | - match v { |
117 | | - Some(val) => std::env::set_var(k, val), |
118 | | - None => std::env::remove_var(k), |
119 | | - } |
120 | | - } |
| 116 | + // 测试隔离(与 ai_cost_tests 同模式):锁内清除/还原宿主环境变量 |
| 117 | + with_env_locked(|| { |
| 118 | + // Act:无 env/凭据 → 设置值;密钥为空 |
| 119 | + let s = AiSettings::default(); |
| 120 | + let c = AiClient::from_settings(&s, None).config; |
| 121 | + // Assert:2026-08-21 真机排查后默认值——长生成超时 300s + 输出上限 20000 token |
| 122 | + assert_eq!(c.base_url, s.base_url); |
| 123 | + assert_eq!(c.model, s.model); |
| 124 | + assert!(c.api_key.is_empty()); |
| 125 | + assert_eq!(c.timeout_secs, 300); |
| 126 | + assert_eq!(c.max_retries, 2); |
| 127 | + assert_eq!(c.max_tokens, 20000); |
| 128 | + // env 覆盖 max_tokens(锁内顺序执行,无并行竞争) |
| 129 | + std::env::set_var("SILICONFLOW_MAX_TOKENS", "4096"); |
| 130 | + let c2 = AiClient::from_settings(&AiSettings::default(), None).config; |
| 131 | + assert_eq!(c2.max_tokens, 4096); |
| 132 | + }); |
121 | 133 | } |
122 | 134 |
|
123 | 135 | #[test] |
124 | 136 | fn from_settings_uses_stored_key_when_no_env() { |
125 | | - // 凭据库密钥生效(无 env 时) |
126 | | - let s = AiSettings::default(); |
127 | | - let c = AiClient::from_settings(&s, Some("sk-stored".to_string())).config; |
128 | | - assert_eq!(c.api_key, "sk-stored"); |
| 137 | + // 测试隔离(与 ai_cost_tests 同模式):锁内清除宿主泄漏的 SILICONFLOW_API_KEY |
| 138 | + // (本机 User 级已设真实密钥)——无 env 才验证凭据库密钥 |
| 139 | + with_env_locked(|| { |
| 140 | + // 凭据库密钥生效(无 env 时) |
| 141 | + let s = AiSettings::default(); |
| 142 | + let c = AiClient::from_settings(&s, Some("sk-stored".to_string())).config; |
| 143 | + assert_eq!(c.api_key, "sk-stored"); |
| 144 | + }); |
129 | 145 | } |
130 | 146 |
|
131 | 147 | #[test] |
|
0 commit comments