ocpi-tariffs 0.53.0

OCPI tariff calculations
Documentation
//! Tests for the fixing loop: that a fix which only becomes possible once another has been made
//! is reached by a later pass, that a tariff with nothing to fix is left alone, and that the pass
//! limit hands back what the passes that did run produced.

#![allow(
    clippy::panic,
    clippy::unwrap_used,
    reason = "test code with known-structure parsed data"
)]

use crate::{json, tariff, test, Version};

use super::{Fixed, Outcome, UnexpectedFields, MAX_PASSES};

/// A tariff with one restriction, whose `day_of_week` is `{DAYS}`.
const TARIFF: &str = r#"{
  "country_code": "NL",
  "party_id": "TDR",
  "id": "TEST",
  "currency": "EUR",
  "elements": [
    {
      "restrictions": {
        "day_of_week": {DAYS}
      },
      "price_components": [
        {
          "type": "ENERGY",
          "price": 0.25,
          "step_size": 1
        }
      ]
    }
  ],
  "last_updated": "2018-12-17T11:36:01Z"
}"#;

/// A `day_of_week` list that is both written in the wrong case and out of order.
///
/// The casing is the schema's finding, one warning per day; the order is the linter's, one
/// warning on the array.
const MISCASED_AND_UNSORTED: &str = r#"["tuesday", "monday"]"#;

#[test]
fn a_fix_that_another_fix_enables_is_reached_by_a_later_pass() {
    test::setup();

    let fixed = fix(&TARIFF.replace("{DAYS}", MISCASED_AND_UNSORTED), MAX_PASSES);

    assert_eq!(fixed.outcome, Outcome::AllFixesApplied);
    assert_eq!(fixed.passes, 2);
    assert_eq!(fixed.edits, 3);
    assert!(
        fixed.source.contains(r#"["MONDAY", "TUESDAY"]"#),
        "{}",
        fixed.source
    );
}

#[test]
fn a_tariff_with_nothing_to_fix_is_returned_unchanged() {
    test::setup();

    let source = TARIFF.replace("{DAYS}", r#"["MONDAY", "TUESDAY"]"#);
    let fixed = fix(&source, MAX_PASSES);

    assert_eq!(fixed.outcome, Outcome::AllFixesApplied);
    assert_eq!(fixed.passes, 0);
    assert_eq!(fixed.edits, 0);
    assert_eq!(fixed.source, source);
}

#[test]
fn the_pass_limit_hands_back_the_last_pass_that_ran() {
    test::setup();

    let fixed = fix(&TARIFF.replace("{DAYS}", MISCASED_AND_UNSORTED), 1);

    assert_eq!(fixed.outcome, Outcome::FixLimit);
    assert_eq!(fixed.passes, 1);
    assert_eq!(fixed.edits, 2);
    assert!(
        fixed.source.contains(r#"["TUESDAY", "MONDAY"]"#),
        "{}",
        fixed.source
    );
}

#[test]
fn one_pass_fixes_the_schema_warnings_and_the_lint_warnings_together() {
    test::setup();

    let source = TARIFF
        .replace("{DAYS}", r#"["TUESDAY", "MONDAY", "TUESDAY"]"#)
        .replace(
            r#""currency": "EUR","#,
            "\"currency\": \"EUR\",\n  \"tariff_alt_url\": null,",
        );

    let fixed = fix(&source, MAX_PASSES);

    assert_eq!(fixed.outcome, Outcome::AllFixesApplied);
    assert_eq!(fixed.passes, 1);
    assert!(!fixed.source.contains("tariff_alt_url"), "{}", fixed.source);
    assert!(
        fixed.source.contains(r#"["MONDAY", "TUESDAY"]"#),
        "{}",
        fixed.source
    );
}

#[test]
fn an_unexpected_field_is_removed_when_that_is_asked_for() {
    test::setup();

    let fixed = fix_with(
        &unexpected_field_tariff(),
        UnexpectedFields::Remove,
        MAX_PASSES,
    );

    assert_eq!(fixed.outcome, Outcome::AllFixesApplied);
    assert_eq!(fixed.edits, 1);
    assert!(!fixed.source.contains("surprise"), "{}", fixed.source);
}

#[test]
fn an_unexpected_field_is_left_in_place_when_that_is_asked_for() {
    test::setup();

    let source = unexpected_field_tariff();
    let fixed = fix_with(&source, UnexpectedFields::Keep, MAX_PASSES);

    assert_eq!(fixed.outcome, Outcome::AllFixesApplied);
    assert_eq!(fixed.edits, 0);
    assert_eq!(fixed.source, source);
}

/// A tariff whose only fault is one field the OCPI spec does not define.
fn unexpected_field_tariff() -> String {
    TARIFF
        .replace("{DAYS}", r#"["MONDAY", "TUESDAY"]"#)
        .replace(
            r#""currency": "EUR","#,
            "\"currency\": \"EUR\",\n  \"surprise\": \"not in the spec\",",
        )
}

/// Fix the tariff in `json` within `max_passes`, removing the fields no OCPI version defines.
#[track_caller]
fn fix(json: &str, max_passes: usize) -> Fixed {
    fix_with(json, UnexpectedFields::Remove, max_passes)
}

/// Fix the tariff in `json` within `max_passes`, handling its unexpected fields as asked.
#[track_caller]
fn fix_with(json: &str, unexpected: UnexpectedFields, max_passes: usize) -> Fixed {
    let doc = json::parse_object(json).expect("the tariff parses");
    let (tariff, _warnings) = tariff::from_json(doc, Version::V221).into_parts();

    super::fix_until_no_edits(tariff.as_json_str(), Version::V221, unexpected, max_passes)
        .expect("the fixes apply")
}