ocpi-tariffs 0.53.0

OCPI tariff calculations
Documentation
use crate::{
    json, schema, test,
    test::Expectation,
    warning::{
        self,
        test::{assert_warnings, build_warnings, expect},
    },
};

/// A tariff whose `currency` and whose every `step_round` draw a warning.
const TARIFF: &str = r#"{
    "party_id": "ENE",
    "currency": "EUR",
    "elements": [
        { "price_components": [ { "step_round": 1 } ] },
        { "price_components": [ { "step_round": 1 } ] }
    ]
}"#;

/// Where the expectations would have been written; it only appears in panic messages.
const EXPECTATION: &str = "`warnings` in the `output_test__tariff.json` file";

/// An entry with no `*` component names one element, so precise expectations keep working.
#[test]
fn exact_paths_match_their_own_element() {
    test::setup();

    let doc = json::parse_object(TARIFF).unwrap();
    let warnings = build_warnings(&doc);

    let expect = expect(vec![
        ("$.currency", vec!["null_field"]),
        (
            "$.elements[0].price_components[0].step_round",
            vec!["unexpected_field"],
        ),
        (
            "$.elements[1].price_components[0].step_round",
            vec!["unexpected_field"],
        ),
    ]);

    assert_warnings(EXPECTATION, &warnings, expect);
}

/// One wildcard entry stands in for the same warning on every entry of an array. This is what
/// keeps a repetitive expectation from being written out once per array index.
#[test]
fn a_wildcard_entry_covers_every_element_it_matches() {
    test::setup();

    let doc = json::parse_object(TARIFF).unwrap();
    let warnings = build_warnings(&doc);

    let expect = expect(vec![
        ("$.currency", vec!["null_field"]),
        (
            "$.elements.*.price_components[0].step_round",
            vec!["unexpected_field"],
        ),
    ]);

    assert_warnings(EXPECTATION, &warnings, expect);
}

/// An element that has warnings but that no entry matches is a warning nobody has signed off on.
#[test]
#[should_panic(expected = "Elements with warnings that no entry of `warnings` in the")]
fn an_element_matched_by_no_entry_fails() {
    test::setup();

    let doc = json::parse_object(TARIFF).unwrap();
    let warnings = build_warnings(&doc);

    let expect = expect(vec![(
        "$.elements.*.price_components[0].step_round",
        vec!["unexpected_field"],
    )]);

    assert_warnings(EXPECTATION, &warnings, expect);
}

/// An entry that matches nothing is a stale expectation left behind after the warning it pinned
/// down stopped being raised.
#[test]
#[should_panic(expected = "that match no element with warnings")]
fn an_entry_matching_no_element_fails() {
    test::setup();

    let doc = json::parse_object(TARIFF).unwrap();
    let warnings = build_warnings(&doc);

    let expect = expect(vec![
        ("$.currency", vec!["null_field"]),
        ("$.party_id", vec!["null_field"]),
        (
            "$.elements.*.price_components[0].step_round",
            vec!["unexpected_field"],
        ),
    ]);

    assert_warnings(EXPECTATION, &warnings, expect);
}

/// The ids listed by the matching entry have to be the ids actually reported, not a subset.
#[test]
#[should_panic(expected = "Elements whose warnings are not the warnings listed by `warnings`")]
fn an_entry_listing_the_wrong_ids_fails() {
    test::setup();

    let doc = json::parse_object(TARIFF).unwrap();
    let warnings = build_warnings(&doc);

    let expect = expect(vec![
        ("$.currency", vec!["unexpected_field"]),
        (
            "$.elements.*.price_components[0].step_round",
            vec!["unexpected_field"],
        ),
    ]);

    assert_warnings(EXPECTATION, &warnings, expect);
}

/// A wildcard entry and an exact entry that both match an element are reported rather than
/// resolved: which of the two id lists applied would depend on the order the entries sort in.
#[test]
#[should_panic(expected = "Elements matched by more than one entry of `warnings` in the")]
fn an_element_matched_by_two_entries_fails() {
    test::setup();

    let doc = json::parse_object(TARIFF).unwrap();
    let warnings = build_warnings(&doc);

    let expect = expect(vec![
        ("$.currency", vec!["null_field"]),
        (
            "$.elements.*.price_components[0].step_round",
            vec!["unexpected_field"],
        ),
        (
            "$.elements[0].price_components[0].step_round",
            vec!["unexpected_field"],
        ),
    ]);

    assert_warnings(EXPECTATION, &warnings, expect);
}

/// An expect file with no `warnings` field asserts that there are no warnings at all.
#[test]
#[should_panic(expected = "There is no `warnings` in the")]
fn an_absent_expectation_fails_when_there_are_warnings() {
    test::setup();

    let doc = json::parse_object(TARIFF).unwrap();
    let warnings = build_warnings(&doc);

    assert_warnings(EXPECTATION, &warnings, Expectation::Absent);
}

/// An absent expectation and an empty set agree, so nothing has to be written down for the
/// tariffs that produce no warnings at all.
#[test]
fn an_absent_expectation_passes_when_there_are_no_warnings() {
    test::setup();

    let warnings = warning::Set::<schema::Warning>::new();

    assert_warnings(EXPECTATION, &warnings, Expectation::Absent);
}