use super::build_tariff;
use crate::{
json,
schema::{Integrity, Number, Warning},
warning,
};
use std::assert_matches;
fn any_warning(warnings: &warning::Set<Warning>, pred: impl Fn(&Warning) -> bool) -> bool {
warnings
.iter()
.any(|group| group.to_parts().1.into_iter().any(&pred))
}
const VALID: &str = r#"{
"currency": "EUR",
"elements": [
{"price_components": [{"price": 0.25, "step_size": 1, "type": "ENERGY"}]}
],
"id": "T1",
"last_updated": "2024-01-01T00:00:00Z"
}"#;
#[test]
fn valid_tariff_builds_without_warnings() {
let doc = json::parse(VALID.into()).unwrap();
let (tariff, warnings) = build_tariff(&doc).into_parts();
assert!(warnings.is_empty(), "{:?}", warnings.path_id_map());
let Integrity::Ok(currency) = &tariff.currency else {
panic!("currency should be built: {:?}", tariff.currency);
};
assert_eq!(
currency.element().to_raw_str().unwrap().as_unescaped_str(),
"EUR"
);
let Integrity::Ok(elements) = &tariff.elements else {
panic!("elements should be built: {:?}", tariff.elements);
};
assert_eq!(elements.len(), 1);
let Integrity::Ok(element) = &elements[0] else {
panic!("the element should be built");
};
let Integrity::Ok(components) = &element.price_components else {
panic!("price_components should be built");
};
assert_eq!(components.len(), 1);
let Integrity::Ok(component) = &components[0] else {
panic!("the price component should be built");
};
let Integrity::Ok(dimension_type) = &component.dimension_type else {
panic!("the dimension type should be built");
};
assert_eq!(dimension_type.canonical(), "ENERGY");
assert_matches!(component.price, Integrity::Ok(_));
}
#[test]
fn id_over_max_length_is_flagged() {
let src = VALID.replace(r#""id": "T1""#, &format!(r#""id": "{}""#, "x".repeat(37)));
let doc = json::parse(src.as_str().into()).unwrap();
let (tariff, warnings) = build_tariff(&doc).into_parts();
assert_matches!(tariff.id, Integrity::Ok(_));
assert!(any_warning(&warnings, |w| matches!(
w,
Warning::StringTooLong { max: 36, len: 37 }
)));
}
#[test]
fn empty_elements_array_is_flagged() {
let src = VALID.replace(
r#"[
{"price_components": [{"price": 0.25, "step_size": 1, "type": "ENERGY"}]}
]"#,
"[]",
);
let doc = json::parse(src.as_str().into()).unwrap();
let (tariff, warnings) = build_tariff(&doc).into_parts();
let Integrity::Ok(elements) = &tariff.elements else {
panic!(
"an empty elements array still builds: {:?}",
tariff.elements
);
};
assert!(elements.is_empty());
assert!(any_warning(&warnings, |w| matches!(
w,
Warning::Cardinality { .. }
)));
}
#[test]
fn string_encoded_number_is_accepted_silently() {
let src = VALID.replace(r#""price": 0.25"#, r#""price": "0.25""#);
let doc = json::parse(src.as_str().into()).unwrap();
let (tariff, warnings) = build_tariff(&doc).into_parts();
assert!(warnings.is_empty(), "{:?}", warnings.path_id_map());
let Integrity::Ok(elements) = &tariff.elements else {
panic!("elements should be built: {:?}", tariff.elements);
};
let Integrity::Ok(element) = &elements[0] else {
panic!("the element should be built");
};
let Integrity::Ok(components) = &element.price_components else {
panic!("price_components should be built");
};
let Integrity::Ok(component) = &components[0] else {
panic!("the price component should be built");
};
assert_matches!(component.price, Integrity::Ok(Number::StringEncoded(_)));
}
#[test]
fn unknown_dimension_type_is_err_not_dropped() {
let src = VALID.replace(r#""type": "ENERGY""#, r#""type": "FOO""#);
let doc = json::parse(src.as_str().into()).unwrap();
let (tariff, warnings) = build_tariff(&doc).into_parts();
let Integrity::Ok(elements) = &tariff.elements else {
panic!("elements should be built");
};
let Integrity::Ok(element) = &elements[0] else {
panic!("the element should be built");
};
let Integrity::Ok(components) = &element.price_components else {
panic!("price_components should be built");
};
let Integrity::Ok(component) = &components[0] else {
panic!("the price component should be built");
};
assert_matches!(component.dimension_type, Integrity::Err);
assert!(any_warning(&warnings, |w| matches!(
w,
Warning::FieldInvalidValue { .. }
)));
}
#[test]
fn missing_required_field_is_missing_not_fatal() {
let src = VALID.replace(r#""currency": "EUR","#, "");
let doc = json::parse(src.as_str().into()).unwrap();
let (tariff, warnings) = build_tariff(&doc).into_parts();
assert_matches!(tariff.currency, Integrity::Missing);
assert!(any_warning(&warnings, |w| matches!(
w,
Warning::MissingField { name: "currency" }
)));
}
#[test]
fn building_is_total_on_degenerate_input() {
for src in ["{}", "[]", "\"not an object\""] {
let doc = json::parse(src.into()).unwrap();
let (tariff, _warnings) = build_tariff(&doc).into_parts();
assert_matches!(tariff.currency, Integrity::Missing);
assert_matches!(tariff.elements, Integrity::Missing);
}
}