ocpi-tariffs 0.52.0

OCPI tariff calculations
Documentation
//! Tests for lowering a `schema::Str` into a `DateTime<Utc>` via `FromSchema`.
//!
//! The schema walk proves the value is a string but never checks its format, so the
//! RFC 3339 parsing (and OCPI's tolerance of a missing timezone offset) is still the
//! lowering's job. Each test drives a real `v2.2.1` tariff through `build_tariff` and
//! lowers its `start_date_time` field.

#![allow(
    clippy::unwrap_in_result,
    reason = "unwraps are allowed anywhere in tests"
)]
#![allow(clippy::panic_in_result_fn, reason = "tests are allowed to panic")]

use chrono::{DateTime, TimeZone as _, Utc};

use super::Warning;
use crate::{
    json,
    schema::{self, Integrity},
    FromSchema as _, Verdict,
};

/// A minimal, valid `v2.2.1` tariff. `{START}` is replaced with the value under test.
const TARIFF: &str = r#"{
    "country_code": "NL",
    "party_id": "ENE",
    "currency": "EUR",
    "id": "ID",
    "last_updated": "2024-01-01T00:00:00Z",
    "start_date_time": {START},
    "elements": [
        {"price_components": [{"price": 0.25, "step_size": 1, "type": "ENERGY", "vat": 21}]}
    ]
}"#;

/// Build a tariff carrying `start_date_time` and lower its `Str` leaf.
#[track_caller]
fn parse_timestamp(start_date_time: &str) -> Verdict<DateTime<Utc>, Warning> {
    let src = TARIFF.replace("{START}", start_date_time);
    let doc = json::parse(src.as_str().into()).unwrap();
    let tariff = schema::v221::build_tariff(&doc).ignore_warnings();

    let Integrity::Ok(Some(start)) = &tariff.start_date_time else {
        panic!(
            "start_date_time should be built: {:?}",
            tariff.start_date_time
        );
    };

    DateTime::<Utc>::from_schema(start)
}

#[test]
fn should_parse_utc_datetime() {
    const START: &str = r#""2015-06-29T22:39:09Z""#;

    let (datetime, warnings) = parse_timestamp(START).unwrap().into_parts();
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
    assert_eq!(
        datetime,
        Utc.with_ymd_and_hms(2015, 6, 29, 22, 39, 9).unwrap()
    );
}

#[test]
fn should_parse_timezone_to_utc() {
    const START: &str = r#""2015-06-29T22:39:09+02:00""#;

    let (datetime, warnings) = parse_timestamp(START).unwrap().into_parts();
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
    assert_eq!(
        datetime,
        Utc.with_ymd_and_hms(2015, 6, 29, 20, 39, 9).unwrap()
    );
}

#[test]
fn should_parse_timezone_naive_to_utc() {
    // This is a mess, but unfortunately OCPI 2.1.1 and 2.2 specify that datetimes without any
    // timezone specification are also allowed
    const START: &str = r#""2015-06-29T22:39:09""#;

    let (datetime, warnings) = parse_timestamp(START).unwrap().into_parts();
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());

    assert_eq!(
        datetime,
        Utc.with_ymd_and_hms(2015, 6, 29, 22, 39, 9).unwrap()
    );
}