Newer
Older
navi-1 / tests / unit / overlay / test_merge.py
"""Merge-patch semantics (RFC 7386) — the rule every overlay section relies on."""

from navi.overlay.merge import merge_patch


def test_missing_key_leaves_target_alone():
    assert merge_patch({"a": 1, "b": 2}, {"a": 9}) == {"a": 9, "b": 2}


def test_null_removes_the_key():
    assert merge_patch({"a": 1, "b": 2}, {"b": None}) == {"a": 1}


def test_null_on_missing_key_is_a_noop():
    assert merge_patch({"a": 1}, {"zzz": None}) == {"a": 1}


def test_objects_merge_recursively():
    target = {"settings": {"x": 1, "nested": {"keep": True, "change": 1}}}
    patch = {"settings": {"nested": {"change": 2}}}
    assert merge_patch(target, patch) == {
        "settings": {"x": 1, "nested": {"keep": True, "change": 2}}
    }


def test_arrays_replace_wholesale():
    # A list is a value, not a container to merge: "append one allowed command"
    # is not expressible, which is intentional — partial list edits are how a
    # policy ends up with values nobody ordered.
    assert merge_patch({"list": [1, 2, 3]}, {"list": [9]}) == {"list": [9]}


def test_scalar_patch_replaces_target():
    assert merge_patch({"a": {"deep": 1}}, {"a": "flat"}) == {"a": "flat"}


def test_dict_patch_over_scalar_target_starts_from_empty():
    assert merge_patch({"a": "flat"}, {"a": {"deep": 1}}) == {"a": {"deep": 1}}


def test_non_dict_patch_replaces_everything():
    assert merge_patch({"a": 1}, [1, 2]) == [1, 2]
    assert merge_patch({"a": 1}, None) is None


def test_inputs_are_not_mutated():
    target = {"a": {"b": 1}}
    patch = {"a": {"c": 2}}
    merge_patch(target, patch)
    assert target == {"a": {"b": 1}}
    assert patch == {"a": {"c": 2}}