use super::*;
fn mk(name: &str) -> CapKey {
cap_key(newt_core::Serving::Multiplexer, "", name)
}
#[test]
fn looks_like_tool_call_json_native_object() {
assert!(looks_like_tool_call_json(
r#"{"name":"list_dir","arguments":{"path":"."}}"#
));
}
#[test]
fn looks_like_tool_call_json_array() {
assert!(looks_like_tool_call_json(
r#"[{"name":"list_dir","arguments":{"path":"."}}]"#
));
}
#[test]
fn looks_like_tool_call_json_plain_text() {
assert!(!looks_like_tool_call_json(
"Here are the files: README.md, src/"
));
}
#[test]
fn looks_like_tool_call_json_incomplete_object() {
assert!(!looks_like_tool_call_json(r#"{"name":"list_dir"}"#));
}
#[test]
fn load_cache_returns_empty_on_missing_file() {
let _ = load_cache();
}
#[test]
fn conformance_symbol_coverage() {
assert!(ToolConformance::Native.symbol().contains('✓'));
assert!(ToolConformance::TextMode.symbol().contains('~'));
assert!(ToolConformance::NoTools.symbol().contains('✗'));
}
#[test]
fn tune_confidence_promotes_correctly() {
assert_eq!(TuneConfidence::None.promote(), TuneConfidence::Low);
assert_eq!(TuneConfidence::Low.promote(), TuneConfidence::Medium);
assert_eq!(TuneConfidence::Medium.promote(), TuneConfidence::High);
assert_eq!(TuneConfidence::High.promote(), TuneConfidence::High);
}
fn make_entry() -> CapabilityEntry {
CapabilityEntry {
conformance: ToolConformance::Native,
tested_date: "2026-06-06".to_string(),
context_window: Some(32768),
safe_context: Some(26214),
..Default::default()
}
}
#[test]
fn record_success_updates_max_ok_input() {
let mut e = make_entry();
e.record_success(10_000, "2026-06-06");
assert_eq!(e.max_ok_input, Some(10_000));
e.record_success(8_000, "2026-06-06");
assert_eq!(e.max_ok_input, Some(10_000));
}
#[test]
fn record_success_promotes_confidence_after_five() {
let mut e = make_entry();
for i in 0..4 {
e.record_success(5_000, "2026-06-06");
assert_eq!(e.tune_confidence, TuneConfidence::None, "early iter {i}");
}
e.record_success(5_000, "2026-06-06");
assert_eq!(e.tune_confidence, TuneConfidence::Low);
assert_eq!(e.consecutive_ok, 0); }
#[test]
fn record_overflow_reduces_safe_context() {
let mut e = make_entry();
e.record_overflow(30_000, "2026-06-06");
assert_eq!(e.safe_context, Some(22_500));
assert_eq!(e.tune_confidence, TuneConfidence::Low);
assert_eq!(e.overflow_at, Some(30_000));
}
#[test]
fn record_overflow_reins_max_ok_input_down() {
let mut e = make_entry();
e.max_ok_input = Some(28_000);
e.record_overflow(20_000, "2026-06-12");
assert_eq!(e.safe_context, Some(15_000));
assert_eq!(e.max_ok_input, Some(15_000));
let mut e2 = make_entry();
e2.max_ok_input = Some(10_000);
e2.record_overflow(20_000, "2026-06-12");
assert_eq!(e2.max_ok_input, Some(10_000));
let mut e3 = make_entry();
e3.record_overflow(20_000, "2026-06-12");
assert_eq!(e3.max_ok_input, None);
}
#[test]
fn record_accepted_prompt_is_a_pure_high_water_ratchet() {
let mut e = make_entry();
e.consecutive_ok = 3;
e.tune_confidence = TuneConfidence::Medium;
assert!(
e.record_accepted_prompt(8_734, "2026-06-12"),
"first: dirty"
);
assert_eq!(e.max_ok_input, Some(8_734));
assert_eq!(e.tune_date.as_deref(), Some("2026-06-12"), "date stamped");
assert_eq!(e.consecutive_ok, 3, "untouched");
assert_eq!(e.tune_confidence, TuneConfidence::Medium, "untouched");
assert!(
!e.record_accepted_prompt(8_734, "2026-06-13"),
"equal: clean"
);
assert!(
!e.record_accepted_prompt(4_000, "2026-06-13"),
"lower: clean"
);
assert_eq!(e.max_ok_input, Some(8_734), "HWM only raises");
assert_eq!(e.tune_date.as_deref(), Some("2026-06-12"), "no re-stamp");
assert!(e.record_accepted_prompt(9_000, "2026-06-13"));
assert_eq!(e.max_ok_input, Some(9_000));
assert_eq!(e.tune_date.as_deref(), Some("2026-06-13"));
}
#[test]
fn record_estimate_sample_initializes_then_emas() {
let mut e = make_entry();
assert!(e.record_estimate_sample(8_734, 6_600));
let first = e.estimate_ratio.unwrap();
assert!((first - 8_734.0 / 6_600.0).abs() < 1e-6, "got {first}");
assert!(e.record_estimate_sample(2_000, 1_000));
let second = e.estimate_ratio.unwrap();
assert!(
(second - (0.75 * first + 0.25 * 2.0)).abs() < 1e-6,
"got {second}"
);
}
#[test]
fn record_estimate_sample_clamps_the_high_end() {
let mut e = make_entry();
assert!(e.record_estimate_sample(10_000, 1_000)); assert_eq!(e.estimate_ratio, Some(3.0), "init clamped to 3.0");
assert!(!e.record_estimate_sample(10_000, 1_000), "3.0 → 3.0: clean");
assert_eq!(e.estimate_ratio, Some(3.0));
}
#[test]
fn record_estimate_sample_excludes_the_partial_cache_hit_band() {
let mut clean = make_entry();
assert!(clean.record_estimate_sample(1_000, 1_000));
assert_eq!(clean.estimate_ratio, Some(1.0));
let mut cache_hit = make_entry();
assert!(!cache_hit.record_estimate_sample(900, 1_000));
assert_eq!(
cache_hit.estimate_ratio, None,
"a [0.5, 1.0) sample must not seed the ratio"
);
cache_hit.estimate_ratio = Some(1.3);
assert!(!cache_hit.record_estimate_sample(900, 1_000));
assert_eq!(cache_hit.estimate_ratio, Some(1.3));
let mut boundary = make_entry();
assert!(!boundary.record_estimate_sample(999, 1_000)); assert!(boundary.record_estimate_sample(1_001, 1_000)); }
#[test]
fn record_estimate_sample_skips_cache_hits_and_zero_estimates() {
let mut e = make_entry();
assert!(!e.record_estimate_sample(400, 1_000));
assert_eq!(e.estimate_ratio, None);
assert!(!e.record_estimate_sample(400, 0));
assert_eq!(e.estimate_ratio, None);
e.estimate_ratio = Some(1.3);
assert!(!e.record_estimate_sample(100, 1_000));
assert_eq!(e.estimate_ratio, Some(1.3));
}
#[test]
fn record_estimate_sample_dirty_only_above_threshold() {
let mut e = make_entry();
assert!(e.record_estimate_sample(1_300, 1_000)); let stored = e.estimate_ratio.unwrap();
assert!(!e.record_estimate_sample(1_301, 1_000));
let drifted = e.estimate_ratio.unwrap();
assert!((drifted - stored).abs() < 0.01, "stored as-is, tiny drift");
assert!(e.record_estimate_sample(2_000, 1_000));
}
#[test]
fn record_thinking_only_is_sticky_and_dirty_once() {
let mut e = make_entry();
assert_eq!(e.emits_thinking, None);
assert!(e.record_thinking_only(), "first observation: dirty");
assert_eq!(e.emits_thinking, Some(true));
assert!(!e.record_thinking_only(), "repeat: clean");
assert_eq!(e.emits_thinking, Some(true));
}
#[test]
fn apply_observation_dispatches_each_variant() {
let today = "2026-06-12";
let mut e = make_entry();
let obs = newt_core::RoundObservation::Accepted {
prompt_tokens: 8_734,
estimated_tokens: 6_600,
};
assert!(apply_observation(&mut e, &obs, today));
assert_eq!(e.max_ok_input, Some(8_734));
assert!(e.estimate_ratio.is_some());
assert!(!apply_observation(&mut e, &obs, today));
let recal = newt_core::RoundObservation::Accepted {
prompt_tokens: 8_000,
estimated_tokens: 3_000,
};
assert!(apply_observation(&mut e, &recal, today));
assert_eq!(e.max_ok_input, Some(8_734), "lower prompt: no ratchet");
let mut e = make_entry();
e.max_ok_input = Some(28_000);
let obs = newt_core::RoundObservation::SuspectedOverflow {
prompt_tokens: 20_000,
};
assert!(apply_observation(&mut e, &obs, today));
assert_eq!(e.safe_context, Some(15_000));
assert_eq!(e.max_ok_input, Some(15_000));
assert_eq!(e.overflow_at, Some(20_000));
let mut e = make_entry();
assert!(apply_observation(
&mut e,
&newt_core::RoundObservation::ThinkingOnly,
today
));
assert_eq!(e.emits_thinking, Some(true));
assert!(!apply_observation(
&mut e,
&newt_core::RoundObservation::ThinkingOnly,
today
));
let mut e = make_entry();
e.context_window = Some(65_536);
e.safe_context = Some(52_428);
e.max_ok_input = Some(52_428);
assert!(apply_observation(
&mut e,
&newt_core::RoundObservation::ContextWindow400 {
context_window: 32_768,
},
today,
));
assert_eq!(e.context_window, Some(32_768));
assert_eq!(e.safe_context, Some(26_214));
assert_eq!(e.max_ok_input, Some(26_214));
assert!(apply_observation(
&mut e,
&newt_core::RoundObservation::Accepted {
prompt_tokens: 1_000,
estimated_tokens: 1_000,
},
today,
));
assert_eq!(e.context_window, Some(32_768));
assert_eq!(e.safe_context, Some(26_214));
}
#[test]
fn estimate_ratio_and_emits_thinking_roundtrip_json() {
let mut e = make_entry();
e.estimate_ratio = Some(1.29);
e.emits_thinking = Some(true);
let json = serde_json::to_string(&e).unwrap();
let back: CapabilityEntry = serde_json::from_str(&json).unwrap();
assert_eq!(back.estimate_ratio, Some(1.29));
assert_eq!(back.emits_thinking, Some(true));
let bare = serde_json::to_string(&make_entry()).unwrap();
assert!(!bare.contains("estimate_ratio"), "{bare}");
assert!(!bare.contains("emits_thinking"), "{bare}");
}
#[test]
fn record_overflow_does_not_increase_safe_context() {
let mut e = make_entry();
e.safe_context = Some(10_000);
e.record_overflow(5_000, "2026-06-06");
assert_eq!(e.safe_context, Some(3_750));
e.record_overflow(40_000, "2026-06-06");
assert_eq!(e.safe_context, Some(3_750));
}
#[test]
fn parse_context_window_error_none_for_unrelated_400() {
let msg = "inference endpoint 400: invalid api key";
assert_eq!(super::parse_context_window_error(msg), None);
}
#[test]
fn parse_context_window_error_extracts_prompt_and_max() {
let msg = "inference endpoint 400: litellm.ContextWindowExceededError: prompt is too long: 5960028 tokens > 1000000 maximum";
assert_eq!(
super::parse_context_window_error(msg),
Some((5_960_028, 1_000_000))
);
}
#[test]
fn parse_context_window_error_extracts_vllm_output_and_prompt_limits() {
let msg = "inference endpoint 400 Bad Request: This model's maximum context length is 32768 tokens. However, you requested 16000 output tokens and your prompt contains 20000 input tokens, for a total of 36000 tokens (20000 + 16000 = 36000 > 32768). Please reduce the length of the input prompt or the number of requested output tokens.";
assert_eq!(
super::parse_context_window_error(msg),
Some((20_000, 32_768))
);
}
#[test]
fn parse_context_window_error_extracts_vllm_input_only_limit() {
let msg = "inference endpoint 400 Bad Request: This model's maximum context length is 32768 tokens. However, your request has 33000 input tokens. Please reduce the length of the input messages.";
assert_eq!(
super::parse_context_window_error(msg),
Some((33_000, 32_768))
);
}
#[test]
fn parse_context_window_error_none_without_max_clause() {
let msg = "prompt is too long: 5960028 tokens";
assert_eq!(super::parse_context_window_error(msg), None);
}
#[test]
fn record_context_window_400_tightens_max_ok_input_to_80pct() {
let mut e = make_entry();
e.max_ok_input = Some(251_640);
let dirty = e.record_context_window_400(1_000_000, "2026-06-08");
assert!(dirty);
assert_eq!(e.context_window, Some(1_000_000));
assert_eq!(e.max_ok_input, Some(800_000));
assert_eq!(e.tune_confidence, TuneConfidence::Low);
assert_eq!(e.consecutive_ok, 0);
}
#[test]
fn runtime_context_window_400_persists_the_configured_percentage_cap() {
let mut e = make_entry();
e.max_ok_input = Some(52_428);
e.record_context_window_400_with_pct(32_768, 90, "2026-08-01");
assert_eq!(e.context_window, Some(32_768));
assert_eq!(e.hard_context_window, Some(32_768));
assert_eq!(e.max_ok_input, Some(29_491));
e.record_context_window_400_with_pct(65_536, 90, "2026-08-01");
assert_eq!(
e.hard_context_window,
Some(32_768),
"a later, larger error must not raise the persisted hard ceiling",
);
assert_eq!(e.context_window, Some(32_768));
assert_eq!(e.max_ok_input, Some(29_491));
let persisted = serde_json::to_string(&e).unwrap();
let restored: CapabilityEntry = serde_json::from_str(&persisted).unwrap();
assert_eq!(restored.hard_context_window, Some(32_768));
}
#[test]
fn record_context_window_400_lowers_an_overshot_cap() {
let mut e = make_entry();
e.max_ok_input = Some(2_000_000);
e.record_context_window_400(1_000_000, "2026-06-08");
assert_eq!(e.max_ok_input, Some(800_000));
}
#[test]
fn record_context_window_400_caps_safe_context_without_raising_it() {
let mut e = make_entry();
e.safe_context = Some(64_000); e.record_context_window_400(1_000_000, "2026-06-08");
assert_eq!(e.safe_context, Some(64_000));
}
#[test]
fn fmt_k_formats_correctly() {
assert_eq!(fmt_k(1024), "1k");
assert_eq!(fmt_k(32768), "32k");
assert_eq!(fmt_k(131072), "128k");
assert_eq!(fmt_k(512), "512");
}
#[test]
fn capability_entry_roundtrips_json_with_new_fields() {
let mut e = make_entry();
e.overflow_at = Some(28_000);
e.max_ok_input = Some(25_000);
e.tune_confidence = TuneConfidence::Medium;
e.tune_date = Some("2026-06-06".to_string());
let json = serde_json::to_string(&e).unwrap();
let back: CapabilityEntry = serde_json::from_str(&json).unwrap();
assert_eq!(back.context_window, Some(32768));
assert_eq!(back.overflow_at, Some(28_000));
assert_eq!(back.tune_confidence, TuneConfidence::Medium);
}
#[test]
fn capability_entry_deserializes_legacy_json_without_new_fields() {
let legacy = r#"{"conformance":"native","tested_date":"2026-06-04"}"#;
let e: CapabilityEntry = serde_json::from_str(legacy).unwrap();
assert_eq!(e.conformance, ToolConformance::Native);
assert_eq!(e.context_window, None);
assert_eq!(e.tune_confidence, TuneConfidence::None);
assert_eq!(e.accounting_version, 0);
}
#[test]
fn migrate_accounting_invalidates_poisoned_entry() {
let mut cache = CapabilityCache::default();
cache.insert(
mk("llama3.1:8b"),
CapabilityEntry {
conformance: ToolConformance::Native,
tested_date: "2026-06-08".into(),
context_window: Some(8_192),
hard_context_window: None,
safe_context: Some(6_553),
overflow_at: None,
max_ok_input: Some(25_602),
consecutive_ok: 3,
tune_confidence: TuneConfidence::High,
tune_date: Some("2026-06-08".into()),
estimate_ratio: None,
emits_thinking: None,
accounting_version: 0, },
);
assert!(
migrate_accounting(&mut cache),
"migration must report dirty"
);
let e = &cache[&mk("llama3.1:8b")];
assert_eq!(e.max_ok_input, None, "poisoned ratchet value dropped");
assert_eq!(e.consecutive_ok, 0);
assert_eq!(e.tune_confidence, TuneConfidence::None);
assert_eq!(e.accounting_version, ACCOUNTING_VERSION);
assert_eq!(e.context_window, Some(8_192));
assert_eq!(e.safe_context, Some(6_553));
assert_eq!(e.conformance, ToolConformance::Native);
}
#[test]
fn migrate_accounting_leaves_current_version_entry_untouched() {
let mut cache = CapabilityCache::default();
let entry = CapabilityEntry {
conformance: ToolConformance::Native,
tested_date: "2026-06-09".into(),
safe_context: Some(64_000),
max_ok_input: Some(800_000), consecutive_ok: 2,
tune_confidence: TuneConfidence::Medium,
..Default::default() };
cache.insert(mk("hosted-model"), entry.clone());
assert!(!migrate_accounting(&mut cache), "nothing to migrate");
let e = &cache[&mk("hosted-model")];
assert_eq!(e.max_ok_input, Some(800_000));
assert_eq!(e.consecutive_ok, 2);
assert_eq!(e.tune_confidence, TuneConfidence::Medium);
}
#[test]
fn migrate_accounting_stamps_untuned_legacy_entry() {
let mut cache = CapabilityCache::default();
cache.insert(
mk("old-model"),
CapabilityEntry {
conformance: ToolConformance::TextMode,
tested_date: "2026-06-04".into(),
accounting_version: 0,
..Default::default()
},
);
assert!(migrate_accounting(&mut cache));
assert_eq!(
cache[&mk("old-model")].accounting_version,
ACCOUNTING_VERSION
);
assert_eq!(
cache[&mk("old-model")].conformance,
ToolConformance::TextMode
);
}
#[test]
fn migrate_accounting_is_idempotent() {
let mut cache = CapabilityCache::default();
let mut e = make_entry();
e.max_ok_input = Some(25_602);
e.accounting_version = 0;
cache.insert(mk("m"), e);
assert!(migrate_accounting(&mut cache), "first pass migrates");
let snapshot = serde_json::to_string(&cache).unwrap();
assert!(!migrate_accounting(&mut cache), "second pass is a no-op");
assert_eq!(serde_json::to_string(&cache).unwrap(), snapshot);
}
#[test]
fn invalidate_suspect_pins_drops_the_1967_incident_pin() {
let mut cache = CapabilityCache::default();
cache.insert(
mk("nemotron-3-ultra"),
CapabilityEntry {
conformance: ToolConformance::Native,
tested_date: "2026-08-29".into(),
context_window: Some(262_144),
hard_context_window: None,
safe_context: Some(209_715),
max_ok_input: Some(205_189),
consecutive_ok: 0,
tune_confidence: TuneConfidence::High,
tune_date: Some("2026-08-29".into()),
..Default::default()
},
);
assert!(invalidate_suspect_pins(&mut cache), "must report dirty");
let e = &cache[&mk("nemotron-3-ultra")];
assert_eq!(e.max_ok_input, None, "the poisoned pin must be dropped");
assert_eq!(e.consecutive_ok, 0);
assert_eq!(e.tune_confidence, TuneConfidence::None);
assert_eq!(e.context_window, Some(262_144));
assert_eq!(e.safe_context, Some(209_715));
}
#[test]
fn invalidate_suspect_pins_spares_a_legitimate_cw_400_pin() {
let mut cache = CapabilityCache::default();
cache.insert(
mk("hosted-model"),
CapabilityEntry {
conformance: ToolConformance::Native,
tested_date: "2026-06-09".into(),
hard_context_window: Some(1_000_000),
safe_context: Some(64_000),
max_ok_input: Some(800_000), consecutive_ok: 2,
tune_confidence: TuneConfidence::Medium,
..Default::default()
},
);
assert!(
!invalidate_suspect_pins(&mut cache),
"a hard_context_window-confirmed pin must never be touched"
);
let e = &cache[&mk("hosted-model")];
assert_eq!(e.max_ok_input, Some(800_000));
assert_eq!(e.tune_confidence, TuneConfidence::Medium);
}
#[test]
fn invalidate_suspect_pins_spares_a_healthy_pin() {
let mut cache = CapabilityCache::default();
let mut e = make_entry();
e.max_ok_input = Some(4_136);
e.tune_confidence = TuneConfidence::High;
cache.insert(mk("healthy-model"), e);
assert!(!invalidate_suspect_pins(&mut cache));
assert_eq!(cache[&mk("healthy-model")].max_ok_input, Some(4_136));
}
#[test]
fn invalidate_suspect_pins_is_idempotent() {
let mut cache = CapabilityCache::default();
let mut e = make_entry();
e.safe_context = Some(209_715);
e.max_ok_input = Some(205_189);
e.tune_confidence = TuneConfidence::High;
cache.insert(mk("m"), e);
assert!(
invalidate_suspect_pins(&mut cache),
"first pass invalidates"
);
let snapshot = serde_json::to_string(&cache).unwrap();
assert!(
!invalidate_suspect_pins(&mut cache),
"second pass is a no-op"
);
assert_eq!(serde_json::to_string(&cache).unwrap(), snapshot);
}
fn fixture_entry(max_ok_input: Option<u32>, safe_context: Option<u32>) -> CapabilityEntry {
CapabilityEntry {
conformance: ToolConformance::Native,
tested_date: "2026-06-10".into(),
context_window: Some(32_768),
safe_context,
max_ok_input,
..Default::default()
}
}
#[test]
fn resolve_memory_budget_explicit_config_wins() {
let entry = fixture_entry(Some(24_000), Some(26_214));
assert_eq!(
resolve_memory_budget(Some(16_000), Some(1_000_000), Some(&entry)),
16_000
);
}
#[test]
fn resolve_memory_budget_uses_the_active_models_declared_window() {
let entry = fixture_entry(Some(24_000), None);
assert_eq!(
resolve_memory_budget(None, Some(1_000_000), Some(&entry)),
1_000_000
);
assert_eq!(
resolve_memory_budget(None, Some(131_072), None),
131_072,
"the selected model's declaration replaces the prior model's budget"
);
}
#[test]
fn resolve_memory_budget_capability_max_ok_input_second() {
let entry = fixture_entry(Some(24_000), Some(6_553));
assert_eq!(resolve_memory_budget(None, None, Some(&entry)), 24_000);
}
#[test]
fn resolve_memory_budget_max_keeps_safe_context_over_low_hwm() {
let entry = fixture_entry(Some(6_068), Some(26_214));
assert_eq!(resolve_memory_budget(None, None, Some(&entry)), 26_214);
}
#[test]
fn resolve_memory_budget_falls_back_to_safe_context() {
let entry = fixture_entry(None, Some(6_553));
assert_eq!(resolve_memory_budget(None, None, Some(&entry)), 6_553);
let entry = fixture_entry(Some(24_000), None);
assert_eq!(resolve_memory_budget(None, None, Some(&entry)), 24_000);
}
#[test]
fn resolve_memory_budget_static_default_last() {
assert_eq!(
resolve_memory_budget(None, None, None),
newt_core::DEFAULT_CONTEXT_TOKENS
);
let untuned = fixture_entry(None, None);
assert_eq!(
resolve_memory_budget(None, None, Some(&untuned)),
newt_core::DEFAULT_CONTEXT_TOKENS
);
}
#[test]
fn resolve_memory_budget_never_ignores_probe_data() {
let entry = fixture_entry(Some(24_000), Some(26_214));
let budget = resolve_memory_budget(None, None, Some(&entry));
assert_ne!(
budget,
newt_core::DEFAULT_CONTEXT_TOKENS,
"capability data present — the static default must not win"
);
assert_eq!(budget, 26_214);
}
#[test]
fn message_thinking_fields_truth_table() {
assert!(message_thinking_fields(&serde_json::json!({
"content": "", "thinking": "reasoning here"
})));
assert!(message_thinking_fields(&serde_json::json!({
"content": " \n", "reasoning": "x"
})));
assert!(message_thinking_fields(&serde_json::json!({
"content": "", "reasoning_content": "y"
})));
assert!(message_thinking_fields(&serde_json::json!({
"thinking": "z"
})));
assert!(!message_thinking_fields(&serde_json::json!({
"content": "ok", "thinking": "z"
})));
assert!(!message_thinking_fields(&serde_json::json!({
"content": "", "thinking": " "
})));
assert!(!message_thinking_fields(
&serde_json::json!({"content": ""})
));
}
#[test]
fn build_padded_prompt_sizes_near_target_across_ratios() {
for &(target, ratio) in &[
(512u32, 1.0f32),
(2_048, 1.0),
(8_000, 1.3),
(4_096, 0.8),
(16_000, 2.5),
] {
let s = build_padded_prompt(target, ratio);
let est = s.chars().count() as f32 / 4.0;
let predicted_real = est * sanitize_ratio(ratio);
let rel = (predicted_real - target as f32).abs() / target as f32;
assert!(
rel <= 0.10,
"target={target} ratio={ratio}: chars/4*ratio={predicted_real} ({:.1}% off)",
rel * 100.0
);
}
}
#[test]
fn build_padded_prompt_sanitizes_bad_ratio_to_one() {
let s = build_padded_prompt(4_000, f32::NAN);
let est = s.chars().count() as f32 / 4.0;
assert!((est - 4_000.0).abs() / 4_000.0 <= 0.10);
let s2 = build_padded_prompt(4_000, 99.0);
let est2 = s2.chars().count() as f32 / 4.0;
assert!((est2 - 4_000.0).abs() / 4_000.0 <= 0.10);
}
#[test]
fn classify_boundary_probe_accepted_when_eval_meets_threshold() {
let json = serde_json::json!({
"message": {"content": "ok"},
"prompt_eval_count": 9_500,
"eval_count": 3
});
assert_eq!(
classify_boundary_probe(Ok(&json), 10_000),
BoundaryClass::Accepted {
prompt_tokens: 9_500
}
);
}
#[test]
fn classify_boundary_probe_accepted_via_tool_call_or_eval_count() {
let tc = serde_json::json!({
"message": {"content": "", "tool_calls": [{"function": {"name": "x"}}]},
"prompt_eval_count": 9_900
});
assert!(matches!(
classify_boundary_probe(Ok(&tc), 10_000),
BoundaryClass::Accepted { .. }
));
let ec = serde_json::json!({
"message": {"content": ""},
"prompt_eval_count": 9_900,
"eval_count": 5
});
assert!(matches!(
classify_boundary_probe(Ok(&ec), 10_000),
BoundaryClass::Accepted { .. }
));
}
#[test]
fn classify_boundary_probe_truncated_when_eval_below_threshold() {
let json = serde_json::json!({
"message": {"content": "ok"},
"prompt_eval_count": 4_000,
"eval_count": 2
});
assert_eq!(
classify_boundary_probe(Ok(&json), 10_000),
BoundaryClass::Truncated
);
let no_count = serde_json::json!({"message": {"content": "ok"}, "eval_count": 1});
assert_eq!(
classify_boundary_probe(Ok(&no_count), 10_000),
BoundaryClass::Truncated
);
}
#[test]
fn classify_boundary_probe_ctx_window_400() {
let err = anyhow::anyhow!(
"inference endpoint 400: litellm.ContextWindowExceededError: \
prompt is too long: 12000 tokens > 8192 maximum"
);
assert_eq!(
classify_boundary_probe(Err(&err), 12_000),
BoundaryClass::CtxWindow400 { limit: 8_192 }
);
}
#[test]
fn classify_boundary_probe_inconclusive_for_other_errors() {
let err = anyhow::anyhow!("request failed: connection reset");
assert_eq!(
classify_boundary_probe(Err(&err), 10_000),
BoundaryClass::Inconclusive
);
}
#[test]
fn is_tuning_stale_boundaries() {
assert!(is_tuning_stale(None, "2026-06-13", 30));
assert!(!is_tuning_stale(Some("2026-05-14"), "2026-06-13", 30));
assert!(is_tuning_stale(Some("2026-05-13"), "2026-06-13", 30));
assert!(!is_tuning_stale(Some("2026-06-13"), "2026-06-13", 30));
assert!(is_tuning_stale(Some("not-a-date"), "2026-06-13", 30));
assert!(is_tuning_stale(Some("2026-06-13"), "garbage", 30));
}
#[test]
fn sanitize_ratio_clamps_and_defaults() {
assert_eq!(sanitize_ratio(1.3), 1.3);
assert_eq!(sanitize_ratio(0.5), 0.5);
assert_eq!(sanitize_ratio(3.0), 3.0);
assert_eq!(sanitize_ratio(0.4), 1.0); assert_eq!(sanitize_ratio(3.1), 1.0); assert_eq!(sanitize_ratio(f32::NAN), 1.0);
assert_eq!(sanitize_ratio(f32::INFINITY), 1.0);
}
#[test]
fn parse_show_response_reads_llama_key() {
let json = serde_json::json!({"model_info": {"llama.context_length": 32768}});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
Some(32768)
);
}
#[test]
fn parse_show_response_reads_nemotron_key() {
let json = serde_json::json!({"model_info": {"nemotron_h_omni.context_length": 131072}});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
Some(131072)
);
}
#[test]
fn parse_show_response_bare_context_length_key() {
let json = serde_json::json!({"model_info": {"context_length": 8192}});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
Some(8192)
);
}
#[test]
fn parse_show_response_modelfile_num_ctx_wins_when_smaller() {
let json = serde_json::json!({
"model_info": {"llama.context_length": 131072},
"parameters": "num_ctx 32768\ntemperature 0.7"
});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
Some(32768)
);
}
#[test]
fn parse_show_response_arch_wins_when_num_ctx_larger() {
let json = serde_json::json!({
"model_info": {"llama.context_length": 4096},
"parameters": "num_ctx 32768"
});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
Some(4096)
);
}
#[test]
fn parse_show_response_returns_none_when_no_keys() {
let json = serde_json::json!({"model_info": {"general.architecture": "llama"}});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
None
);
}
#[test]
fn parse_show_response_uses_minimum_when_multiple_arch_keys() {
let json = serde_json::json!({
"model_info": {
"llama.context_length": 131072,
"gemma.context_length": 8192
}
});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
Some(8192)
);
}
#[test]
fn parse_show_response_modelfile_only_no_model_info() {
let json = serde_json::json!({
"parameters": "stop \"<|end|>\"\nnum_ctx 16384\ntemperature 0.2"
});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
Some(16384)
);
}
#[test]
fn parse_show_response_ignores_unparsable_num_ctx() {
let json = serde_json::json!({"parameters": "num_ctx lots"});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
None
);
}
#[test]
fn parse_show_response_parameters_without_num_ctx() {
let json = serde_json::json!({"parameters": "temperature 0.7\ntop_p 0.9"});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
None
);
}
#[test]
fn parse_show_response_non_numeric_context_length_ignored() {
let json = serde_json::json!({"model_info": {"llama.context_length": "32768"}});
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&json),
None
);
}
#[test]
fn parse_show_response_empty_json() {
assert_eq!(
newt_core::backend_probe::parse_ollama_show_window(&serde_json::json!({})),
None
);
}
#[test]
fn probe_tool_schema_is_single_list_dir_function() {
let schema = super::probe_tool_schema();
let arr = schema.as_array().expect("schema is a JSON array");
assert_eq!(arr.len(), 1, "probe uses exactly one tool");
let f = &arr[0];
assert_eq!(f["type"], "function");
assert_eq!(f["function"]["name"], "list_dir");
let params = &f["function"]["parameters"];
assert_eq!(params["properties"]["path"]["type"], "string");
assert_eq!(params["required"][0], "path");
}
#[test]
fn capability_entry_default_is_untested_no_tools() {
let e = CapabilityEntry::default();
assert_eq!(e.conformance, ToolConformance::NoTools);
assert!(e.tested_date.is_empty());
assert_eq!(e.context_window, None);
assert_eq!(e.safe_context, None);
assert_eq!(e.overflow_at, None);
assert_eq!(e.max_ok_input, None);
assert_eq!(e.consecutive_ok, 0);
assert_eq!(e.tune_confidence, TuneConfidence::None);
assert_eq!(e.tune_date, None);
}
#[test]
fn tune_confidence_default_is_none() {
assert_eq!(TuneConfidence::default(), TuneConfidence::None);
}
#[test]
fn the_capability_table_renders_its_current_bytes() {
let mut cache = CapabilityCache::default();
let mut e = make_entry();
e.tune_confidence = TuneConfidence::High;
e.tested_date = "2026-06-10".to_string();
cache.insert(mk("llama3.1:8b"), e);
let models: Vec<ModelInfo> = [("llama3.1:8b", "8B"), ("qwen2.5-coder:7b", "7B")]
.into_iter()
.map(|(n, s)| ModelInfo {
name: n.to_string(),
param_size: s.to_string(),
})
.collect();
let t = capabilities_table(&models, &cache, "llama3.1:8b");
assert_eq!(t.active, Some(0), "the active row is identified by index");
assert_eq!(
t.head[0],
"| Model | Size | Tool Use | Think | Ctx Win | Safe Ctx | Conf | Tested |"
);
assert_eq!(
t.head[1],
"| ---------------- | ---: | -------- | ----- | ------: | -------: | ---: | ---------- |"
);
assert_eq!(t.rows.len(), 2);
assert_eq!(
t.rows[0],
"| llama3.1:8b \u{25c0} | 8B | \u{2713} native | \u{2014} | 32k | 25k | High | 2026-06-10 |"
);
assert_eq!(
t.rows[1],
"| qwen2.5-coder:7b | 7B | \u{2014} | \u{2014} | \u{2014} | \u{2014} | \u{2014} | (untested) |"
);
assert!(
!t.rows.iter().any(|r| r.contains(" ")),
"no run of hand-laid padding survives: {:?}",
t.rows
);
}
#[test]
fn a_cjk_model_name_no_longer_misaligns_any_column() {
let cache = CapabilityCache::default();
let models: Vec<ModelInfo> = [("日本語モデル:7b", "7B"), ("ascii-model:7b", "7B")]
.into_iter()
.map(|(n, s)| ModelInfo {
name: n.to_string(),
param_size: s.to_string(),
})
.collect();
let t = capabilities_table(&models, &cache, "none");
assert_eq!("日本語モデル:7b".len(), 21);
assert_eq!("日本語モデル:7b".chars().count(), 9);
let cells = |s: &str| -> usize {
s.chars()
.map(|c| {
if ('\u{1100}'..='\u{115f}').contains(&c)
|| ('\u{2e80}'..='\u{a4cf}').contains(&c)
|| ('\u{ac00}'..='\u{d7a3}').contains(&c)
|| ('\u{f900}'..='\u{faff}').contains(&c)
|| ('\u{ff00}'..='\u{ff60}').contains(&c)
{
2
} else {
1
}
})
.sum()
};
assert_eq!(
cells(&t.rows[0]),
cells(&t.rows[1]),
"the two rows line up on a terminal — they differed by 6 cells before"
);
assert_eq!(cells(&t.head[0]), cells(&t.rows[0]));
assert_eq!(cells(&t.head[1]), cells(&t.rows[0]));
}
#[test]
fn print_capabilities_table_handles_empty_model_list() {
let cache = CapabilityCache::default();
print_capabilities_table(&[], &cache, "none", "http://localhost:11434", false);
}
#[test]
fn print_capabilities_table_renders_all_branches() {
let mut cache = CapabilityCache::default();
for (name, conf) in [
("m-none", TuneConfidence::None),
("m-low", TuneConfidence::Low),
("m-med", TuneConfidence::Medium),
("m-high", TuneConfidence::High),
] {
let mut e = make_entry();
e.tune_confidence = conf;
cache.insert(mk(name), e);
}
cache.insert(
mk("m-noctx"),
CapabilityEntry {
conformance: ToolConformance::TextMode,
tested_date: "2026-06-06".to_string(),
..Default::default()
},
);
cache.insert(
mk("m-think"),
CapabilityEntry {
conformance: ToolConformance::Native,
tested_date: "2026-06-10".to_string(),
emits_thinking: Some(true),
..Default::default()
},
);
let models: Vec<ModelInfo> = [
("m-none", "7B"),
("m-low", "13B"),
("m-med", ""),
("m-high", "32.8B"),
("m-noctx", "3B"),
("m-think", "30B"),
("m-untested", "1B"),
]
.into_iter()
.map(|(n, s)| ModelInfo {
name: n.to_string(),
param_size: s.to_string(),
})
.collect();
print_capabilities_table(&models, &cache, "m-low", "http://localhost:11434", false);
print_capabilities_table(&models, &cache, "m-high", "http://localhost:11434", true);
print_capabilities_table(&models, &cache, "absent", "http://localhost:11434", true);
}