import pytest from streamd.localize.repository_configuration import ( Dimension, Marker, MarkerPlacement, RepositoryConfiguration, merge_dimensions, merge_markers, merge_repository_configuration, merge_single_dimension, merge_single_marker, ) class TestMergeSingleDimension: def test_second_overrides_display_name_when_non_empty(self): base = Dimension(display_name="Base", comment="c1", propagate=True) second = Dimension(display_name="Second", comment="c2", propagate=False) merged = merge_single_dimension(base, second) assert merged.display_name == "Second" assert merged.comment == "c2" assert merged.propagate is False def test_second_empty_display_name_falls_back_to_base(self): base = Dimension(display_name="Base", comment="c1", propagate=True) second = Dimension(display_name="", comment="c2", propagate=False) merged = merge_single_dimension(base, second) assert merged.display_name == "Base" assert merged.comment == "c2" assert merged.propagate is False def test_second_comment_none_does_not_erase_base_comment(self): base = Dimension(display_name="Base", comment="keep", propagate=True) second = Dimension(display_name="Second", comment=None, propagate=False) merged = merge_single_dimension(base, second) assert merged.display_name == "Second" assert merged.comment == "keep" def test_second_comment_non_none_overrides_base_comment(self): base = Dimension(display_name="Base", comment="c1", propagate=True) second = Dimension(display_name="Second", comment="c2", propagate=True) merged = merge_single_dimension(base, second) assert merged.comment == "c2" def test_second_propagate_overrides_base_when_provided(self): base = Dimension(display_name="Base", comment="c1", propagate=True) second = Dimension(display_name="Second", comment="c2", propagate=False) merged = merge_single_dimension(base, second) assert merged.propagate is False def test_propagate_merging_retains_base_when_second_not_provided(self): base = Dimension(display_name="Base", comment="c1", propagate=True) second = Dimension(display_name="Second", comment="c2") merged = merge_single_dimension(base, second) assert merged.propagate is True class TestMergeDimensions: def test_adds_new_keys_from_second(self): base = {"a": Dimension(display_name="A", propagate=True)} second = {"b": Dimension(display_name="B", propagate=False)} merged = merge_dimensions(base, second) assert set(merged.keys()) == {"a", "b"} assert merged["a"].display_name == "A" assert merged["b"].display_name == "B" def test_merges_existing_keys(self): base = {"a": Dimension(display_name="A", comment="c1", propagate=True)} second = {"a": Dimension(display_name="A2", comment=None, propagate=False)} merged = merge_dimensions(base, second) assert merged["a"].display_name == "A2" assert merged["a"].comment == "c1" assert merged["a"].propagate is False def test_does_not_mutate_inputs(self): base = {"a": Dimension(display_name="A", comment="c1", propagate=True)} second = {"b": Dimension(display_name="B", comment="c2", propagate=False)} merged = merge_dimensions(base, second) assert "b" not in base assert "a" not in second assert set(merged.keys()) == {"a", "b"} class TestMergeSingleMarker: def test_second_overrides_display_name_when_non_empty(self): base = Marker( display_name="Base", placements=[MarkerPlacement(dimension="project", value=None)], ) second = Marker( display_name="Second", placements=[MarkerPlacement(dimension="timesheet", value="coding")], ) merged = merge_single_marker(base, second) assert merged.display_name == "Second" assert merged.placements == [ MarkerPlacement(dimension="project", value=None, if_with=set()), MarkerPlacement(dimension="timesheet", value="coding", if_with=set()), ] def test_second_empty_display_name_falls_back_to_base(self): base = Marker(display_name="Base", placements=[]) second = Marker(display_name="", placements=[]) merged = merge_single_marker(base, second) assert merged.display_name == "Base" def test_appends_new_placements(self): base = Marker( display_name="Base", placements=[ MarkerPlacement(dimension="project"), ], ) second = Marker( display_name="Second", placements=[ MarkerPlacement( if_with={"Timesheet"}, dimension="timesheet", value="x" ), ], ) merged = merge_single_marker(base, second) assert merged.placements == [ MarkerPlacement(dimension="project"), MarkerPlacement(if_with={"Timesheet"}, dimension="timesheet", value="x"), ] def test_deduplicates_by_identity_and_second_overrides_base(self): base = Marker( display_name="Base", placements=[ MarkerPlacement(if_with={"A"}, dimension="d", value="v"), MarkerPlacement(if_with={"B"}, dimension="d", value="v2"), ], ) second = Marker( display_name="Second", placements=[ MarkerPlacement(if_with={"A"}, dimension="d", value="v"), MarkerPlacement(if_with={"C"}, dimension="d", value="v3"), ], ) merged = merge_single_marker(base, second) assert merged.placements == [ MarkerPlacement(if_with={"A"}, dimension="d", value="v"), MarkerPlacement(if_with={"B"}, dimension="d", value="v2"), MarkerPlacement(if_with={"C"}, dimension="d", value="v3"), ] def test_identity_is_order_insensitive_for_if_with(self): base = Marker( display_name="Base", placements=[MarkerPlacement(if_with={"A", "B"}, dimension="d", value="v")], ) second = Marker( display_name="Second", placements=[MarkerPlacement(if_with={"B", "A"}, dimension="d", value="v2")], ) merged = merge_single_marker(base, second) # With `if_with` as a set, identity is order-insensitive; second overrides base. assert merged.placements == [ MarkerPlacement(if_with={"A", "B"}, dimension="d", value="v2"), ] class TestMergeMarkers: def test_adds_new_marker_keys_from_second(self): base = {"M1": Marker(display_name="M1", placements=[])} second = {"M2": Marker(display_name="M2", placements=[])} merged = merge_markers(base, second) assert set(merged.keys()) == {"M1", "M2"} def test_merges_existing_marker_keys(self): base = { "M": Marker( display_name="Base", placements=[MarkerPlacement(dimension="project")], ) } second = { "M": Marker( display_name="Second", placements=[ MarkerPlacement( if_with={"Timesheet"}, dimension="timesheet", value="coding" ) ], ) } merged = merge_markers(base, second) assert merged["M"].display_name == "Second" assert merged["M"].placements == [ MarkerPlacement(dimension="project", value=None, if_with=set()), MarkerPlacement( if_with={"Timesheet"}, dimension="timesheet", value="coding" ), ] def test_does_not_mutate_inputs(self): base = {"M1": Marker(display_name="M1", placements=[])} second = {"M2": Marker(display_name="M2", placements=[])} merged = merge_markers(base, second) assert "M2" not in base assert "M1" not in second assert set(merged.keys()) == {"M1", "M2"} class TestMergeRepositoryConfiguration: def test_merges_dimensions_and_markers(self): base = RepositoryConfiguration( dimensions={ "project": Dimension( display_name="Project", comment="c1", propagate=True ), "moment": Dimension( display_name="Moment", comment="c2", propagate=True ), }, markers={ "Streamd": Marker( display_name="Streamd", placements=[MarkerPlacement(dimension="project")], ) }, ) second = RepositoryConfiguration( dimensions={ "project": Dimension(display_name="Project2", propagate=False), "timesheet": Dimension( display_name="Timesheet", comment="c3", propagate=False ), }, markers={ "Streamd": Marker( display_name="Streamd2", placements=[ MarkerPlacement( if_with={"Timesheet"}, dimension="timesheet", value="coding" ) ], ), "JobHunting": Marker( display_name="JobHunting", placements=[MarkerPlacement(dimension="project")], ), }, ) merged = merge_repository_configuration(base, second) assert set(merged.dimensions.keys()) == {"project", "moment", "timesheet"} assert merged.dimensions["project"].display_name == "Project2" assert merged.dimensions["project"].comment == "c1" assert merged.dimensions["project"].propagate is False assert merged.dimensions["moment"].display_name == "Moment" assert merged.dimensions["timesheet"].display_name == "Timesheet" assert set(merged.markers.keys()) == {"Streamd", "JobHunting"} assert merged.markers["Streamd"].display_name == "Streamd2" assert merged.markers["Streamd"].placements == [ MarkerPlacement(dimension="project", value=None, if_with=set()), MarkerPlacement( if_with={"Timesheet"}, dimension="timesheet", value="coding" ), ] assert merged.markers["JobHunting"].placements == [ MarkerPlacement(dimension="project", value=None, if_with=set()) ] def test_does_not_mutate_base_or_second(self): base = RepositoryConfiguration( dimensions={"a": Dimension(display_name="A", propagate=True)}, markers={"M": Marker(display_name="M", placements=[])}, ) second = RepositoryConfiguration( dimensions={"b": Dimension(display_name="B", propagate=False)}, markers={"N": Marker(display_name="N", placements=[])}, ) _ = merge_repository_configuration(base, second) assert set(base.dimensions.keys()) == {"a"} assert set(second.dimensions.keys()) == {"b"} assert set(base.markers.keys()) == {"M"} assert set(second.markers.keys()) == {"N"} def test_merge_is_associative_for_non_conflicting_inputs(self): a = RepositoryConfiguration( dimensions={"d1": Dimension(display_name="D1", propagate=True)}, markers={"m1": Marker(display_name="M1", placements=[])}, ) b = RepositoryConfiguration( dimensions={"d2": Dimension(display_name="D2", propagate=False)}, markers={"m2": Marker(display_name="M2", placements=[])}, ) c = RepositoryConfiguration( dimensions={"d3": Dimension(display_name="D3", propagate=False)}, markers={"m3": Marker(display_name="M3", placements=[])}, ) left = merge_repository_configuration(merge_repository_configuration(a, b), c) right = merge_repository_configuration(a, merge_repository_configuration(b, c)) assert left == right assert set(left.dimensions.keys()) == {"d1", "d2", "d3"} assert set(left.markers.keys()) == {"m1", "m2", "m3"} @pytest.mark.parametrize( ("base", "second", "expected_propagate"), [ ( RepositoryConfiguration( dimensions={"d": Dimension(display_name="D", propagate=True)}, markers={}, ), RepositoryConfiguration( dimensions={"d": Dimension(display_name="D2")}, markers={}, ), True, ) ], ) def test_merge_repository_configuration_propagate_preserves_base_when_omitted( base: RepositoryConfiguration, second: RepositoryConfiguration, expected_propagate: bool, ): merged = merge_repository_configuration(base, second) assert merged.dimensions["d"].propagate is expected_propagate