use assert_matches::assert_matches;
use chrono::TimeDelta;
use crate::{
assert_approx_eq,
generate::{self, PartialCdr},
tariff,
warning::test::VerdictTestExt as _,
Kwh, Money, Price,
};
use super::test;
const DATE: &str = "2025-11-10";
const TARIFF_JSON: &str = r#"{
"country_code": "DE",
"party_id": "ALL",
"id": "1",
"currency": "EUR",
"type": "REGULAR",
"elements": [
{
"price_components": [{
"type": "ENERGY",
"price": 0.50,
"vat": 20.0,
"step_size": 1
}]
}
],
"last_updated": "2018-12-05T12:01:09Z"
}
"#;
fn generate_config() -> generate::Config {
generate::Config {
timezone: chrono_tz::Europe::Amsterdam,
start_date_time: test::datetime_utc(DATE, "15:02:12"),
end_date_time: test::datetime_utc(DATE, "15:12:12"),
max_power_supply_kw: 12.into(),
requested_kwh: 80.into(),
max_current_supply_amp: 2.into(),
}
}
#[track_caller]
fn generate(tariff_json: &str) -> generate::Caveat<generate::Report> {
let tariff = tariff::parse(tariff_json).unwrap().unwrap_certain();
generate::cdr_from_tariff(&tariff, &generate_config()).unwrap()
}
#[test]
fn should_warn_duration_below_min() {
let tariff = tariff::parse(TARIFF_JSON).unwrap().unwrap_certain();
let config = generate::Config {
timezone: chrono_tz::Europe::Amsterdam,
start_date_time: test::datetime_utc(DATE, "15:02:12"),
end_date_time: test::datetime_utc(DATE, "15:03:12"),
max_power_supply_kw: 12.into(),
requested_kwh: 80.into(),
max_current_supply_amp: 2.into(),
};
let failure = generate::cdr_from_tariff(&tariff, &config).unwrap_only_error();
assert_matches!(
failure.into_warning(),
generate::Warning::DurationBelowMinimum
);
}
#[test]
fn should_warn_end_before_start() {
let tariff = tariff::parse(TARIFF_JSON).unwrap().unwrap_certain();
let config = generate::Config {
timezone: chrono_tz::Europe::Amsterdam,
start_date_time: test::datetime_utc(DATE, "15:12:12"),
end_date_time: test::datetime_utc(DATE, "15:02:12"),
max_power_supply_kw: 12.into(),
requested_kwh: 80.into(),
max_current_supply_amp: 2.into(),
};
let failure = generate::cdr_from_tariff(&tariff, &config).unwrap_only_error();
assert_matches!(
failure.into_warning(),
generate::Warning::StartDateTimeIsAfterEndDateTime
);
}
#[test]
fn should_generate_energy_for_ten_minutes() {
let report = generate(TARIFF_JSON);
let (report, warnings) = report.into_parts();
assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
let PartialCdr {
party_id: _,
start_date_time: _,
end_date_time: _,
currency_code: _,
total_energy,
total_charging_duration,
total_parking_duration,
total_cost,
total_energy_cost,
total_fixed_cost,
total_parking_duration_cost,
total_charging_duration_cost,
charging_periods: _,
} = report.partial_cdr;
assert_approx_eq!(
total_cost,
Some(Price {
excl_vat: Money::from(1),
incl_vat: Some(Money::from(1.2))
})
);
assert_eq!(
total_charging_duration,
Some(TimeDelta::minutes(10)),
"The charging session is 10 min and is stopped before the battery is fully charged."
);
assert_eq!(
total_parking_duration, None,
"There is no parking time since the battery never fully charged."
);
assert_approx_eq!(total_energy, Some(Kwh::from(2)));
assert_approx_eq!(
total_energy_cost,
Some(Price {
excl_vat: Money::from(1),
incl_vat: Some(Money::from(1.2))
}),
"The cost per KwH is 50 cents and the VAT is 20%."
);
assert_eq!(total_fixed_cost, None, "There are no fixed costs.");
assert_eq!(
total_parking_duration_cost, None,
"There is no parking cost as there is no parking time."
);
assert_eq!(
total_charging_duration_cost, None,
"There are no time costs defined in the tariff."
);
}