ocpi-tariffs 0.53.0

OCPI tariff calculations
Documentation
//! Tests for [`apply`]: splicing a set of [`Edit`]s into a document's source.

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

use crate::json;

use super::{apply, splice, Edit, Error, Json, Splice};

fn parsed(s: &str) -> json::Document<'_> {
    json::parse(s.into()).unwrap()
}

fn field_id(element: &json::Element<'_>, key: &str) -> json::ElemId {
    element.find_field(key).unwrap().element().id()
}

fn item_id(element: &json::Element<'_>, index: usize) -> json::ElemId {
    element.as_array().unwrap()[index].id()
}

/// The payloads here are whole JSON values rather than string content, since what is under test is
/// the splicing and not the writing, so they go in verbatim.
fn replace(elem: json::ElemId, json: &str) -> Edit {
    Edit::replace(elem, Json::raw(json))
}

#[test]
fn no_edits_leave_the_source_alone() {
    let source = r#"{"a": 1, "b": 2}"#;
    let doc = parsed(source);

    assert_eq!(apply(&doc, &[]), Ok(source.to_owned()));
}

#[test]
fn remove_a_field() {
    let source = r#"{"a": 1, "b": 2}"#;
    let doc = parsed(source);
    let edits = [Edit::remove(field_id(doc.root(), "a"))];

    assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2}"#.to_owned()));
}

#[test]
fn replace_a_value_and_keep_the_key() {
    let source = r#"{"min_duration": 1.5, "b": 2}"#;
    let doc = parsed(source);
    let edits = [replace(field_id(doc.root(), "min_duration"), "2")];

    assert_eq!(
        apply(&doc, &edits),
        Ok(r#"{"min_duration": 2, "b": 2}"#.to_owned())
    );
}

#[test]
fn replace_a_scalar_with_an_object() {
    let source = r#"{"a": 1, "b": 2}"#;
    let doc = parsed(source);
    let edits = [replace(field_id(doc.root(), "a"), r#"{"c": [3]}"#)];

    assert_eq!(
        apply(&doc, &edits),
        Ok(r#"{"a": {"c": [3]}, "b": 2}"#.to_owned())
    );
}

#[test]
fn replace_an_array_item() {
    let source = "[1, 2, 3]";
    let doc = parsed(source);
    let edits = [replace(item_id(doc.root(), 1), "20")];

    assert_eq!(apply(&doc, &edits), Ok("[1, 20, 3]".to_owned()));
}

/// Every span is resolved against the original source, so a replacement that is longer than
/// the text it replaces must not shift the edits that come after it.
#[test]
fn a_longer_replacement_does_not_shift_later_edits() {
    let source = r#"{"a": 1, "b": 2, "c": 3}"#;
    let doc = parsed(source);
    let edits = [
        replace(
            field_id(doc.root(), "a"),
            r#""a much longer value than before""#,
        ),
        Edit::remove(field_id(doc.root(), "c")),
    ];

    assert_eq!(
        apply(&doc, &edits),
        Ok(r#"{"a": "a much longer value than before", "b": 2}"#.to_owned())
    );
}

#[test]
fn remove_and_replace_different_elements() {
    let source = r#"{"a": 1, "b": 2, "c": 3}"#;
    let doc = parsed(source);
    let edits = [
        Edit::remove(field_id(doc.root(), "a")),
        replace(field_id(doc.root(), "c"), "30"),
    ];

    assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2, "c": 30}"#.to_owned()));
}

/// Two warnings on one element can each ask for it to go; it is removed once.
#[test]
fn removing_the_same_element_twice_removes_it_once() {
    let source = r#"{"a": 1, "b": 2}"#;
    let doc = parsed(source);
    let id = field_id(doc.root(), "a");
    let edits = [Edit::remove(id), Edit::remove(id)];

    assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2}"#.to_owned()));
}

#[test]
fn a_removal_absorbs_a_replacement_of_the_same_element() {
    let source = r#"{"a": 1, "b": 2}"#;
    let doc = parsed(source);
    let id = field_id(doc.root(), "a");
    let edits = [Edit::remove(id), replace(id, "99")];

    assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2}"#.to_owned()));
}

/// The plan's case: removing an element must swallow a pending fix inside it.
#[test]
fn a_removal_absorbs_a_replacement_nested_inside_it() {
    let source = r#"{"a": {"max_kwh": 1.5}, "b": 2}"#;
    let doc = parsed(source);
    let outer = doc.root().find_field("a").unwrap().element();
    let edits = [
        Edit::remove(outer.id()),
        replace(field_id(outer, "max_kwh"), "2"),
    ];

    assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2}"#.to_owned()));
}

#[test]
fn replacing_the_same_element_twice_is_a_duplicate() {
    let source = r#"{"a": 1}"#;
    let doc = parsed(source);
    let id = field_id(doc.root(), "a");
    let edits = [replace(id, "2"), replace(id, "3")];

    assert_eq!(apply(&doc, &edits), Err(Error::Duplicate(id)));
}

/// Two warnings can describe the same defect from different angles and ask for the same text,
/// which is one edit proposed twice rather than a disagreement about what to write.
#[test]
fn replacing_the_same_element_twice_with_the_same_text_is_not() {
    let source = r#"{"a": 1}"#;
    let doc = parsed(source);
    let id = field_id(doc.root(), "a");
    let edits = [replace(id, "2"), replace(id, "2")];

    assert_eq!(apply(&doc, &edits), Ok(r#"{"a": 2}"#.to_owned()));
}

/// A replacement's payload was written without knowledge of the inner edit, so applying it
/// would discard that edit silently.
#[test]
fn a_replacement_nested_in_a_replacement_is_a_conflict() {
    let source = r#"{"a": {"b": 1}}"#;
    let doc = parsed(source);
    let outer = doc.root().find_field("a").unwrap().element();
    let edits = [
        replace(outer.id(), r#"{"b": 2}"#),
        replace(field_id(outer, "b"), "3"),
    ];

    assert_eq!(
        apply(&doc, &edits),
        Err(Error::Conflict {
            outer: outer.id(),
            inner: field_id(outer, "b"),
        })
    );
}

#[test]
fn a_removal_nested_in_a_replacement_is_a_conflict() {
    let source = r#"{"a": {"b": 1, "c": 2}}"#;
    let doc = parsed(source);
    let outer = doc.root().find_field("a").unwrap().element();
    let edits = [
        replace(outer.id(), r#"{"b": 1}"#),
        Edit::remove(field_id(outer, "c")),
    ];

    assert_eq!(
        apply(&doc, &edits),
        Err(Error::Conflict {
            outer: outer.id(),
            inner: field_id(outer, "c"),
        })
    );
}

#[test]
fn an_id_from_beyond_the_document_is_reported() {
    let source = r#"{"a": 1}"#;
    let doc = parsed(source);
    let unknown = json::ElemId::from(99);
    let edits = [replace(unknown, "2")];

    assert_eq!(apply(&doc, &edits), Err(Error::UnknownElement(unknown)));
}

#[test]
fn removing_the_root_is_reported() {
    let source = r#"{"a": 1}"#;
    let doc = parsed(source);
    let edits = [Edit::remove(doc.root().id())];

    assert_eq!(
        apply(&doc, &edits),
        Err(Error::Removal(json::RemovalError::Root))
    );
}

/// Resolution is meant to make this unreachable, so the spans are handed to `splice` directly.
#[test]
fn overlapping_spans_are_reported_rather_than_spliced() {
    let source = r#"{"a": 1, "b": 2}"#;
    let first = json::Span { start: 1, end: 7 };
    let second = json::Span { start: 4, end: 9 };
    let splices = [
        Splice {
            span: first,
            text: "",
        },
        Splice {
            span: second,
            text: "",
        },
    ];

    assert_eq!(
        splice(source, &splices),
        Err(Error::OverlappingSpans { first, second })
    );
}

#[test]
fn edits_apply_to_pretty_printed_json() {
    let source = "{\n  \"a\": 1,\n  \"b\": null,\n  \"c\": 3\n}";
    let doc = parsed(source);
    let edits = [
        Edit::remove(field_id(doc.root(), "b")),
        replace(field_id(doc.root(), "c"), "30"),
    ];

    assert_eq!(
        apply(&doc, &edits),
        Ok("{\n  \"a\": 1,\n  \"c\": 30\n}".to_owned())
    );
}