use super::{toml_document, unknown_key_paths, yaml_document};
use crate::core::config::MpmConfig;
use crate::core::trusty_tools_config::TrustyToolsConfig;
fn mpm_unknown(raw: &str) -> Vec<String> {
let doc = toml_document(raw).expect("valid toml");
let parsed: MpmConfig = toml::from_str(raw).expect("MpmConfig parses leniently");
unknown_key_paths(&doc, &parsed)
}
#[test]
fn unknown_top_level_key_is_reported() {
assert_eq!(
mpm_unknown("[modles]\ndefault = \"opus\"\n"),
vec!["modles"]
);
}
#[test]
fn unknown_nested_key_is_reported() {
assert_eq!(
mpm_unknown("[models]\ndefualt = \"opus\"\n"),
vec!["models.defualt"]
);
}
#[test]
fn known_keys_are_not_reported() {
assert!(
mpm_unknown("[models]\ndefault = \"opus\"\n\n[models.tiers]\nopus = \"claude-opus-4-5\"\n")
.is_empty()
);
}
#[test]
fn map_valued_sections_are_not_reported() {
assert!(
mpm_unknown("[models.agents]\nengineer = \"haiku\"\nsome-custom-agent = \"opus\"\n")
.is_empty(),
"arbitrary map keys are data, not schema violations"
);
}
#[test]
fn every_unknown_key_is_reported() {
let unknown = mpm_unknown("[modles]\nx = 1\n\n[models]\ndefualt = \"opus\"\n");
assert!(unknown.contains(&"modles".to_string()), "{unknown:?}");
assert!(
unknown.contains(&"models.defualt".to_string()),
"{unknown:?}"
);
}
#[test]
fn explicit_null_is_not_reported() {
let raw = "default_model: null\nauto_resume: true\n";
let doc = yaml_document(raw).expect("valid yaml mapping");
let parsed: TrustyToolsConfig = serde_yaml::from_str(raw).expect("parses");
assert!(
unknown_key_paths(&doc, &parsed).is_empty(),
"an explicitly-null optional field is not an unknown key"
);
}
#[test]
fn unknown_yaml_key_is_reported() {
let raw = "defualt_model: opus\n";
let doc = yaml_document(raw).expect("valid yaml mapping");
let parsed: TrustyToolsConfig = serde_yaml::from_str(raw).expect("parses leniently");
assert_eq!(unknown_key_paths(&doc, &parsed), vec!["defualt_model"]);
}
#[test]
fn report_is_silent_for_a_clean_document() {
let raw = "workspace_root_template: ~/code\nauto_resume: false\n";
let doc = yaml_document(raw).expect("valid yaml mapping");
let parsed: TrustyToolsConfig = serde_yaml::from_str(raw).expect("parses");
assert!(unknown_key_paths(&doc, &parsed).is_empty());
}
#[test]
fn non_mapping_yaml_yields_no_document() {
assert!(yaml_document("").is_none());
assert!(yaml_document("just-a-scalar\n").is_none());
}
#[test]
fn build_lease_keys_are_not_reported() {
let text = "[builders]\nmax_concurrent = 2\nmemory_pressure_max = \"warn\"\nlease_wait_secs = 30\n\
heavy_build_commands = [\"cargo test\"]\ncount_foreign_builds = false\n\
min_available_pct = 5\nmax_concurent = 3\n";
let raw = toml_document(text).expect("toml");
let parsed: MpmConfig = toml::from_str(text).expect("parse");
assert_eq!(
unknown_key_paths(&raw, &parsed),
vec!["builders.max_concurent".to_string()]
);
}
#[test]
fn a_product_written_supervisor_session_is_not_reported() {
let text = "[supervisor]\nprojects = [\"/p\"]\nsession = \"tm-supervisor\"\n";
assert!(mpm_unknown(text).is_empty(), "{:?}", mpm_unknown(text));
}
#[test]
fn an_unknown_key_warns_once_per_process_across_repeated_loads() {
use tracing_subscriber::layer::SubscriberExt;
crate::test_support::enable_event_capture();
let root = tempfile::tempdir().expect("tempdir");
std::fs::write(
root.path().join("config.toml"),
"[supervisor]\nsession = \"tm-supervisor\"\n\n[modles]\ndefault = \"opus\"\n",
)
.expect("write config");
let buffer = trusty_common::log_buffer::LogBuffer::new(64);
let subscriber = tracing_subscriber::registry().with(
trusty_common::log_buffer::LogBufferLayer::new(buffer.clone()),
);
tracing::subscriber::with_default(subscriber, || {
MpmConfig::load(root.path());
MpmConfig::load(root.path());
});
let warnings: Vec<String> = buffer
.tail(64)
.into_iter()
.filter(|l| l.contains("unrecognised key"))
.collect();
assert_eq!(warnings.len(), 1, "{warnings:#?}");
assert!(warnings[0].contains("modles"), "{warnings:#?}");
assert!(!warnings[0].contains("supervisor.session"), "{warnings:#?}");
}
#[test]
fn accounts_table_is_not_reported() {
assert_eq!(
mpm_unknown("[accounts]\nduettoresearch = \"bob-duetto\"\n\n[acounts]\nx = \"y\"\n"),
vec!["acounts".to_string()]
);
}