use crate::brain::provider::CodexCliProvider;
use crate::brain::provider::Provider;
#[test]
fn default_model_is_gpt55() {
let Ok(p) = CodexCliProvider::new() else {
return;
};
assert_eq!(p.default_model(), "gpt-5.5");
}
#[test]
fn with_default_model_overrides() {
let Ok(p) = CodexCliProvider::new() else {
return;
};
let p = p.with_default_model("gpt-5.3-codex".to_string());
assert_eq!(p.default_model(), "gpt-5.3-codex");
}
#[test]
fn supported_models_includes_recommended_set() {
let Ok(p) = CodexCliProvider::new() else {
return;
};
let models = p.supported_models();
assert!(models.iter().any(|m| m == "gpt-5.5"));
assert!(models.iter().any(|m| m == "gpt-5.4"));
assert!(models.iter().any(|m| m == "gpt-5.4-mini"));
assert!(models.iter().any(|m| m == "gpt-5.3-codex"));
}
#[test]
fn capability_flags_match_cli_subprocess_pattern() {
let Ok(p) = CodexCliProvider::new() else {
return;
};
assert!(p.cli_handles_tools());
assert!(!p.cli_manages_context());
assert!(!p.supports_vision());
}
#[test]
fn name_is_codex_cli() {
let Ok(p) = CodexCliProvider::new() else {
return;
};
assert_eq!(p.name(), "codex-cli");
}
use crate::brain::provider::codex_cli::{classify_codex_failure, codex_exit_failure};
use crate::brain::provider::error::ProviderError;
#[test]
fn turn_failed_overload_signals_classify_as_rate_limit() {
for msg in [
"Our servers are currently overloaded. Please try again later.",
"rate limit reached for this account",
"quota exceeded",
"HTTP 429: too many requests",
"server capacity unavailable",
] {
assert!(
matches!(
classify_codex_failure(msg, "codex_turn_failed"),
ProviderError::RateLimitExceeded(_)
),
"expected RateLimitExceeded for: {msg}"
);
}
}
#[test]
fn turn_failed_context_length_signals_classify_as_context_length() {
for msg in [
"context length exceeded",
"too many tokens in the request",
"prompt is too long",
] {
assert!(
matches!(
classify_codex_failure(msg, "codex_turn_failed"),
ProviderError::ContextLengthExceeded(_)
),
"expected ContextLengthExceeded for: {msg}"
);
}
}
#[test]
fn turn_failed_generic_maps_to_retryable_500() {
let err = classify_codex_failure("boom: something exploded", "codex_turn_failed");
assert!(
err.is_retryable(),
"generic turn failure must stay in the retry budget"
);
match err {
ProviderError::ApiError {
status, error_type, ..
} => {
assert_eq!(status, 500);
assert_eq!(error_type.as_deref(), Some("codex_turn_failed"));
}
_ => panic!("expected ApiError for generic turn failure"),
}
}
#[test]
fn exit_with_stderr_signal_classifies_like_turn_failure() {
let err = codex_exit_failure(
"exit status: 1",
"some banner\nOur servers are currently overloaded. Please try again later.\n",
);
assert!(
matches!(err, ProviderError::RateLimitExceeded(_)),
"stderr overload signal must classify as RateLimitExceeded"
);
}
#[test]
fn silent_exit_is_transient_not_internal() {
let err = codex_exit_failure("exit status: 1", " ");
assert!(
matches!(err, ProviderError::StreamError(_)),
"silent exit must be a transient StreamError"
);
assert!(
err.is_retryable(),
"silent exit must reach the retry budget and fallback chain"
);
}