#![allow(clippy::as_conversions, reason = "tests are allowed to panic")]
#![allow(clippy::panic, reason = "tests are allowed panic")]
use chrono::Utc;
use chrono_tz::Tz;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use crate::{
assert_approx_eq, cdr,
price::{self, test::UnwrapReport},
tariff, test, Kwh, Version,
};
use super::{Consumed, Period, TariffSource};
#[test]
fn should_price_periods_from_time_and_parking_time_cdr_and_tariff() {
const VERSION: Version = Version::V211;
const CDR_JSON: &str = include_str!(
"../../test_data/v211/real_world/time_and_parking_time_separate_tariff/cdr.json"
);
const TARIFF_JSON: &str = include_str!(
"../../test_data/v211/real_world/time_and_parking_time_separate_tariff/tariff.json"
);
const PERIOD_DURATION: chrono::TimeDelta = chrono::TimeDelta::minutes(15);
fn charging(start_date_time: &str, energy: Vec<Decimal>) -> Vec<Period> {
let start: chrono::DateTime<Utc> = start_date_time.parse().unwrap();
energy
.into_iter()
.enumerate()
.map(|(i, kwh)| {
let i = i32::try_from(i).unwrap();
let start_date_time = start + (PERIOD_DURATION * i);
Period {
start_date_time,
consumed: Consumed {
duration_charging: Some(PERIOD_DURATION),
energy: Some(kwh.into()),
..Default::default()
},
}
})
.collect()
}
fn parking(start_date_time: &str, period_count: usize) -> Vec<Period> {
let period_energy = Kwh::from(0);
let start: chrono::DateTime<Utc> = start_date_time.parse().unwrap();
let period_count = i32::try_from(period_count).unwrap();
let mut periods: Vec<Period> = (0..period_count - 1)
.map(|i| {
let start_date_time = start + (PERIOD_DURATION * i);
Period {
start_date_time,
consumed: Consumed {
duration_parking: Some(PERIOD_DURATION),
energy: Some(period_energy),
..Default::default()
},
}
})
.collect();
let start_date_time = start + (PERIOD_DURATION * (period_count - 1));
periods.push(Period {
start_date_time,
consumed: Consumed {
duration_parking: Some(chrono::TimeDelta::seconds(644)),
energy: Some(period_energy),
..Default::default()
},
});
periods
}
test::setup();
let report = cdr::parse_with_version(CDR_JSON, VERSION).unwrap();
let cdr::ParseReport {
cdr,
unexpected_fields,
} = report;
assert!(unexpected_fields.is_empty());
let tariff::ParseReport {
tariff,
unexpected_fields,
} = tariff::parse_with_version(TARIFF_JSON, VERSION).unwrap();
assert!(unexpected_fields.is_empty());
let report = cdr::price(
&cdr,
TariffSource::Override(vec![tariff.clone()]),
Tz::Europe__Amsterdam,
)
.unwrap_report(cdr.as_json_str());
let (report, warnings) = report.into_parts();
assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
let price::Report {
periods,
tariff_used: _,
tariff_reports: _,
timezone: _,
billed_energy,
billed_parking_time,
billed_charging_time,
total_charging_time,
total_energy,
total_parking_time,
total_time: _,
total_cost,
total_energy_cost,
total_fixed_cost,
total_parking_cost,
total_reservation_cost: _,
total_time_cost,
} = report;
let mut cdr_periods = charging(
"2025-04-09T16:12:54.000Z",
vec![
dec!(2.75),
dec!(2.77),
dec!(1.88),
dec!(2.1),
dec!(2.09),
dec!(2.11),
dec!(2.09),
dec!(2.09),
dec!(2.09),
dec!(2.09),
dec!(2.09),
dec!(2.09),
dec!(2.09),
dec!(2.11),
dec!(2.13),
dec!(2.09),
dec!(2.11),
dec!(2.12),
dec!(2.13),
dec!(2.1),
dec!(2.0),
dec!(0.69),
dec!(0.11),
],
);
let mut periods_parking = parking("2025-04-09T21:57:55.000Z", 47);
cdr_periods.append(&mut periods_parking);
cdr_periods.sort_by_key(|p| p.start_date_time);
assert_eq!(
cdr_periods.len(),
periods.len(),
"The amount of `price::Report` periods should equal the periods given to the `price::periods` fn"
);
assert_eq!(
periods.len(),
70,
"The `time_and_parking/cdr.json` has 70 `charging_periods`"
);
assert!(periods
.iter()
.map(|p| p.start_date_time)
.collect::<Vec<_>>()
.is_sorted());
let (tariff, warnings) = super::tariff::parse(&tariff).unwrap().into_parts();
assert!(warnings.is_empty());
let cdr_periods_count = cdr_periods.len();
let periods_report = price::periods(
"2025-04-10T09:38:38.000Z".parse().unwrap(),
chrono_tz::Europe::Amsterdam,
&tariff,
cdr_periods,
)
.unwrap()
.unwrap();
let price::PeriodsReport {
billable,
periods,
totals,
total_costs,
} = periods_report;
assert_eq!(
cdr_periods_count,
periods.len(),
"The amount of `price::Report` periods should equal the periods given to the `price::periods` fn"
);
assert_eq!(
periods.len(),
70,
"The `time_and_parking/cdr.json` has 70 `charging_periods`"
);
assert_approx_eq!(billable.charging_time, billed_charging_time);
assert_approx_eq!(billable.energy, billed_energy);
assert_approx_eq!(billable.parking_time, billed_parking_time,);
assert_approx_eq!(totals.duration_charging, total_charging_time);
assert_approx_eq!(totals.energy, total_energy.calculated);
assert_approx_eq!(totals.duration_parking, total_parking_time.calculated);
assert_approx_eq!(total_costs.duration_charging, total_time_cost.calculated,);
assert_approx_eq!(total_costs.energy, total_energy_cost.calculated,);
assert_approx_eq!(total_costs.fixed, total_fixed_cost.calculated);
assert_approx_eq!(total_costs.duration_parking, total_parking_cost.calculated);
assert_approx_eq!(total_costs.total(), total_cost.calculated);
}