use crate::brain::provider::factory::{vision_candidates, vision_candidates_for};
use crate::config::{Config, FallbackProviderConfig, ProviderConfig, ProviderConfigs};
use std::collections::BTreeMap;
fn opencode_go(key: Option<&str>) -> ProviderConfig {
ProviderConfig {
enabled: false,
api_key: key.map(|k| k.to_string()),
base_url: Some("https://opencode.ai/zen/go/v1".into()),
default_model: Some("kimi-k3".into()),
models: vec![],
vision_model: Some("kimi-k3".into()),
..Default::default()
}
}
fn config_with_custom(name: &str, cfg: ProviderConfig, chain: Vec<String>) -> Config {
let mut custom = BTreeMap::new();
custom.insert(name.to_string(), cfg);
Config {
providers: ProviderConfigs {
custom: Some(custom),
..Default::default()
},
..Default::default()
}
.with_chain(chain)
}
trait WithChain {
fn with_chain(self, chain: Vec<String>) -> Self;
}
impl WithChain for Config {
fn with_chain(mut self, chain: Vec<String>) -> Self {
self.providers.fallback = Some(FallbackProviderConfig {
generation: vec![],
enabled: true,
vision: chain,
..Default::default()
});
self
}
}
#[test]
fn keyless_remote_custom_is_not_a_vision_candidate() {
let config = config_with_custom(
"opencode-go",
opencode_go(None),
vec!["opencode-go".to_string()],
);
let cands = vision_candidates(&config);
assert!(
cands.is_empty(),
"keyless remote entry must be excluded, got: {cands:?}"
);
}
#[test]
fn keyed_remote_custom_is_a_vision_candidate() {
let config = config_with_custom(
"opencode-go",
opencode_go(Some("go-key")),
vec!["opencode-go".to_string()],
);
let cands = vision_candidates(&config);
assert_eq!(
cands.len(),
1,
"keyed entry must survive the gate: {cands:?}"
);
assert_eq!(cands[0].0, "go-key");
assert!(
cands[0]
.1
.starts_with("https://opencode.ai/zen/go/v1/chat/completions"),
"url normalized as before: {}",
cands[0].1
);
}
#[test]
fn empty_string_key_is_no_key() {
let config = config_with_custom(
"opencode-go",
opencode_go(Some("")),
vec!["opencode-go".to_string()],
);
assert!(
vision_candidates(&config).is_empty(),
"api_key = \"\" must not count as auth"
);
}
#[test]
fn keyless_local_custom_is_still_a_candidate() {
let mut local = opencode_go(None);
local.base_url = Some("http://localhost:11434/v1".into());
local.vision_model = Some("llava".into());
let config = config_with_custom("ollama-local", local, vec!["ollama-local".to_string()]);
let cands = vision_candidates(&config);
assert_eq!(
cands.len(),
1,
"keyless local endpoint must stay a candidate: {cands:?}"
);
assert!(
cands[0].0.is_empty(),
"local stays keyless: {:?}",
cands[0].0
);
}
#[test]
fn disabled_but_keyed_custom_is_still_a_candidate() {
assert!(
!opencode_go(None).enabled,
"fixture must be disabled to prove the point"
);
let config = config_with_custom(
"opencode-go",
opencode_go(Some("go-key")),
vec!["opencode-go".to_string()],
);
assert_eq!(
vision_candidates(&config).len(),
1,
"enabled = false must not be a vision gate (#401)"
);
}
#[test]
fn session_provider_candidate_is_gated_too() {
let config = config_with_custom("opencode-go", opencode_go(None), vec![]);
let cands = vision_candidates_for(&config, Some("opencode-go"));
assert!(
cands.is_empty(),
"the session provider must not bypass the auth gate: {cands:?}"
);
}
#[test]
fn all_keyless_chain_burns_no_round_trips() {
let mut custom = BTreeMap::new();
custom.insert("opencode-go".to_string(), opencode_go(None));
let mut second = opencode_go(None);
second.base_url = Some("https://api.example-remote.test/v1".into());
second.vision_model = Some("other-vision".into());
custom.insert("other-remote".to_string(), second);
let config = Config {
providers: ProviderConfigs {
custom: Some(custom),
..Default::default()
},
..Default::default()
}
.with_chain(vec!["opencode-go".to_string(), "other-remote".to_string()]);
let cands = vision_candidates(&config);
assert!(
cands.is_empty(),
"no entry in this chain can authenticate, so none may be attempted: {cands:?}"
);
}