from dataclasses import asdict, dataclass import pytest from src.backend.logic.reporting.report import config, main from src.backend.logic.reporting.report.config import ( PLACEHOLDER_DELIMITERS_END, PLACEHOLDER_DELIMITERS_START, ) class TestReplacePlaceholders: _class = main.PlaceholderReplacer @dataclass class MockData: placeholder1: str = "value1" placeholder2: int = 2 placeholder3: float = 3.0 _data = MockData() _data_as_dict = asdict(_data) @pytest.fixture def instance(self): return self._class(self._data) @dataclass class MockRun: text: str @pytest.mark.parametrize("casing_modifier", [str.upper, str.lower, str.capitalize]) def test_replace(self, instance, casing_modifier): """Test replace_placeholders function with diverse data types, and also with different placeholder text casing modifiers. """ placeholders = " ".join( self._wrap_as_placeholder(key) for key in map(casing_modifier, self._data_as_dict) ) mock_run = self.MockRun(text=self._wrap_string(placeholders)) instance.replace([mock_run]) values = " ".join(str(v) for v in self._data_as_dict.values()) expected = self._wrap_string(values) assert mock_run.text == expected, "Placeholders not replaced correctly." def test_replace_placeholders_not_found(self, instance): """Test replace_placeholders function with a placeholder that is not found in the data object. This should raise an exception as we don't want to let unmatched placeholders go unnoticed. """ mock_non_existing_placeholder = self._wrap_as_placeholder( "non_existing_placeholder" ) mock_run = self.MockRun(text=self._wrap_string(mock_non_existing_placeholder)) with pytest.raises(ValueError): instance.replace([mock_run]) @pytest.mark.parametrize( "texts", [ ("", "", ""), ("", " ", ""), ("", " ", " "), ("", " ", " ", ""), ], ) def test_replace_runs_empty_text(self, instance, texts): """Early return if aggregated run text is empty.""" mock_runs = [self.MockRun(text=text) for text in texts] instance.replace(mock_runs) assert all( run.text == text for run, text in zip(mock_runs, texts) ), "Empty text should not be modified." @pytest.mark.parametrize( "start_count, end_count, expected", [ (1, 0, ValueError), (0, 1, ValueError), (0, 0, None), (1, 1, None), ], ) def test_replace_unbalanced_placeholders( self, instance, start_count, end_count, expected ): """Test replace_placeholders function with unbalanced placeholders.""" mock_run = self.MockRun( text=PLACEHOLDER_DELIMITERS_START * start_count + PLACEHOLDER_DELIMITERS_END * end_count ) if expected: with pytest.raises(expected): instance.replace([mock_run]) else: instance.replace([mock_run]) assert mock_run.text == mock_run.text @pytest.mark.parametrize( "text, expected", [ ( f"Foo {PLACEHOLDER_DELIMITERS_END}{PLACEHOLDER_DELIMITERS_START} Bar", ValueError, ), (f"Foo {PLACEHOLDER_DELIMITERS_START} Bar", ValueError), (f"Foo {PLACEHOLDER_DELIMITERS_END} Bar", ValueError), ( f"Foo {PLACEHOLDER_DELIMITERS_START}{PLACEHOLDER_DELIMITERS_START}" f"{PLACEHOLDER_DELIMITERS_END}{PLACEHOLDER_DELIMITERS_END} Bar", ValueError, ), ( f"Foo {PLACEHOLDER_DELIMITERS_END} Bar {PLACEHOLDER_DELIMITERS_END}", ValueError, ), ( f"Foo {PLACEHOLDER_DELIMITERS_START} Bar {PLACEHOLDER_DELIMITERS_START}", ValueError, ), ( f"Foo {PLACEHOLDER_DELIMITERS_START} Bar {PLACEHOLDER_DELIMITERS_END}", None, ), ], ) def test_check_placeholders_balanced(self, instance, text, expected): """Test _check_placeholders_balanced function with balanced placeholders.""" if expected: with pytest.raises(expected): instance._check_placeholders_balanced(text) else: assert instance._check_placeholders_balanced(text) is None @staticmethod def _wrap_string(string: str): """Wrap string with 'Foo' and 'Bar' for simulating placeholders are in middle of a larger text. """ return f"Foo {string} Bar" @staticmethod def _wrap_as_placeholder(string): return ( f"{config.PLACEHOLDER_DELIMITERS_START}{string}" f"{config.PLACEHOLDER_DELIMITERS_END}" )