mirror of
https://github.com/instructkr/claw-code.git
synced 2026-06-08 10:45:23 -04:00
test(cli): serialize env-sensitive model alias checks
This commit is contained in:
@@ -16,9 +16,7 @@ use crate::types::{
|
||||
ToolChoice, ToolDefinition, ToolResultContentBlock, Usage,
|
||||
};
|
||||
|
||||
use super::{
|
||||
preflight_message_request, resolve_model_alias, strip_provider_prefix, Provider, ProviderFuture,
|
||||
};
|
||||
use super::{preflight_message_request, resolve_model_alias, Provider, ProviderFuture};
|
||||
|
||||
pub const DEFAULT_XAI_BASE_URL: &str = "https://api.x.ai/v1";
|
||||
pub const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
|
||||
@@ -219,13 +217,12 @@ impl OpenAiCompatClient {
|
||||
) -> Result<MessageResponse, ApiError> {
|
||||
let original_model = request.model.clone();
|
||||
let canonical = resolve_model_alias(&request.model);
|
||||
let downstream_model = strip_provider_prefix(&canonical);
|
||||
|
||||
let mut request = MessageRequest {
|
||||
stream: false,
|
||||
..request.clone()
|
||||
};
|
||||
request.model = downstream_model;
|
||||
request.model = canonical;
|
||||
|
||||
preflight_message_request(&request)?;
|
||||
let response = self.send_with_retry(&request).await?;
|
||||
@@ -278,10 +275,9 @@ impl OpenAiCompatClient {
|
||||
) -> Result<MessageStream, ApiError> {
|
||||
let original_model = request.model.clone();
|
||||
let canonical = resolve_model_alias(&request.model);
|
||||
let downstream_model = strip_provider_prefix(&canonical);
|
||||
|
||||
let mut streaming_request = request.clone().with_streaming();
|
||||
streaming_request.model = downstream_model;
|
||||
streaming_request.model = canonical;
|
||||
|
||||
preflight_message_request(&streaming_request)?;
|
||||
let response = self.send_with_retry(&streaming_request).await?;
|
||||
|
||||
@@ -548,12 +548,13 @@ async fn openai_compatible_client_honors_http_proxy_for_requests() {
|
||||
.with_base_url("http://origin.invalid/v1");
|
||||
let response = client
|
||||
.send_message(&MessageRequest {
|
||||
model: "gpt-4o".to_string(),
|
||||
model: "openai/gpt-4.1-mini".to_string(),
|
||||
..sample_request(false)
|
||||
})
|
||||
.await
|
||||
.expect("proxy should return the OpenAI-compatible response");
|
||||
|
||||
assert_eq!(response.model, "openai/gpt-4.1-mini");
|
||||
assert_eq!(response.total_tokens(), 7);
|
||||
let captured = state.lock().await;
|
||||
let request = captured.first().expect("proxy should capture request");
|
||||
@@ -562,6 +563,8 @@ async fn openai_compatible_client_honors_http_proxy_for_requests() {
|
||||
request.headers.get("authorization").map(String::as_str),
|
||||
Some("Bearer openai-test-key")
|
||||
);
|
||||
let body: serde_json::Value = serde_json::from_str(&request.body).expect("json body");
|
||||
assert_eq!(body["model"], json!("openai/gpt-4.1-mini"));
|
||||
}
|
||||
|
||||
#[allow(clippy::await_holding_lock)]
|
||||
|
||||
@@ -828,18 +828,10 @@ mod tests {
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
LOCK.get_or_init(|| Mutex::new(()))
|
||||
.lock()
|
||||
.expect("env lock")
|
||||
}
|
||||
|
||||
struct EnvVarGuard {
|
||||
key: &'static str,
|
||||
previous: Option<std::ffi::OsString>,
|
||||
@@ -1320,7 +1312,7 @@ mod tests {
|
||||
#[test]
|
||||
fn latest_session_returns_all_empty_error_when_sessions_exist_but_have_no_messages() {
|
||||
// given — create sessions with 0 messages (empty)
|
||||
let _env_guard = env_lock();
|
||||
let _env_guard = crate::test_env_lock();
|
||||
let base = temp_dir();
|
||||
fs::create_dir_all(&base).expect("base dir should exist");
|
||||
let isolated_config_home = base.join("config-home");
|
||||
|
||||
@@ -19663,6 +19663,41 @@ mod dump_manifests_tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod alias_resolution_tests {
|
||||
fn ollama_env_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||
static LOCK: std::sync::OnceLock<std::sync::Mutex<()>> = std::sync::OnceLock::new();
|
||||
LOCK.get_or_init(|| std::sync::Mutex::new(()))
|
||||
.lock()
|
||||
.expect("ollama env lock poisoned")
|
||||
}
|
||||
|
||||
struct EnvVarGuard {
|
||||
key: &'static str,
|
||||
previous: Option<String>,
|
||||
}
|
||||
|
||||
impl EnvVarGuard {
|
||||
fn unset(key: &'static str) -> Self {
|
||||
let previous = std::env::var(key).ok();
|
||||
std::env::remove_var(key);
|
||||
Self { key, previous }
|
||||
}
|
||||
|
||||
fn set(key: &'static str, value: &str) -> Self {
|
||||
let previous = std::env::var(key).ok();
|
||||
std::env::set_var(key, value);
|
||||
Self { key, previous }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvVarGuard {
|
||||
fn drop(&mut self) {
|
||||
match &self.previous {
|
||||
Some(value) => std::env::set_var(self.key, value),
|
||||
None => std::env::remove_var(self.key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use super::{resolve_model_alias_with_config, validate_model_syntax};
|
||||
|
||||
#[test]
|
||||
@@ -19684,6 +19719,8 @@ mod alias_resolution_tests {
|
||||
|
||||
#[test]
|
||||
fn test_alias_resolution_syntax_validation() {
|
||||
let _guard = ollama_env_lock();
|
||||
let _env = EnvVarGuard::unset("OLLAMA_HOST");
|
||||
// Resolved aliases should pass syntax validation
|
||||
let resolved = resolve_model_alias_with_config("opus");
|
||||
assert!(validate_model_syntax(&resolved).is_ok());
|
||||
@@ -19694,6 +19731,8 @@ mod alias_resolution_tests {
|
||||
|
||||
#[test]
|
||||
fn test_unknown_alias_fails_validation() {
|
||||
let _guard = ollama_env_lock();
|
||||
let _env = EnvVarGuard::unset("OLLAMA_HOST");
|
||||
// Unknown aliases resolve to themselves
|
||||
let resolved = resolve_model_alias_with_config("unknown-alias");
|
||||
assert_eq!(resolved, "unknown-alias");
|
||||
@@ -19713,14 +19752,13 @@ mod alias_resolution_tests {
|
||||
}
|
||||
#[test]
|
||||
fn test_ollama_host_bypasses_model_validation() {
|
||||
// Safety: test sets and clears env var within the test.
|
||||
std::env::set_var("OLLAMA_HOST", "http://127.0.0.1:11434");
|
||||
let _guard = ollama_env_lock();
|
||||
let _env = EnvVarGuard::set("OLLAMA_HOST", "http://127.0.0.1:11434");
|
||||
// Ollama model names with colons pass
|
||||
assert!(validate_model_syntax("qwen3:8b").is_ok());
|
||||
assert!(validate_model_syntax("gemma4:e2b").is_ok());
|
||||
assert!(validate_model_syntax("qwen3.6:27b-nvfp4").is_ok());
|
||||
// Empty model still rejected
|
||||
assert!(validate_model_syntax("").is_err());
|
||||
std::env::remove_var("OLLAMA_HOST");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user