mod capability;
use super::{ProviderRow, provider_rows, row_views};
pub(super) const LIVE_LISTING: &str = r#"{"providers":[
{"auth":"oauth2","credential":"stored","name":"codex","protocol":"openai_responses","tools":true},
{"auth":"none","credential":"not required","name":"local","protocol":"ollama_chat","tools":true},
{"auth":"none","credential":"not required","name":"claude-code","protocol":"claude_code","tools":false},
{"auth":"oauth2","credential":"ambient","name":"claude-session-direct","protocol":"anthropic_messages","tools":true},
{"auth":"api_key","credential":"missing","name":"anthropic","protocol":"anthropic_messages","tools":true}
]}"#;
#[test]
fn provider_rows_reads_name_and_auth_in_listing_order() {
let rows = provider_rows(LIVE_LISTING);
assert_eq!(
rows.iter().map(|r| r.name.clone()).collect::<Vec<_>>(),
[
"codex",
"local",
"claude-code",
"claude-session-direct",
"anthropic"
]
);
assert_eq!(rows[0].auth, "oauth2");
assert_eq!(rows[1].auth, "none");
}
#[test]
fn the_device_column_is_the_headless_login_capability() {
let rows = provider_rows(
r#"{"providers":[
{"name":"chatgpt","auth":"oauth2","device":"codex"},
{"name":"standards","auth":"oauth2","device":"rfc8628"},
{"name":"loopback-only","auth":"oauth2"},
{"name":"shapeless","auth":"oauth2","device":7}
]}"#,
);
assert_eq!(rows[0].device, "codex");
assert_eq!(rows[1].device, "rfc8628");
assert!(rows[0].headless_login() && rows[1].headless_login());
assert!(
!rows[2].headless_login() && !rows[3].headless_login(),
"no column, and an unreadable one, both read as browser-only"
);
assert_eq!(rows[2].login_blocked(), None);
}
#[test]
fn login_is_blocked_for_every_row_without_an_oauth_block() {
let rows = provider_rows(LIVE_LISTING);
assert_eq!(rows[0].login_blocked(), None, "codex is oauth2");
assert_eq!(
rows[3].login_blocked(),
None,
"claude-session-direct is oauth2"
);
assert_eq!(
rows[1].login_blocked().as_deref(),
Some("keyless — nothing to log in")
);
assert_eq!(
rows[2].login_blocked().as_deref(),
Some("keyless — nothing to log in")
);
assert_eq!(
rows[4].login_blocked().as_deref(),
Some("api-key provider — set the key in Config")
);
}
#[test]
fn a_bearer_row_is_told_where_its_token_lives() {
let rows = provider_rows(r#"{"providers":[{"name":"vertex","auth":"bearer"}]}"#);
assert_eq!(
rows[0].login_blocked().as_deref(),
Some("bearer-token provider — set the token in Config")
);
}
#[test]
fn an_unrecognized_auth_spelling_names_itself_rather_than_guessing() {
let rows = provider_rows(r#"{"providers":[{"name":"x","auth":"mtls"},{"name":"y"}]}"#);
let why = rows[0].login_blocked().expect("not oauth2");
assert!(why.contains("mtls"), "reason quotes the spelling: {why}");
assert!(why.contains("oauth2"), "and names what bz can serve: {why}");
assert!(
rows[1].login_blocked().is_some(),
"an empty column is not oauth2"
);
}
#[test]
fn row_views_state_the_credential_fact_in_words() {
let rows = provider_rows(LIVE_LISTING);
let views = row_views(&rows);
assert_eq!(
views.iter().map(|v| v.fact.clone()).collect::<Vec<_>>(),
[
"auth oauth2 \u{b7} signed in",
"auth none \u{b7} no credential needed",
"auth none \u{b7} no credential needed",
"auth oauth2 \u{b7} signed in",
"auth api_key \u{b7} no credential stored",
]
);
assert_eq!(views[0].name, "codex");
assert_eq!(
views
.iter()
.map(|v| v.blocked.is_none())
.collect::<Vec<_>>(),
[true, false, false, true, false]
);
assert_eq!(views[4].blocked, rows[4].login_blocked());
}
#[test]
fn credentialed_is_every_spelling_but_missing_and_not_required() {
for (credential, want) in [
("stored", true),
("ambient", true),
("inline", true),
("keychain-from-a-later-brazen", true),
("not required", false),
("missing", false),
] {
let quoted = format!("\"{credential}\"");
let listing =
format!(r#"{{"providers":[{{"name":"r","auth":"oauth2","credential":{quoted}}}]}}"#);
assert_eq!(
provider_rows(&listing)[0].credentialed(),
want,
"{credential}"
);
}
}
#[test]
fn a_keyed_row_with_a_credential_reports_it_stored_not_signed_in() {
let rows = provider_rows(
r#"{"providers":[{"name":"anthropic","auth":"api_key","credential":"stored"}]}"#,
);
assert_eq!(
row_views(&rows)[0].fact,
"auth api_key \u{b7} credential stored"
);
}
#[test]
fn an_unstated_credential_column_is_not_a_refusal() {
let rows = provider_rows(r#"{"providers":[{"name":"codex","auth":"oauth2"}]}"#);
assert_eq!(row_views(&rows)[0].fact, "auth oauth2 \u{b7} signed in");
assert_eq!(
row_views(&[]).len(),
0,
"the table names the rows, and an empty table names none"
);
}
#[test]
fn a_shapeless_listing_folds_to_no_rows() {
assert_eq!(provider_rows(""), Vec::new());
assert_eq!(provider_rows("MalformedFile at line 3"), Vec::new());
assert_eq!(provider_rows("{}"), Vec::new());
assert_eq!(provider_rows(r#"{"providers":7}"#), Vec::new());
}
#[test]
fn a_row_missing_a_column_degrades_to_empty_not_loginable() {
let rows = provider_rows(r#"{"providers":[{"nome":"x"},{"name":7,"auth":"oauth2"}]}"#);
assert_eq!(
rows,
[
ProviderRow {
name: String::new(),
protocol: String::new(),
auth: String::new(),
credential: String::new(),
effort: false,
priority: false,
tools: None,
device: String::new(),
},
ProviderRow {
name: String::new(),
protocol: String::new(),
auth: "oauth2".to_owned(),
credential: String::new(),
effort: false,
priority: false,
tools: None,
device: String::new(),
},
]
);
assert!(rows[0].login_blocked().is_some());
}
#[test]
fn row_names_projects_the_name_column_in_listing_order() {
let rows = provider_rows(
r#"{"providers":[{"name":"codex","auth":"oauth2"},{"name":"claude-session-direct","auth":"oauth2"}]}"#,
);
assert_eq!(
super::row_names(&rows),
vec!["codex".to_string(), "claude-session-direct".to_string()]
);
assert!(super::row_names(&[]).is_empty());
}