#![allow(clippy::indexing_slicing, reason = "tests are allowed to panic")]
use assert_matches::assert_matches;
use crate::{
cdr, json, test, timezone::Warning, warning::test::VerdictTestExt, ObjectType, Verdict, Version,
};
use super::{find_or_infer, Source};
#[test]
fn should_find_timezone() {
const JSON: &str = r#"{
"country_code": "NL",
"cdr_location": {
"time_zone": "Europe/Amsterdam"
}
}"#;
test::setup();
let timezone = parse_expect_v221_and_time_zone_field(JSON)
.unwrap()
.unwrap();
assert_matches!(timezone, Source::Found(chrono_tz::Tz::Europe__Amsterdam));
}
#[test]
fn should_find_timezone_but_warn_about_use_of_location_for_v221_cdr() {
const JSON: &str = r#"{
"country_code": "NL",
"location": {
"time_zone": "Europe/Amsterdam"
}
}"#;
test::setup();
let cdr::ParseReport {
cdr,
unexpected_fields,
} = cdr::parse_with_version(JSON, Version::V221).unwrap();
assert_unexpected_fields(&unexpected_fields, &["$.location", "$.location.time_zone"]);
let (timezone_source, warnings) = find_or_infer(&cdr).unwrap().into_parts();
let warnings = warnings.into_path_as_str_map();
let warnings = &warnings["$"];
assert_matches!(
timezone_source,
Source::Found(chrono_tz::Tz::Europe__Amsterdam)
);
assert_matches!(warnings.as_slice(), [Warning::V221CdrHasLocationField]);
}
#[test]
fn should_find_timezone_without_cdr_country() {
const JSON: &str = r#"{
"cdr_location": {
"time_zone": "Europe/Amsterdam"
}
}"#;
test::setup();
let timezone = parse_expect_v221_and_time_zone_field(JSON)
.unwrap()
.unwrap();
assert_matches!(timezone, Source::Found(chrono_tz::Tz::Europe__Amsterdam));
}
#[test]
fn should_infer_timezone_and_warn_about_invalid_type() {
const JSON: &str = r#"{
"country_code": "NL",
"cdr_location": {
"time_zone": null,
"country": "BEL"
}
}"#;
test::setup();
let (timezone, warnings) = parse_expect_v221_and_time_zone_field(JSON)
.unwrap()
.into_parts();
let warnings = warnings.into_path_as_str_map();
let warnings = &warnings["$.cdr_location.time_zone"];
assert_matches!(timezone, Source::Inferred(chrono_tz::Tz::Europe__Brussels));
assert_matches!(warnings.as_slice(), [Warning::InvalidTimezoneType]);
}
#[test]
fn should_find_timezone_and_warn_about_invalid_type() {
const JSON: &str = r#"{
"country_code": "NL",
"cdr_location": {
"time_zone": "Europe/Hamsterdam",
"country": "BEL"
}
}"#;
test::setup();
let (timezone, warnings) = parse_expect_v221_and_time_zone_field(JSON)
.unwrap()
.into_parts();
assert_matches!(timezone, Source::Inferred(chrono_tz::Tz::Europe__Brussels));
let warnings = warnings.into_path_as_str_map();
let warnings = &warnings["$.cdr_location.time_zone"];
assert_matches!(warnings.as_slice(), [Warning::InvalidTimezone]);
}
#[test]
fn should_find_timezone_and_warn_about_escape_codes_and_invalid_type() {
const JSON: &str = r#"{
"country_code": "NL",
"cdr_location": {
"time_zone": "Europe\/Hamsterdam",
"country": "BEL"
}
}"#;
test::setup();
let (timezone, warnings) = parse_expect_v221_and_time_zone_field(JSON)
.unwrap()
.into_parts();
assert_matches!(timezone, Source::Inferred(chrono_tz::Tz::Europe__Brussels));
let groups = warnings.into_path_as_str_map();
let warnings = &groups["$.cdr_location.time_zone"];
assert_matches!(
warnings.as_slice(),
[Warning::ContainsEscapeCodes, Warning::InvalidTimezone]
);
}
#[test]
fn should_find_timezone_and_warn_about_escape_codes() {
const JSON: &str = r#"{
"country_code": "NL",
"cdr_location": {
"time_zone": "Europe\/Amsterdam",
"country": "BEL"
}
}"#;
test::setup();
let (timezone, warnings) = parse_expect_v221_and_time_zone_field(JSON)
.unwrap()
.into_parts();
assert_matches!(timezone, Source::Found(chrono_tz::Tz::Europe__Amsterdam));
let groups = warnings.into_path_as_str_map();
let warnings = &groups["$.cdr_location.time_zone"];
assert_matches!(warnings.as_slice(), [Warning::ContainsEscapeCodes]);
}
#[test]
fn should_infer_timezone_from_location_country() {
const JSON: &str = r#"{
"country_code": "NL",
"cdr_location": {
"country": "BEL"
}
}"#;
test::setup();
let timezone = parse_expect_v221(JSON).unwrap().unwrap();
assert_matches!(timezone, Source::Inferred(chrono_tz::Tz::Europe__Brussels));
}
#[test]
fn should_find_timezone_but_report_alpha2_location_country_code() {
const JSON: &str = r#"{
"country_code": "NL",
"cdr_location": {
"country": "BE"
}
}"#;
test::setup();
let (timezone, warnings) = parse_expect_v221(JSON).unwrap().into_parts();
assert_matches!(timezone, Source::Inferred(chrono_tz::Tz::Europe__Brussels));
let groups = warnings.into_path_as_str_map();
let warnings = &groups["$.cdr_location.country"];
assert_matches!(
warnings.as_slice(),
[Warning::LocationCountryShouldBeAlpha3,]
);
}
#[test]
fn should_not_find_timezone_due_to_no_location() {
const JSON: &str = r#"{ "country_code": "BE" }"#;
test::setup();
let error = parse_expect_v221(JSON).unwrap_only_error();
assert_eq!(error.element().path().as_str(), "$");
assert_matches!(error.warning(), Warning::NoLocation);
}
#[test]
fn should_not_find_timezone_due_to_no_country() {
const JSON: &str = r#"{
"country_code": "BELGIUM",
"cdr_location": {}
}"#;
test::setup();
let error = parse_expect_v221(JSON).unwrap_only_error();
assert_eq!(error.element().path().as_str(), "$.cdr_location");
assert_matches!(error.warning(), Warning::NoLocationCountry);
}
#[test]
fn should_not_find_timezone_due_to_country_having_many_timezones() {
const JSON: &str = r#"{
"country_code": "BE",
"cdr_location": {
"country": "CHN"
}
}"#;
test::setup();
let error = parse_expect_v221(JSON).unwrap_only_error();
assert_eq!(error.element().path().as_str(), "$.cdr_location.country");
assert_matches!(error.warning(), Warning::CantInferTimezoneFromCountry("CN"));
}
#[test]
fn should_fail_due_to_json_not_being_object() {
const JSON: &str = r#"["not_a_cdr"]"#;
test::setup();
let error = parse_expect_v221(JSON).unwrap_only_error();
assert_eq!(error.element().path().as_str(), "$");
assert_matches!(error.warning(), Warning::ShouldBeAnObject);
}
#[track_caller]
#[expect(
clippy::unwrap_in_result,
reason = "A test can unwrap whereever it wants"
)]
fn parse_expect_v221(json: &str) -> Verdict<Source, Warning> {
let cdr::ParseReport {
cdr,
unexpected_fields,
} = cdr::parse_with_version(json, Version::V221).unwrap();
test::assert_no_unexpected_fields(ObjectType::Cdr, &unexpected_fields);
find_or_infer(&cdr)
}
#[track_caller]
#[expect(
clippy::unwrap_in_result,
reason = "A test can unwrap whereever it wants"
)]
fn parse_expect_v221_and_time_zone_field(json: &str) -> Verdict<Source, Warning> {
let cdr::ParseReport {
cdr,
unexpected_fields,
} = cdr::parse_with_version(json, Version::V221).unwrap();
assert_unexpected_fields(&unexpected_fields, &["$.cdr_location.time_zone"]);
find_or_infer(&cdr)
}
#[track_caller]
fn assert_unexpected_fields(
unexpected_fields: &json::UnexpectedFields<'_>,
expected: &[&'static str],
) {
if unexpected_fields.len() != expected.len() {
let unexpected_fields = unexpected_fields
.into_iter()
.map(|path| path.to_string())
.collect::<Vec<_>>();
panic!(
"The unexpected fields and expected fields lists have different lengths.\n\nUnexpected fields found:\n{}",
unexpected_fields.join(",\n")
);
}
let unmatched_paths = unexpected_fields
.into_iter()
.zip(expected.iter())
.filter(|(a, b)| a != *b)
.collect::<Vec<_>>();
if !unmatched_paths.is_empty() {
let unmatched_paths = unmatched_paths
.into_iter()
.map(|(a, b)| format!("{a} != {b}"))
.collect::<Vec<_>>();
panic!(
"The unexpected fields don't match the expected fields.\n\nUnexpected fields found:\n{}",
unmatched_paths.join(",\n")
);
}
}