Skip to content

Commit a1da1ca

Browse files
committed
test(cli): serialize env-sensitive model alias checks
1 parent 27acfe1 commit a1da1ca

4 files changed

Lines changed: 49 additions & 20 deletions

File tree

rust/crates/api/src/providers/openai_compat.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ use crate::types::{
1616
ToolChoice, ToolDefinition, ToolResultContentBlock, Usage,
1717
};
1818

19-
use super::{
20-
preflight_message_request, resolve_model_alias, strip_provider_prefix, Provider, ProviderFuture,
21-
};
19+
use super::{preflight_message_request, resolve_model_alias, Provider, ProviderFuture};
2220

2321
pub const DEFAULT_XAI_BASE_URL: &str = "https://api.x.ai/v1";
2422
pub const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
@@ -219,13 +217,12 @@ impl OpenAiCompatClient {
219217
) -> Result<MessageResponse, ApiError> {
220218
let original_model = request.model.clone();
221219
let canonical = resolve_model_alias(&request.model);
222-
let downstream_model = strip_provider_prefix(&canonical);
223220

224221
let mut request = MessageRequest {
225222
stream: false,
226223
..request.clone()
227224
};
228-
request.model = downstream_model;
225+
request.model = canonical;
229226

230227
preflight_message_request(&request)?;
231228
let response = self.send_with_retry(&request).await?;
@@ -278,10 +275,9 @@ impl OpenAiCompatClient {
278275
) -> Result<MessageStream, ApiError> {
279276
let original_model = request.model.clone();
280277
let canonical = resolve_model_alias(&request.model);
281-
let downstream_model = strip_provider_prefix(&canonical);
282278

283279
let mut streaming_request = request.clone().with_streaming();
284-
streaming_request.model = downstream_model;
280+
streaming_request.model = canonical;
285281

286282
preflight_message_request(&streaming_request)?;
287283
let response = self.send_with_retry(&streaming_request).await?;

rust/crates/api/tests/openai_compat_integration.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,12 +548,13 @@ async fn openai_compatible_client_honors_http_proxy_for_requests() {
548548
.with_base_url("http://origin.invalid/v1");
549549
let response = client
550550
.send_message(&MessageRequest {
551-
model: "gpt-4o".to_string(),
551+
model: "openai/gpt-4.1-mini".to_string(),
552552
..sample_request(false)
553553
})
554554
.await
555555
.expect("proxy should return the OpenAI-compatible response");
556556

557+
assert_eq!(response.model, "openai/gpt-4.1-mini");
557558
assert_eq!(response.total_tokens(), 7);
558559
let captured = state.lock().await;
559560
let request = captured.first().expect("proxy should capture request");
@@ -562,6 +563,8 @@ async fn openai_compatible_client_honors_http_proxy_for_requests() {
562563
request.headers.get("authorization").map(String::as_str),
563564
Some("Bearer openai-test-key")
564565
);
566+
let body: serde_json::Value = serde_json::from_str(&request.body).expect("json body");
567+
assert_eq!(body["model"], json!("openai/gpt-4.1-mini"));
565568
}
566569

567570
#[allow(clippy::await_holding_lock)]

rust/crates/runtime/src/session_control.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -828,18 +828,10 @@ mod tests {
828828
use std::fs;
829829
use std::path::{Path, PathBuf};
830830
use std::sync::atomic::{AtomicU64, Ordering};
831-
use std::sync::{Mutex, OnceLock};
832831
use std::time::{SystemTime, UNIX_EPOCH};
833832

834833
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
835834

836-
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
837-
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
838-
LOCK.get_or_init(|| Mutex::new(()))
839-
.lock()
840-
.expect("env lock")
841-
}
842-
843835
struct EnvVarGuard {
844836
key: &'static str,
845837
previous: Option<std::ffi::OsString>,
@@ -1320,7 +1312,7 @@ mod tests {
13201312
#[test]
13211313
fn latest_session_returns_all_empty_error_when_sessions_exist_but_have_no_messages() {
13221314
// given — create sessions with 0 messages (empty)
1323-
let _env_guard = env_lock();
1315+
let _env_guard = crate::test_env_lock();
13241316
let base = temp_dir();
13251317
fs::create_dir_all(&base).expect("base dir should exist");
13261318
let isolated_config_home = base.join("config-home");

rust/crates/rusty-claude-cli/src/main.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19663,6 +19663,41 @@ mod dump_manifests_tests {
1966319663

1966419664
#[cfg(test)]
1966519665
mod alias_resolution_tests {
19666+
fn ollama_env_lock() -> std::sync::MutexGuard<'static, ()> {
19667+
static LOCK: std::sync::OnceLock<std::sync::Mutex<()>> = std::sync::OnceLock::new();
19668+
LOCK.get_or_init(|| std::sync::Mutex::new(()))
19669+
.lock()
19670+
.expect("ollama env lock poisoned")
19671+
}
19672+
19673+
struct EnvVarGuard {
19674+
key: &'static str,
19675+
previous: Option<String>,
19676+
}
19677+
19678+
impl EnvVarGuard {
19679+
fn unset(key: &'static str) -> Self {
19680+
let previous = std::env::var(key).ok();
19681+
std::env::remove_var(key);
19682+
Self { key, previous }
19683+
}
19684+
19685+
fn set(key: &'static str, value: &str) -> Self {
19686+
let previous = std::env::var(key).ok();
19687+
std::env::set_var(key, value);
19688+
Self { key, previous }
19689+
}
19690+
}
19691+
19692+
impl Drop for EnvVarGuard {
19693+
fn drop(&mut self) {
19694+
match &self.previous {
19695+
Some(value) => std::env::set_var(self.key, value),
19696+
None => std::env::remove_var(self.key),
19697+
}
19698+
}
19699+
}
19700+
1966619701
use super::{resolve_model_alias_with_config, validate_model_syntax};
1966719702

1966819703
#[test]
@@ -19684,6 +19719,8 @@ mod alias_resolution_tests {
1968419719

1968519720
#[test]
1968619721
fn test_alias_resolution_syntax_validation() {
19722+
let _guard = ollama_env_lock();
19723+
let _env = EnvVarGuard::unset("OLLAMA_HOST");
1968719724
// Resolved aliases should pass syntax validation
1968819725
let resolved = resolve_model_alias_with_config("opus");
1968919726
assert!(validate_model_syntax(&resolved).is_ok());
@@ -19694,6 +19731,8 @@ mod alias_resolution_tests {
1969419731

1969519732
#[test]
1969619733
fn test_unknown_alias_fails_validation() {
19734+
let _guard = ollama_env_lock();
19735+
let _env = EnvVarGuard::unset("OLLAMA_HOST");
1969719736
// Unknown aliases resolve to themselves
1969819737
let resolved = resolve_model_alias_with_config("unknown-alias");
1969919738
assert_eq!(resolved, "unknown-alias");
@@ -19713,14 +19752,13 @@ mod alias_resolution_tests {
1971319752
}
1971419753
#[test]
1971519754
fn test_ollama_host_bypasses_model_validation() {
19716-
// Safety: test sets and clears env var within the test.
19717-
std::env::set_var("OLLAMA_HOST", "http://127.0.0.1:11434");
19755+
let _guard = ollama_env_lock();
19756+
let _env = EnvVarGuard::set("OLLAMA_HOST", "http://127.0.0.1:11434");
1971819757
// Ollama model names with colons pass
1971919758
assert!(validate_model_syntax("qwen3:8b").is_ok());
1972019759
assert!(validate_model_syntax("gemma4:e2b").is_ok());
1972119760
assert!(validate_model_syntax("qwen3.6:27b-nvfp4").is_ok());
1972219761
// Empty model still rejected
1972319762
assert!(validate_model_syntax("").is_err());
19724-
std::env::remove_var("OLLAMA_HOST");
1972519763
}
1972619764
}

0 commit comments

Comments
 (0)