mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
test(config): pin that every key survives a write and a read
`Config` is `#[serde(default)]`, which is what makes this worth asserting. A key whose serialized and deserialized names have drifted apart does not fail to load — it silently resolves to the default, so the setting is forgotten the next time the file is read, with nothing on any error path to say so. Nothing was checking that the names still agree. The test nudges every bool and number off its default, writes the config, reads it back, and requires the value to return rather than the default. Strings are skipped on purpose: most are enums in kebab-case, and `de_lenient` deliberately resolves a spelling it does not know to the default, so an arbitrary string would report that intended behaviour as a failure. Confirmed to fail for the case it exists for, rather than assumed: an injected `rename(serialize = "cursor_blink", deserialize = "cursor_blink_x")` trips it. A *symmetric* rename does not, and should not — that changes the file format consistently and loses nothing. Floats compare loosely because several fields are `f32` and a value nudged through `f64` returns a fraction off; an exact compare reported arithmetic as a lost setting on the first run. `checked > 20` guards the loop against silently testing nothing. No key fails today.
This commit is contained in:
@@ -1765,6 +1765,63 @@ mod tests {
|
||||
assert_eq!(clamp(100_000), 3600);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_config_key_survives_being_written_and_read_back() {
|
||||
// `#[serde(default)]` is what makes this worth asserting: a key whose
|
||||
// serialized name and deserialized name have drifted apart does not
|
||||
// fail to load, it silently resolves to the default — the setting is
|
||||
// simply forgotten the next time the file is read.
|
||||
//
|
||||
// Only bools and numbers are nudged. A string is usually an enum
|
||||
// spelled in kebab-case, and `de_lenient` deliberately falls back to
|
||||
// the default for a spelling it does not know, so an arbitrary string
|
||||
// would report that deliberate behaviour as a failure.
|
||||
let base = serde_json::to_value(Config::default()).expect("Config serializes");
|
||||
let obj = base.as_object().expect("a config is a JSON object").clone();
|
||||
let mut checked = 0;
|
||||
for (key, value) in &obj {
|
||||
let nudged = match value {
|
||||
serde_json::Value::Bool(b) => serde_json::Value::Bool(!b),
|
||||
serde_json::Value::Number(n) if n.is_u64() => {
|
||||
serde_json::Value::from(n.as_u64().unwrap() + 1)
|
||||
}
|
||||
serde_json::Value::Number(n) if n.is_f64() => {
|
||||
serde_json::Value::from(n.as_f64().unwrap() + 1.0)
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
let mut doc = obj.clone();
|
||||
doc.insert(key.clone(), nudged.clone());
|
||||
let parsed: Config = serde_json::from_value(serde_json::Value::Object(doc))
|
||||
.unwrap_or_else(|e| panic!("`{key}` set to {nudged} did not parse: {e}"));
|
||||
let back = serde_json::to_value(parsed).expect("Config serializes");
|
||||
let got = back.get(key).unwrap_or_else(|| panic!("`{key}` vanished"));
|
||||
// The property that matters is that the written value came back
|
||||
// rather than reverting to the default. Compared loosely for
|
||||
// floats: several of these are `f32`, so a value nudged through
|
||||
// `f64` comes back a fraction off and an exact compare would
|
||||
// report arithmetic as a lost setting.
|
||||
match (got.as_f64(), nudged.as_f64()) {
|
||||
(Some(a), Some(b)) => assert!(
|
||||
(a - b).abs() < 1e-3 * b.abs().max(1.0),
|
||||
"`{key}` did not survive a write and read back: wrote {nudged}, read {got}"
|
||||
),
|
||||
_ => assert_eq!(
|
||||
got, &nudged,
|
||||
"`{key}` did not survive a write and read back"
|
||||
),
|
||||
}
|
||||
assert_ne!(
|
||||
got, value,
|
||||
"`{key}` read back as its default, so the write was dropped"
|
||||
);
|
||||
checked += 1;
|
||||
}
|
||||
// A guard against this test quietly checking nothing if the shape of
|
||||
// the config changes.
|
||||
assert!(checked > 20, "only {checked} keys were nudged");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanitize_normalizes_blank_http_proxy_to_none() {
|
||||
let normalize = |proxy: Option<&str>| {
|
||||
|
||||
Reference in New Issue
Block a user