use crate::brain::provider::{
Provider, claude_cli::ClaudeCliProvider, opencode_cli::OpenCodeCliProvider,
};
use crate::utils::providers::cli_supported_models;
#[test]
fn claude_cli_menu_matches_provider_supported_models() {
let (menu_models, _) =
cli_supported_models("claude-cli").expect("claude-cli must be a known CLI provider");
assert_eq!(
menu_models,
crate::brain::provider::claude_cli::available_models(),
"menu and provider must read from the same discovery function"
);
for m in crate::brain::provider::claude_cli::SUPPORTED_MODELS {
assert!(
menu_models.iter().any(|x| x == m),
"discovery dropped the built-in model '{m}' — the const must stay a floor"
);
}
}
#[test]
fn opencode_cli_menu_matches_provider_supported_models() {
let (menu_models, _) =
cli_supported_models("opencode-cli").expect("opencode-cli must be a known CLI provider");
let provider_models: Vec<String> = crate::brain::provider::opencode_cli::SUPPORTED_MODELS
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(
menu_models, provider_models,
"menu and provider must read from the same SUPPORTED_MODELS const"
);
}
#[test]
fn command_code_cli_menu_matches_provider_supported_models() {
let (menu_models, default) = cli_supported_models("command-code-cli")
.expect("command-code-cli must be a known CLI provider");
let provider_models: Vec<String> = crate::brain::provider::command_code_cli::SUPPORTED_MODELS
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(
menu_models, provider_models,
"menu and provider must read from the same SUPPORTED_MODELS const"
);
assert!(
menu_models.iter().any(|m| m == default),
"default {default} must appear in the menu list"
);
assert!(
!menu_models.iter().any(|m| m == "taste-1"),
"taste-1 is not a real Command Code model"
);
}
#[test]
fn underscore_alias_resolves_same_list() {
let (hyphen, _) = cli_supported_models("opencode-cli").unwrap();
let (underscore, _) = cli_supported_models("opencode_cli").unwrap();
assert_eq!(
hyphen, underscore,
"hyphen and underscore aliases must resolve to the same canonical list"
);
let (hyphen_c, _) = cli_supported_models("claude-cli").unwrap();
let (underscore_c, _) = cli_supported_models("claude_cli").unwrap();
assert_eq!(hyphen_c, underscore_c);
let (hyphen_cc, _) = cli_supported_models("command-code-cli").unwrap();
let (underscore_cc, _) = cli_supported_models("command_code_cli").unwrap();
let (short_cc, _) = cli_supported_models("cmd-cli").unwrap();
assert_eq!(hyphen_cc, underscore_cc);
assert_eq!(hyphen_cc, short_cc);
}
#[test]
fn unknown_provider_returns_none() {
assert!(cli_supported_models("openai").is_none());
assert!(cli_supported_models("qwen").is_none());
assert!(cli_supported_models("anthropic").is_none());
assert!(cli_supported_models("").is_none());
}
#[test]
fn opencode_cli_does_not_list_claude_names() {
let (models, default) = cli_supported_models("opencode-cli").unwrap();
for m in &models {
assert!(
!m.contains("sonnet") && !m.contains("opus") && !m.contains("haiku"),
"OpenCode CLI must not list Claude model '{m}' — regression of 2026-05-27 bug"
);
}
assert!(
default.starts_with("opencode/"),
"OpenCode CLI default must be an opencode/* model, got: {default}"
);
}
#[test]
fn claude_cli_default_is_real_claude_model() {
let (_, default) = cli_supported_models("claude-cli").unwrap();
assert!(
default.starts_with("opus")
|| default.starts_with("sonnet")
|| default.starts_with("haiku"),
"Claude CLI default must be a real Claude model, got: {default}"
);
}
#[test]
fn claude_cli_trait_supported_models_uses_discovery() {
if let Ok(p) = ClaudeCliProvider::new() {
let trait_models = p.supported_models();
assert_eq!(
trait_models,
crate::brain::provider::claude_cli::available_models()
);
for m in crate::brain::provider::claude_cli::SUPPORTED_MODELS {
assert!(
trait_models.iter().any(|x| x == m),
"provider dropped the built-in model '{m}'"
);
}
}
}
#[test]
fn opencode_cli_trait_supported_models_uses_const() {
if let Ok(p) = OpenCodeCliProvider::new() {
let trait_models = p.supported_models();
let const_models: Vec<String> = crate::brain::provider::opencode_cli::SUPPORTED_MODELS
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(trait_models, const_models);
}
}
const REAL_HELP: &str = r#"Options:
--agent <agent> Agent for the current session.
--model <model> Model for the current session. Provide
an alias for the latest model (e.g.
'fable', 'opus', or 'sonnet') or a
model's full name (e.g.
'claude-fable-5').
-n, --name <name> Set a display name for this session
(shown in the prompt box)
"#;
#[test]
fn help_parser_extracts_aliases_and_full_names() {
let got = crate::brain::provider::claude_cli::parse_models_from_help(REAL_HELP);
for expected in ["fable", "opus", "sonnet", "claude-fable-5"] {
assert!(
got.iter().any(|m| m == expected),
"missing {expected}: {got:?}"
);
}
}
#[test]
fn help_parser_rejects_prose_from_the_shifted_apostrophe() {
let got = crate::brain::provider::claude_cli::parse_models_from_help(REAL_HELP);
for m in &got {
assert!(!m.contains(' '), "prose leaked into the model list: {m:?}");
assert!(!m.contains("full name"), "prose leaked: {m:?}");
}
assert!(!got.iter().any(|m| m == "session"), "{got:?}");
}
#[test]
fn help_parser_stops_at_the_next_option_and_handles_missing_block() {
assert!(
crate::brain::provider::claude_cli::parse_models_from_help("Options:\n -v, --version\n")
.is_empty()
);
}
#[test]
fn discovery_never_offers_non_model_sentinels() {
let models = crate::brain::provider::claude_cli::available_models();
for m in &models {
assert!(
!m.starts_with('<') && !m.contains(' ') && !m.is_empty(),
"non-model sentinel leaked into the menu: {m:?}"
);
}
}
#[test]
fn discovery_normalizes_and_dedupes_spellings() {
let models = crate::brain::provider::claude_cli::available_models();
for m in &models {
assert!(
!m.starts_with("claude-"),
"un-normalized id leaked into the menu: {m:?}"
);
assert!(!m.contains('['), "variant suffix leaked: {m:?}");
}
let mut sorted = models.clone();
sorted.sort();
let before = sorted.len();
sorted.dedup();
assert_eq!(before, sorted.len(), "duplicate models offered: {models:?}");
}