use std::collections::BTreeSet;
use serde_json::{Map, Value, json};
use shep_core::config::DogTable;
use super::table::{default_refusals, table_refusal, touched_refusal};
fn schema() -> Value {
json!({
"$ref": "#/$defs/Settings",
"$defs": {
"Settings": {
"type": "object",
"properties": {
"keep": { "type": "integer", "minimum": 1, "maximum": 64 },
"rate": { "type": "number", "exclusiveMinimum": 0 },
"tags": { "type": "array", "items": { "type": "string", "minLength": 3 } },
"levels": { "type": "array", "items": { "type": "integer", "minimum": 1 } },
"modes": {
"type": "array",
"items": { "oneOf": [{ "const": "ask" }, { "const": "auto" }] },
},
"merge": { "enum": ["ask", "auto"] },
"hours": {
"type": "object",
"properties": {
"start": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" },
},
},
"hosts": { "type": "array", "items": { "$ref": "#/$defs/Host" } },
"retro": { "$ref": "#/$defs/Step" },
"token": { "type": "string", "x-shep-secret": true, "pattern": "^sk-" },
"region": { "type": "string", "pattern": "^eu-" },
"max_age": {
"anyOf": [{ "$ref": "#/$defs/UpDuration" }, { "type": "null" }],
},
},
},
"Host": {
"type": "object",
"properties": {
"name": { "type": "string" },
"weight": { "type": "integer", "minimum": 1 },
},
},
"Step": {
"oneOf": [
{
"type": "object",
"properties": { "kind": { "type": "string", "const": "none" } },
"required": ["kind"],
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "path" },
"path": { "type": "string", "pattern": "^skills/" },
},
"required": ["kind", "path"],
},
{
"type": "object",
"properties": {
"kind": { "type": "string", "const": "plugin" },
"plugin": { "type": "string" },
"depth": { "type": "integer", "minimum": 1, "maximum": 3 },
},
"required": ["kind", "plugin"],
},
],
},
"UpDuration": { "type": "string", "pattern": "^\\d+(ms|h|m|s)?$" },
},
})
}
fn table(value: Value) -> Map<String, Value> {
value.as_object().expect("a table is an object").clone()
}
fn accepted() -> Value {
json!({
"keep": 8,
"rate": 1.5,
"tags": ["ask", "auto"],
"levels": [1, 2],
"modes": ["ask", "auto"],
"merge": "ask",
"hours": { "start": "09:30" },
"hosts": [{ "name": "build", "weight": 2 }],
"retro": { "kind": "plugin", "plugin": "retro", "depth": 2 },
"token": "sk-live-51Hx9Qa",
"max_age": "2h",
})
}
#[test]
fn a_table_the_schema_takes_is_refused_nowhere() {
assert_eq!(table_refusal(&schema(), &table(accepted())), None);
}
#[test]
fn a_scalar_out_of_its_floor_is_refused_with_its_path_and_sentence() {
let refused = table_refusal(&schema(), &table(json!({ "keep": 0 }))).expect("0 is under 1");
assert_eq!(refused.path, ["keep"]);
assert_eq!(refused.text, "keep is \"0\"; keep starts at 1");
}
#[test]
fn a_scalar_that_is_not_a_number_is_refused_in_the_panes_own_words() {
let refused = table_refusal(&schema(), &table(json!({ "keep": "lots" })))
.expect("\"lots\" is not a whole number");
assert_eq!(refused.path, ["keep"]);
assert_eq!(
refused.text,
"keep is \"lots\", which is not a whole number shep accepts"
);
}
#[test]
fn a_closed_set_refuses_a_value_it_does_not_name() {
let refused = table_refusal(&schema(), &table(json!({ "merge": "never" })))
.expect("never is not in the set");
assert_eq!(refused.path, ["merge"]);
assert_eq!(
refused.text,
"merge is \"never\"; merge takes one of ask, auto"
);
}
#[test]
fn a_list_element_the_item_schema_refuses_is_named_with_its_index() {
let refused = table_refusal(&schema(), &table(json!({ "tags": ["ask", "ab"] })))
.expect("ab is under the item schema's minLength");
assert_eq!(refused.path, ["tags", "1"]);
assert_eq!(
refused.text,
"tags.1 is \"ab\"; tags.1 needs at least 3 characters"
);
}
#[test]
fn a_value_where_a_list_belongs_is_refused_as_a_list() {
let refused =
table_refusal(&schema(), &table(json!({ "tags": "ask" }))).expect("a string is not a list");
assert_eq!(refused.path, ["tags"]);
assert_eq!(
refused.text,
"tags is \"ask\", which is not a list shep accepts"
);
}
#[test]
fn a_union_that_names_no_kind_is_refused_with_the_kinds_it_takes() {
let refused = table_refusal(&schema(), &table(json!({ "retro": { "kind": "robot" } })))
.expect("no arm holds a const on robot");
assert_eq!(refused.path, ["retro"]);
assert_eq!(
refused.text,
"retro is \"robot\", which is not a kind retro takes; try one of none, path, plugin"
);
}
#[test]
fn a_union_that_names_no_tag_at_all_says_so() {
let refused = table_refusal(&schema(), &table(json!({ "retro": { "plugin": "retro" } })))
.expect("the arm's fields are not the tag");
assert_eq!(refused.path, ["retro"]);
assert_eq!(
refused.text,
"retro names no kind of its own; try one of none, path, plugin"
);
}
#[test]
fn a_field_of_the_chosen_arm_is_held_to_its_own_bounds() {
let refused = table_refusal(
&schema(),
&table(json!({ "retro": { "kind": "plugin", "plugin": "retro", "depth": 9 } })),
)
.expect("9 is over the arm's ceiling");
assert_eq!(refused.path, ["retro", "depth"]);
assert_eq!(refused.text, "retro.depth is \"9\"; retro.depth stops at 3");
let refused = table_refusal(
&schema(),
&table(json!({ "retro": { "kind": "path", "path": "mine" } })),
)
.expect("mine does not match the arm's pattern");
assert_eq!(refused.path, ["retro", "path"]);
assert_eq!(
refused.text,
"retro.path is \"mine\", which it does not take; try it matches ^skills/"
);
}
#[test]
fn a_nested_tables_leaf_is_refused_at_its_whole_path() {
let refused = table_refusal(&schema(), &table(json!({ "hours": { "start": "9am" } })))
.expect("9am does not match the pattern");
assert_eq!(refused.path, ["hours", "start"]);
assert_eq!(
refused.text,
"hours.start is \"9am\", which it does not take; try it matches ^\\d{2}:\\d{2}$"
);
}
#[test]
fn a_scalar_where_the_schema_has_a_table_is_refused_at_the_table() {
let refused = table_refusal(&schema(), &table(json!({ "hours": "09:30" })))
.expect("a string is not a table");
assert_eq!(refused.path, ["hours"]);
assert_eq!(
refused.text,
"hours is \"09:30\", which is not a table shep accepts"
);
}
#[test]
fn an_entry_of_an_array_of_tables_is_refused_at_its_index() {
let refused = table_refusal(
&schema(),
&table(json!({ "hosts": [{ "name": "build" }, { "name": "web", "weight": 0 }] })),
)
.expect("weight 0 is under the item schema's floor");
assert_eq!(refused.path, ["hosts", "1", "weight"]);
assert_eq!(
refused.text,
"hosts.1.weight is \"0\"; hosts.1.weight starts at 1"
);
}
#[test]
fn a_nullable_grammar_refuses_a_spelling_shep_does_not_take() {
let refused = table_refusal(&schema(), &table(json!({ "max_age": "1w" })))
.expect("1w is outside the grammar");
assert_eq!(refused.path, ["max_age"]);
assert_eq!(
refused.text,
"max_age is \"1w\", which is not a duration shep accepts; \
try 500ms, 2s, 5m, 1h, 7d, a bare number is milliseconds"
);
}
#[test]
fn a_secrets_value_never_reaches_the_sentence() {
const CREDENTIAL: &str = "sk_live_HUNTER2";
let refused = table_refusal(&schema(), &table(json!({ "token": CREDENTIAL })))
.expect("the pattern refuses it");
assert_eq!(
refused.text,
"token is \"<set>\", which it does not take; try it matches ^sk-"
);
assert!(!refused.text.contains(CREDENTIAL), "{}", refused.text);
assert!(!format!("{refused:?}").contains(CREDENTIAL), "{refused:?}");
}
#[test]
fn an_ordinary_value_is_still_quoted_back() {
let refused = table_refusal(&schema(), &table(json!({ "region": "Europe" })))
.expect("Europe does not match ^eu-");
assert_eq!(refused.path, ["region"]);
assert!(refused.text.contains("Europe"), "{}", refused.text);
}
#[test]
fn a_key_the_schema_does_not_name_is_left_alone() {
assert_eq!(
table_refusal(
&schema(),
&table(
json!({ "keep": 8, "operator_wrote_this": "anything", "hours": { "start": "09:30", "extra": 7 } })
)
),
None
);
}
#[test]
fn a_null_is_unset_and_not_a_value_to_check() {
assert_eq!(
table_refusal(
&schema(),
&table(
json!({ "keep": null, "tags": null, "retro": null, "max_age": null, "hours": null })
)
),
None
);
}
#[test]
fn a_read_only_row_refuses_nothing() {
let schema = json!({
"type": "object",
"properties": {
"maybe": { "type": "array", "items": { "type": ["string", "null"] } },
"labels": { "type": "object", "additionalProperties": { "type": "string" } },
},
});
assert_eq!(
table_refusal(
&schema,
&table(json!({ "maybe": [1, 2], "labels": { "any": 7 } }))
),
None
);
}
#[test]
fn the_first_refusal_in_row_order_is_the_only_answer() {
let refused = table_refusal(&schema(), &table(json!({ "keep": 0, "merge": "never" })))
.expect("two refusals are available");
assert_eq!(refused.path, ["keep"]);
}
#[test]
fn the_debug_of_a_refusal_names_the_path_and_the_sentence() {
let refused = table_refusal(&schema(), &table(json!({ "keep": 0 }))).expect("0 is under 1");
assert_eq!(
format!("{refused:?}"),
"TableRefusal { path: [\"keep\"], text: \"keep is \\\"0\\\"; keep starts at 1\" }"
);
}
fn touched(paths: &[&[&str]]) -> BTreeSet<Vec<String>> {
paths
.iter()
.map(|path| path.iter().map(|name| (*name).to_owned()).collect())
.collect()
}
#[test]
fn a_touched_scalar_is_refused_and_an_untouched_one_is_not() {
let proposed = table(json!({ "keep": 0, "merge": "never" }));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["keep"]]))
.expect("keep is a key this caller chose");
assert_eq!(refused.path, ["keep"]);
assert_eq!(refused.text, "keep is \"0\"; keep starts at 1");
assert_eq!(
touched_refusal(&schema(), &proposed, &touched(&[&["region"]])),
None,
"the two refusals in this table sit at keys nobody here chose"
);
}
#[test]
fn a_touched_list_element_is_refused_and_an_untouched_one_is_not() {
let proposed = table(json!({ "tags": ["ask", "ab"] }));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["tags", "1"]]))
.expect("the second element is the one that was edited");
assert_eq!(refused.path, ["tags", "1"]);
assert_eq!(
refused.text,
"tags.1 is \"ab\"; tags.1 needs at least 3 characters"
);
assert_eq!(
touched_refusal(&schema(), &proposed, &touched(&[&["tags", "0"]])),
None,
"the element this caller edited is the one the item schema takes"
);
}
#[test]
fn a_touched_union_is_refused_for_the_tag_it_names() {
let proposed = table(json!({ "retro": { "kind": "robot" } }));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["retro"]]))
.expect("the union row is the one that was edited");
assert_eq!(refused.path, ["retro"]);
assert_eq!(
refused.text,
"retro is \"robot\", which is not a kind retro takes; try one of none, path, plugin"
);
assert_eq!(
touched_refusal(&schema(), &proposed, &touched(&[&["keep"]])),
None,
"an untouched key does not get to report the union"
);
}
#[test]
fn a_touched_nested_leaf_is_refused_at_its_whole_path() {
let proposed = table(json!({ "hours": { "start": "9am" } }));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["hours", "start"]]))
.expect("the leaf is the row an editor writes");
assert_eq!(refused.path, ["hours", "start"]);
assert_eq!(
refused.text,
"hours.start is \"9am\", which it does not take; try it matches ^\\d{2}:\\d{2}$"
);
assert_eq!(
touched_refusal(&schema(), &proposed, &touched(&[&["hours"]])),
None,
"the table row itself names no value to check"
);
}
#[test]
fn a_touched_entry_leaf_is_refused_and_another_entry_is_not() {
let proposed = table(json!({
"hosts": [{ "name": "build", "weight": 0 }, { "name": "web", "weight": 0 }]
}));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["hosts", "0", "weight"]]))
.expect("the first entry's weight is the one that was edited");
assert_eq!(refused.path, ["hosts", "0", "weight"]);
assert_eq!(
refused.text,
"hosts.0.weight is \"0\"; hosts.0.weight starts at 1"
);
assert_eq!(
touched_refusal(&schema(), &proposed, &touched(&[&["hosts", "1", "name"]])),
None,
"the other entry's bad weight rode in from the file and is not this caller's"
);
}
#[test]
fn the_scoped_door_answers_what_the_whole_door_answers_for_the_same_key() {
let proposed = table(json!({ "keep": 0, "merge": "never" }));
let whole = table_refusal(&schema(), &proposed).expect("keep is the first refusal");
let scoped = touched_refusal(&schema(), &proposed, &touched(&[&["keep"]]))
.expect("asked about keep, answered about keep");
assert_eq!(scoped, whole);
}
#[test]
fn a_string_that_reads_like_a_number_is_refused_for_an_integer_field() {
let refused = table_refusal(&schema(), &table(json!({ "keep": "8" })))
.expect("\"8\" is a string, not a number");
assert_eq!(refused.path, ["keep"]);
assert_eq!(
refused.text,
"keep is \"8\", which is not a whole number shep accepts"
);
}
#[test]
fn a_string_that_reads_like_a_decimal_is_refused_for_a_number_field() {
let refused = table_refusal(&schema(), &table(json!({ "rate": "1.5" })))
.expect("\"1.5\" is a string, not a number");
assert_eq!(refused.path, ["rate"]);
assert_eq!(
refused.text,
"rate is \"1.5\", which is not a number shep accepts"
);
}
#[test]
fn a_decimal_is_refused_for_an_integer_field() {
let refused = table_refusal(&schema(), &table(json!({ "keep": 8.5 })))
.expect("8.5 is not a whole number");
assert_eq!(
refused.text,
"keep is \"8.5\", which is not a whole number shep accepts"
);
}
#[test]
fn a_json_number_is_taken_and_a_number_out_of_bounds_is_refused_by_its_bound() {
assert_eq!(
table_refusal(&schema(), &table(json!({ "keep": 8, "rate": 1.5 }))),
None,
"the shape the pane files"
);
let refused = table_refusal(&schema(), &table(json!({ "rate": 0 })))
.expect("0 is not above a floor of 0");
assert_eq!(refused.text, "rate is \"0\"; rate must be above 0");
}
#[test]
fn a_number_element_is_refused_for_a_string_list() {
let refused = table_refusal(&schema(), &table(json!({ "tags": ["ask", 8] })))
.expect("8 is a number, not a string");
assert_eq!(refused.path, ["tags", "1"]);
assert_eq!(
refused.text,
"tags.1 is \"8\", which is not a string shep accepts"
);
}
#[test]
fn a_boolean_element_is_refused_for_a_string_list() {
let refused = table_refusal(&schema(), &table(json!({ "tags": [true] })))
.expect("true is a boolean, not a string");
assert_eq!(refused.path, ["tags", "0"]);
assert_eq!(
refused.text,
"tags.0 is \"true\", which is not a string shep accepts"
);
}
#[test]
fn a_string_that_reads_like_a_number_is_refused_for_an_integer_list() {
let refused = table_refusal(&schema(), &table(json!({ "levels": ["2"] })))
.expect("\"2\" is a string, not a number");
assert_eq!(refused.path, ["levels", "0"]);
assert_eq!(
refused.text,
"levels.0 is \"2\", which is not a whole number shep accepts"
);
}
#[test]
fn an_integer_element_is_taken_and_one_under_the_floor_is_refused_by_the_floor() {
assert_eq!(
table_refusal(&schema(), &table(json!({ "levels": [2, 3] }))),
None,
"the shape the list editor files"
);
let refused = table_refusal(&schema(), &table(json!({ "levels": [0] })))
.expect("0 is under the item schema's floor");
assert_eq!(refused.path, ["levels", "0"]);
assert_eq!(refused.text, "levels.0 is \"0\"; levels.0 starts at 1");
}
#[test]
fn a_type_refusal_for_a_secret_names_the_marker_and_not_the_value() {
let refused = table_refusal(&schema(), &table(json!({ "token": 90210 })))
.expect("a number is not a string");
assert_eq!(
refused.text,
"token is \"<set>\", which is not a string shep accepts"
);
assert!(!refused.text.contains("90210"), "{}", refused.text);
}
#[test]
fn a_list_item_closed_set_spelled_as_a_one_of_refuses_a_name_it_does_not_carry() {
let refused = table_refusal(&schema(), &table(json!({ "modes": ["ask", "never"] })))
.expect("never is not one of the two names the item carries");
assert_eq!(refused.path, ["modes", "1"]);
assert_eq!(
refused.text,
"modes.1 is \"never\"; modes.1 takes one of ask, auto"
);
}
#[test]
fn an_untouched_bad_element_does_not_hide_a_touched_one() {
let proposed = table(json!({ "tags": ["a", "b"] }));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["tags", "1"]]))
.expect("tags.1 is under the item schema's minLength");
assert_eq!(refused.path, ["tags", "1"]);
assert_eq!(
refused.text,
"tags.1 is \"b\"; tags.1 needs at least 3 characters"
);
}
#[test]
fn an_untouched_element_of_the_wrong_type_does_not_hide_a_touched_one() {
let proposed = table(json!({ "tags": [8, "ab"] }));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["tags", "1"]]))
.expect("tags.1 is under the item schema's minLength");
assert_eq!(refused.path, ["tags", "1"]);
assert_eq!(
refused.text,
"tags.1 is \"ab\"; tags.1 needs at least 3 characters"
);
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["tags", "0"]]))
.expect("asked about tags.0, which is a number");
assert_eq!(
refused.text,
"tags.0 is \"8\", which is not a string shep accepts"
);
}
#[test]
fn an_untouched_entry_that_is_not_a_table_does_not_hide_a_touched_one() {
let proposed = table(json!({ "hosts": ["not a table", { "name": "web", "weight": 0 }] }));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["hosts", "1", "weight"]]))
.expect("hosts.1.weight is under the item schema's floor");
assert_eq!(refused.path, ["hosts", "1", "weight"]);
assert_eq!(
refused.text,
"hosts.1.weight is \"0\"; hosts.1.weight starts at 1"
);
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["hosts", "0"]]))
.expect("asked about hosts.0, which is a string");
assert_eq!(
refused.text,
"hosts.0 is \"not a table\", which is not a table shep accepts"
);
}
#[test]
fn an_untouched_map_entry_that_is_not_a_table_does_not_hide_a_touched_one() {
let schema = json!({
"type": "object",
"properties": {
"hosts": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": { "weight": { "type": "integer", "minimum": 1 } },
},
},
},
});
let proposed = table(json!({ "hosts": { "a": "not a table", "b": { "weight": 0 } } }));
let refused = touched_refusal(&schema, &proposed, &touched(&[&["hosts", "b", "weight"]]))
.expect("hosts.b.weight is under the item schema's floor");
assert_eq!(refused.path, ["hosts", "b", "weight"]);
assert_eq!(
refused.text,
"hosts.b.weight is \"0\"; hosts.b.weight starts at 1"
);
}
#[test]
fn a_union_arm_that_requires_a_field_the_table_does_not_hold_is_refused() {
let refused = table_refusal(&schema(), &table(json!({ "retro": { "kind": "path" } })))
.expect("the path arm requires path");
assert_eq!(refused.path, ["retro"]);
assert_eq!(
refused.text,
"retro.path is required when retro.kind is path; give it a value"
);
}
#[test]
fn a_union_arm_that_holds_what_it_requires_is_refused_nowhere() {
assert_eq!(
table_refusal(&schema(), &table(json!({ "retro": { "kind": "none" } }))),
None,
"the none arm requires the tag and nothing else"
);
assert_eq!(
table_refusal(
&schema(),
&table(json!({ "retro": { "kind": "path", "path": "skills/a" } }))
),
None,
"the path arm holding its own field"
);
}
#[test]
fn a_required_field_held_as_null_is_left_as_the_pane_leaves_it() {
assert_eq!(
table_refusal(
&schema(),
&table(json!({ "retro": { "kind": "path", "path": null } }))
),
None,
"the pane files a null for a field it clears, and does not refuse it"
);
}
#[test]
fn a_required_field_with_no_editor_is_not_asked_for() {
let schema = json!({
"type": "object",
"properties": {
"retro": {
"oneOf": [
{
"type": "object",
"properties": {
"kind": { "const": "blob" },
"blob": {
"type": "object",
"properties": { "keep": { "type": "integer" } },
},
},
"required": ["kind", "blob"],
},
],
},
},
});
assert_eq!(
table_refusal(&schema, &table(json!({ "retro": { "kind": "blob" } }))),
None,
"the arm's nested table is a read-only row, and a read-only row is not asked for"
);
}
#[test]
fn a_touched_union_missing_a_required_field_is_refused_and_an_untouched_one_is_not() {
let proposed = table(json!({ "retro": { "kind": "path" } }));
let refused = touched_refusal(&schema(), &proposed, &touched(&[&["retro"]]))
.expect("the union row is the one that was edited");
assert_eq!(refused.path, ["retro"]);
assert_eq!(
refused.text,
"retro.path is required when retro.kind is path; give it a value"
);
assert_eq!(
touched_refusal(&schema(), &proposed, &touched(&[&["keep"]])),
None,
"an untouched union keeps its own business"
);
}
#[test]
fn a_default_is_checked_against_the_layered_table_of_every_sheep_it_reaches() {
let default = DogTable::from(table(json!({ "keep": 100, "merge": "ask" })));
let sheep = std::collections::BTreeMap::from([
("a".to_string(), DogTable::from(table(json!({})))),
("b".to_string(), DogTable::from(table(json!({ "keep": 4 })))),
(
"c".to_string(),
DogTable::from(table(json!({ "merge": "auto" }))),
),
]);
let refused = default_refusals(&schema(), &default, &sheep, &touched(&[&["keep"]]));
assert_eq!(
refused
.sheep
.iter()
.map(|(name, refusal)| (name.as_str(), refusal.path.clone()))
.collect::<Vec<_>>(),
[
("a", vec!["keep".to_string()]),
("c", vec!["keep".to_string()])
]
);
assert!(
refused.sheep[0].1.text.contains("keep"),
"{}",
refused.sheep[0].1.text
);
assert_eq!(refused.default, None, "a sheep already names that value");
assert!(
default_refusals(&schema(), &default, &sheep, &BTreeSet::new()).is_empty(),
"a default that puts no value anywhere is checked against nothing"
);
let reaches_nobody = std::collections::BTreeMap::new();
assert!(
default_refusals(&schema(), &default, &reaches_nobody, &touched(&[&["keep"]])).is_empty()
);
}
#[test]
fn a_default_a_sheep_shadows_is_still_judged_by_its_own_value() {
let default = DogTable::from(table(json!({ "keep": 100 })));
let sheep = std::collections::BTreeMap::from([
("a".to_string(), DogTable::from(table(json!({ "keep": 4 })))),
("b".to_string(), DogTable::from(table(json!({ "keep": 5 })))),
]);
let refused = default_refusals(&schema(), &default, &sheep, &touched(&[&["keep"]]));
assert_eq!(refused.sheep, []);
let by_default = refused.default.expect("the default's own keep is over 64");
assert_eq!(by_default.path, ["keep"]);
assert!(by_default.text.contains("keep"), "{}", by_default.text);
let reaches_nobody = std::collections::BTreeMap::new();
assert!(
default_refusals(&schema(), &default, &reaches_nobody, &touched(&[&["keep"]])).is_empty(),
"a default that reaches no sheep is checked against nothing"
);
assert!(
default_refusals(&schema(), &default, &sheep, &touched(&[&["merge"]])).is_empty(),
"a path the write did not touch is not asked about"
);
}
#[test]
fn a_default_refusal_a_sheep_does_not_name_is_kept_beside_one_it_does() {
let default = DogTable::from(table(json!({ "keep": 100, "rate": -1 })));
let sheep = std::collections::BTreeMap::from([
("a".to_string(), DogTable::from(table(json!({ "rate": 2 })))),
(
"b".to_string(),
DogTable::from(table(json!({ "keep": 5, "rate": 3 }))),
),
]);
let refused = default_refusals(
&schema(),
&default,
&sheep,
&touched(&[&["keep"], &["rate"]]),
);
assert_eq!(
refused
.sheep
.iter()
.map(|(n, r)| (n.as_str(), r.path.clone()))
.collect::<Vec<_>>(),
[("a", vec!["keep".to_string()])]
);
assert_eq!(
refused.default.map(|r| r.path),
Some(vec!["rate".to_string()])
);
}