use crate::tui::onboarding::merge_minimax_baseline;
use crate::usage::pricing::{PricingConfig, PricingEntry, ProviderBlock};
use std::collections::HashMap;
fn pricing_with(entries: Vec<(&str, f64, f64)>) -> PricingConfig {
let mut providers = HashMap::new();
let block = ProviderBlock {
entries: entries
.into_iter()
.map(|(prefix, i, o)| PricingEntry {
prefix: prefix.to_string(),
input_per_m: i,
output_per_m: o,
cache_write_per_m: None,
cache_read_per_m: None,
})
.collect(),
};
providers.insert("minimax".to_string(), block);
PricingConfig { providers }
}
#[test]
fn pricing_merge_appends_missing_entries() {
let mut user = pricing_with(vec![
("minimax-m2.7", 0.30, 1.20),
("minimax-m2.5", 0.30, 1.20),
]);
let baseline = pricing_with(vec![
("minimax-m3", 0.60, 2.40),
("minimax-m2.7", 0.30, 1.20),
("minimax-m2.5", 0.30, 1.20),
]);
let added = user.merge_missing_from(&baseline);
assert_eq!(
added, 1,
"exactly one new entry (minimax-m3) should be appended"
);
let prefixes: Vec<&str> = user.providers["minimax"]
.entries
.iter()
.map(|e| e.prefix.as_str())
.collect();
assert!(prefixes.contains(&"minimax-m3"), "M3 must be appended");
assert!(prefixes.contains(&"minimax-m2.7"), "user's M2.7 preserved");
assert!(prefixes.contains(&"minimax-m2.5"), "user's M2.5 preserved");
}
#[test]
fn pricing_merge_is_case_insensitive_on_prefix() {
let mut user = pricing_with(vec![("MiniMax-M2.7", 0.30, 1.20)]);
let baseline = pricing_with(vec![("minimax-m2.7", 0.30, 1.20)]);
let added = user.merge_missing_from(&baseline);
assert_eq!(
added, 0,
"case-different but same prefix must not duplicate"
);
}
#[test]
fn pricing_merge_is_idempotent() {
let mut user = pricing_with(vec![("minimax-m2.7", 0.30, 1.20)]);
let baseline = pricing_with(vec![
("minimax-m3", 0.60, 2.40),
("minimax-m2.7", 0.30, 1.20),
]);
let first = user.merge_missing_from(&baseline);
assert_eq!(first, 1, "first call appends M3");
let second = user.merge_missing_from(&baseline);
assert_eq!(second, 0, "second call adds zero (idempotent)");
}
#[test]
fn pricing_merge_handles_new_provider_block() {
let mut user = pricing_with(vec![("minimax-m2.7", 0.30, 1.20)]);
let mut baseline_providers = HashMap::new();
baseline_providers.insert(
"zhipu".to_string(),
ProviderBlock {
entries: vec![PricingEntry {
prefix: "glm-5.1".to_string(),
input_per_m: 0.50,
output_per_m: 2.00,
cache_write_per_m: None,
cache_read_per_m: None,
}],
},
);
let baseline = PricingConfig {
providers: baseline_providers,
};
let added = user.merge_missing_from(&baseline);
assert_eq!(added, 1, "new-provider block must contribute its entries");
assert!(user.providers.contains_key("zhipu"));
assert_eq!(user.providers["zhipu"].entries.len(), 1);
}
#[test]
fn pricing_merge_never_overwrites_user_rates() {
let mut user = pricing_with(vec![("minimax-m2.7", 0.10, 0.40)]);
let baseline = pricing_with(vec![("minimax-m2.7", 0.30, 1.20)]);
let added = user.merge_missing_from(&baseline);
assert_eq!(
added, 0,
"existing prefix means baseline is skipped entirely"
);
let entry = &user.providers["minimax"].entries[0];
assert_eq!(entry.input_per_m, 0.10, "user's rate preserved");
assert_eq!(entry.output_per_m, 0.40, "user's rate preserved");
}
#[test]
fn minimax_merge_puts_baseline_first_user_last() {
let baseline = vec![
"MiniMax-M3".to_string(),
"MiniMax-M2.7".to_string(),
"MiniMax-M2.5".to_string(),
"MiniMax-M2.1".to_string(),
];
let user = vec![
"MiniMax-M2.7".to_string(),
"MiniMax-M2.5".to_string(),
"MiniMax-M2.1".to_string(),
"MiniMax-Text-01".to_string(),
];
let merged = merge_minimax_baseline(baseline, user);
assert_eq!(
merged.len(),
5,
"5 distinct entries — 4 baseline + 1 user-only"
);
assert_eq!(
merged[0], "MiniMax-M3",
"baseline first (newest at top of picker)"
);
assert_eq!(
merged[4], "MiniMax-Text-01",
"user-only entries appended at the end"
);
}
#[test]
fn minimax_merge_case_insensitive_dedup() {
let baseline = vec!["MiniMax-M3".to_string()];
let user = vec!["minimax-m3".to_string(), "MiniMax-Text-01".to_string()];
let merged = merge_minimax_baseline(baseline, user);
assert_eq!(merged.len(), 2);
assert_eq!(merged[0], "MiniMax-M3");
assert_eq!(merged[1], "MiniMax-Text-01");
}
#[test]
fn minimax_merge_empty_user_returns_baseline_only() {
let baseline = vec!["MiniMax-M3".to_string(), "MiniMax-M2.7".to_string()];
let merged = merge_minimax_baseline(baseline.clone(), Vec::new());
assert_eq!(merged, baseline);
}
#[test]
fn minimax_merge_empty_baseline_returns_user_only() {
let user = vec!["MiniMax-M2.7".to_string()];
let merged = merge_minimax_baseline(Vec::new(), user.clone());
assert_eq!(merged, user);
}
#[test]
fn minimax_merge_internal_user_dedup() {
let baseline = vec!["MiniMax-M3".to_string()];
let user = vec![
"MiniMax-M2.7".to_string(),
"MiniMax-M2.7".to_string(),
"MiniMax-Text-01".to_string(),
];
let merged = merge_minimax_baseline(baseline, user);
assert_eq!(merged.len(), 3, "duplicates removed inside user list too");
}