use super::{
Control, FieldSpec, MODELS_SCHEMA, ROLES_SCHEMA, Row, Schema, read, schema_for, write,
};
use crate::model_pick::grammar::MODELS_YAML;
use crate::model_pick::tests::{SEEDED_MODELS, TEMPLATE_PROVIDERS};
fn rows() -> Vec<String> {
vec!["codex".to_string(), "anthropic".to_string()]
}
fn row_of(rows: &[Row], entry: &str, field: &str) -> Row {
rows.iter()
.find(|r| r.entry == entry && r.name == field)
.cloned()
.unwrap_or_else(|| panic!("no {entry}.{field} row"))
}
fn fields_of(rows: &[Row], entry: &str) -> Vec<String> {
rows.iter()
.filter(|r| r.entry == entry)
.map(|r| r.name.clone())
.collect()
}
fn spec_of(schema: &Schema, name: &str) -> FieldSpec {
*schema
.fields
.iter()
.find(|f| f.name == name)
.unwrap_or_else(|| panic!("no {name} field"))
}
#[test]
fn schema_is_the_three_files_yog_reads_and_nothing_else() {
assert_eq!(schema_for(MODELS_YAML).map(|s| s.block), Some("models"));
assert_eq!(schema_for("providers.yaml").map(|s| s.block), Some("roles"));
assert_eq!(schema_for("cadence.yaml").map(|s| s.block), Some("cadence"));
assert!(schema_for("compact.yaml").is_none());
assert!(schema_for("").is_none());
}
#[test]
fn cadence_yaml_reads_as_the_watcher_entry_and_writes_in_place() {
use crate::app::cadence;
let schema = schema_for(cadence::CADENCE_YAML).unwrap();
let rows = read(&schema, cadence::TEMPLATE, &[]);
assert!(rows.iter().all(|r| r.entry == "watcher"), "{rows:?}");
assert_eq!(
fields_of(&rows, "watcher"),
["debounce_ms", "cheap_sweep_ms", "full_sweep_ms"],
"the three periods, each typed"
);
assert!(rows.iter().all(|r| r.fault.is_none()));
let text = write(
&schema,
cadence::TEMPLATE,
"watcher",
&spec_of(&schema, "cheap_sweep_ms"),
"5000",
)
.unwrap();
assert_eq!(cadence::parse(&text).cheap_sweep.as_millis(), 5000);
assert!(
text.contains("full_sweep_ms: 15000") && text.contains("# yog's clock"),
"siblings and comments survive the anchored write: {text}"
);
}
#[test]
fn models_yaml_reads_as_one_group_per_declared_id() {
let rows = read(&MODELS_SCHEMA, SEEDED_MODELS, &rows());
assert_eq!(
fields_of(&rows, "gpt-5.4"),
vec!["model_id", "context_window"]
);
assert_eq!(rows.len(), 2, "one entry, its two settings: {rows:?}");
assert_eq!(row_of(&rows, "gpt-5.4", "model_id").value, "gpt-5.4");
assert_eq!(row_of(&rows, "gpt-5.4", "context_window").value, "400000");
assert!(!row_of(&rows, "gpt-5.4", "context_window").help.is_empty());
}
#[test]
fn providers_yaml_reads_as_one_group_per_role() {
let rows = read(&ROLES_SCHEMA, TEMPLATE_PROVIDERS, &rows());
let mut names: Vec<&str> = rows.iter().map(|r| r.entry.as_str()).collect();
names.dedup();
assert_eq!(
names,
vec!["worker", "compactor"],
"entry order is file order"
);
assert_eq!(
row_of(&rows, "worker", "tools").value,
"bash, read_file, load_skill"
);
assert_eq!(fields_of(&rows, "compactor"), vec!["provider", "model"]);
}
#[test]
fn a_file_with_no_block_has_no_settings() {
assert!(read(&MODELS_SCHEMA, "steps:\n - run\n", &rows()).is_empty());
assert!(read(&ROLES_SCHEMA, "", &rows()).is_empty());
}
#[test]
fn a_provider_row_brazen_lacks_is_faulted_where_it_is_read() {
let dead = "roles:\n worker:\n provider: gone\n model: m\n";
let fault = row_of(&read(&ROLES_SCHEMA, dead, &rows()), "worker", "provider").fault;
assert!(fault.is_some_and(|f| f.contains("`gone`")));
assert_eq!(
row_of(&read(&ROLES_SCHEMA, dead, &[]), "worker", "provider").fault,
None
);
}
#[test]
fn an_off_shape_list_and_an_off_range_number_fault_rather_than_render_typed() {
let odd_list = "roles:\n worker:\n provider: codex\n model: m\n tools:\n";
let tools = row_of(&read(&ROLES_SCHEMA, odd_list, &rows()), "worker", "tools");
assert_eq!(tools.value, "");
assert!(tools.fault.is_some_and(|f| f.contains("inline")));
let odd = "models:\n m:\n context_window: lots\n";
let window = row_of(&read(&MODELS_SCHEMA, odd, &rows()), "m", "context_window");
assert_eq!(window.value, "lots");
assert!(window.fault.is_some_and(|f| f.contains("whole number")));
let zero = "models:\n m:\n context_window: 0\n";
assert!(
row_of(&read(&MODELS_SCHEMA, zero, &rows()), "m", "context_window")
.fault
.is_some()
);
}
#[test]
fn writing_a_control_rewrites_one_line_and_nothing_else() {
let spec = spec_of(&MODELS_SCHEMA, "model_id");
let out = write(
&MODELS_SCHEMA,
SEEDED_MODELS,
"gpt-5.4",
&spec,
" gpt-5.4-mini ",
)
.unwrap();
assert!(out.contains(" model_id: gpt-5.4-mini\n"));
assert!(out.starts_with("# Global config-root models.yaml"));
assert!(out.contains(" provider: codex\n"));
assert!(out.contains(" capabilities: [tool_use_native, streaming]\n"));
}
#[test]
fn a_number_is_clamped_and_a_list_is_re_emitted_as_a_flow_sequence() {
let window = spec_of(&MODELS_SCHEMA, "context_window");
let at = |v| write(&MODELS_SCHEMA, SEEDED_MODELS, "gpt-5.4", &window, v).unwrap();
assert!(at("999999999999").contains(" context_window: 100000000\n"));
assert!(at("").contains(" context_window: 1\n"));
let tools = spec_of(&ROLES_SCHEMA, "tools");
let at = |v| write(&ROLES_SCHEMA, TEMPLATE_PROVIDERS, "worker", &tools, v).unwrap();
assert!(at(" a , ,b ").contains(" tools: [a, b]\n"));
assert!(at("").contains(" tools: []\n"));
}
#[test]
fn writing_a_role_field_declines_loudly_off_grammar() {
let model = spec_of(&ROLES_SCHEMA, "model");
assert!(
write(
&ROLES_SCHEMA,
TEMPLATE_PROVIDERS,
"worker",
&model,
"opus-5"
)
.is_ok()
);
let err = write(&ROLES_SCHEMA, "roles: {}\n", "worker", &model, "opus-5").unwrap_err();
assert!(err.to_string().contains("inline value"));
}
#[test]
fn control_kinds_compare_by_value() {
assert_eq!(Control::Provider, Control::Provider);
assert_ne!(Control::Number { min: 1, max: 2 }, Control::Text);
}