use pretty_assertions::assert_eq;
use std::sync::LazyLock;
use super::*;
fn default_keybindings() -> &'static crate::keybindings::Keybindings {
static KEYS: LazyLock<crate::keybindings::Keybindings> =
LazyLock::new(crate::keybindings::Keybindings::default);
&KEYS
}
fn inputs(
current: InternalAgentSelection,
conversation_model: ConversationModelRow,
claude_code: ClaudeCodeRows,
) -> InternalAgentPickerInputs<'static> {
InternalAgentPickerInputs {
agent_id: "advisor",
current,
conversation_model,
claude_code,
favorite_models: &[],
available_auths: &[],
scope: ModelPickerScope::All,
keybindings: default_keybindings(),
}
}
fn labels(picker: &UiPicker) -> Vec<&str> {
picker
.items
.iter()
.map(|item| item.label.as_str())
.collect()
}
fn selected_label(picker: &UiPicker) -> &str {
picker.items[picker.selected].label.as_str()
}
#[test]
fn claude_code_rows_appear_only_when_the_agent_can_delegate() {
let offered = internal_agent_model_picker(inputs(
InternalAgentSelection::Unset,
ConversationModelRow::Omitted,
ClaudeCodeRows::Offered,
));
let expected_labels = std::iter::once("claude-code/default".to_string())
.chain(
crate::claude_runtime::models::CLAUDE_MODEL_ALIASES
.iter()
.map(|alias| format!("claude-code/{}", alias.name)),
)
.collect::<Vec<_>>();
assert_eq!(
labels(&offered),
expected_labels
.iter()
.map(String::as_str)
.collect::<Vec<_>>()
);
let omitted = internal_agent_model_picker(inputs(
InternalAgentSelection::Unset,
ConversationModelRow::Omitted,
ClaudeCodeRows::Omitted,
));
assert_eq!(labels(&omitted), Vec::<&str>::new());
}
#[test]
fn the_picker_opens_on_the_configured_row() {
let cases = [
(
InternalAgentSelection::ClaudeCode {
model: Some("sonnet".into()),
},
ConversationModelRow::Omitted,
"claude-code/sonnet",
),
(
InternalAgentSelection::ClaudeCode { model: None },
ConversationModelRow::Omitted,
"claude-code/default",
),
(
InternalAgentSelection::ClaudeCode {
model: Some("claude-opus-4-6".into()),
},
ConversationModelRow::Omitted,
"claude-code/claude-opus-4-6",
),
(
InternalAgentSelection::Unset,
ConversationModelRow::Offered { selected: true },
USE_CONVERSATION_MODEL,
),
(
InternalAgentSelection::RhoModel {
provider: "anthropic".into(),
model: "claude-fable-5".into(),
},
ConversationModelRow::Omitted,
"claude-code/default",
),
];
for (current, conversation_model, expected) in cases {
let picker = internal_agent_model_picker(inputs(
current.clone(),
conversation_model,
ClaudeCodeRows::Offered,
));
assert_eq!(selected_label(&picker), expected, "{current:?}");
}
}
#[test]
fn each_row_value_routes_to_its_runtime() {
let picker = internal_agent_model_picker(inputs(
InternalAgentSelection::Unset,
ConversationModelRow::Offered { selected: true },
ClaudeCodeRows::Offered,
));
let rows = picker
.items
.iter()
.map(|item| parse_internal_agent_model_row(&item.value))
.collect::<Vec<_>>();
let expected_rows = std::iter::once(InternalAgentModelRow::Conversation)
.chain(std::iter::once(InternalAgentModelRow::ClaudeCode {
model: None,
}))
.chain(
crate::claude_runtime::models::CLAUDE_MODEL_ALIASES
.iter()
.map(|alias| InternalAgentModelRow::ClaudeCode {
model: Some(alias.name.into()),
}),
)
.collect::<Vec<_>>();
assert_eq!(rows, expected_rows);
assert_eq!(
parse_internal_agent_model_row("anthropic/claude-fable-5"),
InternalAgentModelRow::RhoModel("anthropic/claude-fable-5".into())
);
}
#[test]
fn pinned_scope_degrades_to_all_without_a_usable_pin() {
let cases = [
(ModelPickerScope::Pinned, vec![], vec!["api-key"]),
(ModelPickerScope::Pinned, vec!["openai/gpt-5.5"], vec![]),
(
ModelPickerScope::All,
vec!["xai/grok-4.6"],
vec!["xai-api-key"],
),
];
for (scope, pins, auths) in cases {
let pins = pins.iter().map(|pin| pin.to_string()).collect::<Vec<_>>();
let auths = auths
.iter()
.map(|auth| auth.to_string())
.collect::<Vec<_>>();
assert_eq!(
effective_model_picker_scope(scope, &pins, &auths),
ModelPickerScope::All,
"{scope:?} {pins:?} {auths:?}"
);
}
assert_eq!(
effective_model_picker_scope(
ModelPickerScope::Pinned,
&["xai/grok-4.6".into()],
&["xai-api-key".into()]
),
ModelPickerScope::Pinned
);
}
#[test]
fn pinned_scope_lists_only_usable_pins() {
let mut runtime = crate::tui::tests::test_bootstrap().runtime;
runtime.provider = "xai".into();
runtime.model = "grok-4.6".into();
runtime.auth = "xai-api-key".into();
runtime.favorite_models = vec!["xai/grok-4.6".into()];
let auths = vec!["xai-api-key".into()];
let pinned = model_picker(&runtime, &auths, ModelPickerScope::Pinned);
assert_eq!(
pinned
.items
.iter()
.map(|item| item.value.as_str())
.collect::<Vec<_>>(),
vec!["xai/grok-4.6"]
);
assert!(pinned.title.contains("pinned"));
runtime.favorite_models.clear();
let all = model_picker(&runtime, &auths, ModelPickerScope::Pinned);
assert_eq!(
all.items
.iter()
.map(|item| item.value.as_str())
.collect::<Vec<_>>(),
catalog::available_models_for_auths(&auths)
.iter()
.map(|entry| rho_providers::provider::model_reference(&entry.provider, &entry.model))
.collect::<Vec<_>>(),
"empty pins should keep the whole authenticated catalogue"
);
assert!(all.title.contains("all"));
}