use std::collections::BTreeMap;
use crate::{cdr, price, schema, test, warning, Version};
const ELEM_TOTAL: usize = 6;
const WARN_TOTAL: usize = 6;
const WARN_TYPES_TOTAL: usize = 2;
const CDR_WITH_WARNINGS: &str = r#"{
"id": "12345",
"start_date_time": "2015-06-29T17:00:09Z",
"stop_date_time": "2015-06-29T19:00:00Z",
"currency": "EUR",
"charging_periods": [
{
"start_date_time": "2015-06-29T17:00:09Z",
"dimensions": [{
"type": "TIME",
"volume": 0.50003
}]
},
{
"start_date_time": "2015-06-29T17:30:09Z",
"dimensions": [{
"type": "TIME",
"volume": 0.50003
}]
},
{
"start_date_time": "2015-06-29T18:00:09Z",
"dimensions": [{
"type": "CONGESTION_TIME",
"volume": 0.50003
}]
},
{
"start_date_time": "2015-06-29T18:30:09Z",
"dimensions": [{
"type": "CONGESTION_TIME",
"volume": 0.50003
}]
},
{
"start_date_time": "2015-06-29T19:00:09Z",
"dimensions": [{
"type": "CONGESTION_TIME",
"volume": 0.50003
}]
},
{
"start_date_time": "2015-06-29T19:30:09Z",
"dimensions": [{
"type": "CONGESTION_TIME",
"volume": 0.50003
}]
}
],
"total_cost": 4.001234,
"total_energy": 15.342021,
"total_time": 6.03045
}"#;
#[test]
fn should_produce_expected_warnings() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let warnings = warnings.path_id_map();
let expect_warnings = BTreeMap::from([
("$", vec![id("periods_outside_start_end_date_time")]),
(
"$.charging_periods[0].dimensions[0].volume",
vec![id("excessive_precision")],
),
(
"$.charging_periods[1].dimensions[0].volume",
vec![id("excessive_precision")],
),
("$.total_cost", vec![id("excessive_precision")]),
("$.total_energy", vec![id("excessive_precision")]),
("$.total_time", vec![id("excessive_precision")]),
]);
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn schema_reports_the_unknown_dimensions() {
let cdr = cdr::from_json(
crate::json::parse_object(CDR_WITH_WARNINGS).unwrap(),
Version::V211,
);
let (_cdr, warnings) = cdr.into_parts();
let paths: Vec<_> = warnings
.path_map()
.into_iter()
.filter(|(_path, warnings)| {
warnings
.iter()
.any(|warning| matches!(warning, schema::Warning::FieldInvalidValue { .. }))
})
.map(|(path, _warnings)| path)
.collect();
assert_eq!(
paths,
vec![
"$.charging_periods[2].dimensions[0].type",
"$.charging_periods[3].dimensions[0].type",
"$.charging_periods[4].dimensions[0].type",
"$.charging_periods[5].dimensions[0].type",
]
);
}
#[test]
fn should_allow_all() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::None);
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(elements_filtered, 0, "No elements are filtered");
assert_eq!(
warning_distinct_types_filtered, 0,
"No warnings are filtered"
);
let expect_warnings = BTreeMap::from([
(
id("excessive_precision"),
vec![
"$.charging_periods[0].dimensions[0].volume",
"$.charging_periods[1].dimensions[0].volume",
"$.total_cost",
"$.total_energy",
"$.total_time",
],
),
(id("periods_outside_start_end_date_time"), vec!["$"]),
]);
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_limit_warning_ids_only() {
const WARN_LIMIT: usize = 1;
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::WarningTypes(WARN_LIMIT));
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(
elements_filtered, 5,
"The filtered warning type has 5 element paths"
);
assert_eq!(
warning_distinct_types_filtered, 1,
"There are two warning types raised; one is allowed"
);
let expect_warnings = BTreeMap::from([(id("periods_outside_start_end_date_time"), vec!["$"])]);
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_limit_zero_warning_ids_only() {
const WARN_LIMIT: usize = 0;
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::WarningTypes(WARN_LIMIT));
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(
warning_distinct_types_filtered, WARN_TYPES_TOTAL,
"All warning types are filtered"
);
assert_eq!(
elements_filtered, ELEM_TOTAL,
"All warning types are filtered, therefore all elements are filtered"
);
let expect_warnings = BTreeMap::new();
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_limit_element_paths() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::ElemPathsPerId(3));
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(
elements_filtered, 2,
"The first warning type has 2 of its 5 elements filtered; the second type has 1 \
element path, which is within the limit."
);
assert_eq!(
warning_distinct_types_filtered, 0,
"Only element paths are filtered; all warning types pass-through"
);
let expect_warnings = BTreeMap::from([
(
id("excessive_precision"),
vec![
"$.charging_periods[0].dimensions[0].volume",
"$.charging_periods[1].dimensions[0].volume",
"$.total_cost",
],
),
(id("periods_outside_start_end_date_time"), vec!["$"]),
]);
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_limit_zero_element_paths() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::ElemPathsPerId(0));
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(
elements_filtered, ELEM_TOTAL,
"All element paths are filtered"
);
assert_eq!(
warning_distinct_types_filtered, 0,
"Only element paths are filtered"
);
let expect_warnings = BTreeMap::from([
(id("excessive_precision"), vec![]),
(id("periods_outside_start_end_date_time"), vec![]),
]);
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_limit_ids_and_paths() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::All {
max_warning_types: 1,
max_elem_paths_per_warning_id: 3,
});
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(
elements_filtered, 5,
"The allowed warning type is within the path limit; the other type is filtered \
due to only one type being allowed, taking its 5 element paths with it"
);
assert_eq!(
warning_distinct_types_filtered, 1,
"Only one warning type is allowed so the second is filtered"
);
let expect_warnings = BTreeMap::from([(id("periods_outside_start_end_date_time"), vec!["$"])]);
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_limit_zero_ids_and_paths() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::All {
max_warning_types: 0,
max_elem_paths_per_warning_id: 3,
});
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(
warning_distinct_types_filtered, WARN_TYPES_TOTAL,
"All warning types should be filtered"
);
assert_eq!(
elements_filtered, ELEM_TOTAL,
"All elements are filtered as zero warning types are allowed"
);
let expect_warnings = BTreeMap::new();
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_limit_ids_and_zero_paths() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::All {
max_warning_types: 1,
max_elem_paths_per_warning_id: 0,
});
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(elements_filtered, ELEM_TOTAL);
assert_eq!(warning_distinct_types_filtered, 1);
let expect_warnings = BTreeMap::from([(id("periods_outside_start_end_date_time"), vec![])]);
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_limit_zero_ids_and_zero_paths() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.id_path_map(warning::Limit::All {
max_warning_types: 0,
max_elem_paths_per_warning_id: 0,
});
let warning::IdPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(elements_filtered, ELEM_TOTAL);
assert_eq!(
warning_distinct_types_filtered, WARN_TYPES_TOTAL,
"All warning types should be filtered"
);
let expect_warnings = BTreeMap::new();
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[test]
fn should_allow_all_msgs_path_maps() {
let warnings = parse_cdr(CDR_WITH_WARNINGS);
let outcome = warnings.msg_path_map(warning::Limit::None);
let warning::MsgPathMap {
warnings,
total_warnings,
total_elements,
elements_filtered,
warning_distinct_types_filtered,
} = outcome;
assert_eq!(total_warnings, WARN_TOTAL);
assert_eq!(total_elements, ELEM_TOTAL);
assert_eq!(elements_filtered, 0, "No elements are filtered");
assert_eq!(
warning_distinct_types_filtered, 0,
"No warnings are filtered"
);
let expect_warnings = BTreeMap::from([
(
"excessive_precision".to_owned(),
vec![
"$.charging_periods[0].dimensions[0].volume",
"$.charging_periods[1].dimensions[0].volume",
"$.total_cost",
"$.total_energy",
"$.total_time",
],
),
("periods_outside_start_end_date_time".to_owned(), vec!["$"]),
]);
assert_eq!(warnings, expect_warnings, "{warnings:#?}");
}
#[track_caller]
fn parse_cdr(json: &str) -> warning::Set<price::Warning> {
let (cdr, mut warnings) =
cdr::from_json(crate::json::parse_object(json).unwrap(), Version::V211).into_parts();
warnings.remove_missing_fields();
test::assert_schema_warnings(
&warnings,
&[
(
"$.charging_periods[2].dimensions[0].type",
&["field_invalid_value(CONGESTION_TIME)"],
),
(
"$.charging_periods[3].dimensions[0].type",
&["field_invalid_value(CONGESTION_TIME)"],
),
(
"$.charging_periods[4].dimensions[0].type",
&["field_invalid_value(CONGESTION_TIME)"],
),
(
"$.charging_periods[5].dimensions[0].type",
&["field_invalid_value(CONGESTION_TIME)"],
),
],
);
let (_cdr, warnings) = cdr.to_v221().unwrap().into_parts();
warnings
}
const fn id(s: &'static str) -> warning::Id {
warning::Id::from_static(s)
}