diff --git a/src/argenta/app/behavior_handlers/models.py b/src/argenta/app/behavior_handlers/models.py index 7786df6..c997803 100644 --- a/src/argenta/app/behavior_handlers/models.py +++ b/src/argenta/app/behavior_handlers/models.py @@ -80,35 +80,15 @@ class BehaviorHandlersSettersMixin: self._exit_command_handler: NonStandardBehaviorHandler[Response] = exit_command_handler def set_description_message_pattern(self, _: DescriptionMessageGenerator, /) -> None: - """ - Public. Sets the output pattern of the available commands - :param _: output pattern of the available commands - :return: None - """ self._description_message_generator = _ def set_incorrect_input_syntax_handler(self, _: NonStandardBehaviorHandler[str], /) -> None: - """ - Public. Sets the handler for incorrect flags when entering a command - :param _: handler for incorrect flags when entering a command - :return: None - """ self._incorrect_input_syntax_handler = _ def set_repeated_input_flags_handler(self, _: NonStandardBehaviorHandler[str], /) -> None: - """ - Public. Sets the handler for repeated flags when entering a command - :param _: handler for repeated flags when entering a command - :return: None - """ self._repeated_input_flags_handler = _ def set_unknown_command_handler(self, _: NonStandardBehaviorHandler[InputCommand], /) -> None: - """ - Public. Sets the handler for unknown commands when entering a command - :param _: handler for unknown commands when entering a command - :return: None - """ self._unknown_command_handler = _ def set_empty_command_handler(self, _: EmptyCommandHandler, /) -> None: diff --git a/tests/unit_tests/test_behavior_handlers.py b/tests/unit_tests/test_behavior_handlers.py new file mode 100644 index 0000000..7dcb843 --- /dev/null +++ b/tests/unit_tests/test_behavior_handlers.py @@ -0,0 +1,229 @@ +import pytest +from unittest.mock import Mock + +from argenta.app.behavior_handlers.models import BehaviorHandlersFabric, BehaviorHandlersSettersMixin +from argenta.app.presentation.renderers import PlainRenderer +from argenta.command.models import InputCommand +from argenta.response import Response, ResponseStatus + + +@pytest.fixture +def mock_printer() -> Mock: + return Mock() + + +@pytest.fixture +def mock_most_similar_getter() -> Mock: + return Mock(return_value="similar_cmd") + + +@pytest.fixture +def behavior_fabric(mock_printer: Mock, mock_most_similar_getter: Mock) -> BehaviorHandlersFabric: + renderer = PlainRenderer() + return BehaviorHandlersFabric(mock_printer, renderer, mock_most_similar_getter) + + +class TestBehaviorHandlersFabric: + def test_initialization(self, mock_printer: Mock, mock_most_similar_getter: Mock): + renderer = PlainRenderer() + fabric = BehaviorHandlersFabric(mock_printer, renderer, mock_most_similar_getter) + + assert fabric._printer == mock_printer + assert fabric._renderer == renderer + assert fabric._most_similar_command_getter == mock_most_similar_getter + + def test_generate_incorrect_input_syntax_handler(self, behavior_fabric: BehaviorHandlersFabric, mock_printer: Mock): + handler = behavior_fabric.generate_incorrect_input_syntax_handler() + + handler("bad --flag") + + mock_printer.assert_called_once() + call_arg = mock_printer.call_args[0][0] + assert "Incorrect flag syntax" in call_arg + assert "bad --flag" in call_arg + + def test_generate_repeated_input_flags_handler(self, behavior_fabric: BehaviorHandlersFabric, mock_printer: Mock): + handler = behavior_fabric.generate_repeated_input_flags_handler() + + handler("cmd --flag --flag") + + mock_printer.assert_called_once() + call_arg = mock_printer.call_args[0][0] + assert "Repeated input flags" in call_arg + assert "cmd --flag --flag" in call_arg + + def test_generate_empty_input_command_handler(self, behavior_fabric: BehaviorHandlersFabric, mock_printer: Mock): + handler = behavior_fabric.generate_empty_input_command_handler() + + handler() + + mock_printer.assert_called_once() + call_arg = mock_printer.call_args[0][0] + assert "Empty input command" in call_arg + + def test_generate_unknown_command_handler(self, behavior_fabric: BehaviorHandlersFabric, mock_printer: Mock, mock_most_similar_getter: Mock): + handler = behavior_fabric.generate_unknown_command_handler() + + input_command = InputCommand("unknown") + handler(input_command) + + mock_most_similar_getter.assert_called_once_with("unknown") + mock_printer.assert_called_once() + call_arg = mock_printer.call_args[0][0] + assert "Unknown command" in call_arg + assert "unknown" in call_arg + assert "similar_cmd" in call_arg + + def test_generate_unknown_command_handler_no_similar(self, mock_printer: Mock): + renderer = PlainRenderer() + most_similar_getter = Mock(return_value=None) + fabric = BehaviorHandlersFabric(mock_printer, renderer, most_similar_getter) + + handler = fabric.generate_unknown_command_handler() + input_command = InputCommand("unknown") + handler(input_command) + + most_similar_getter.assert_called_once_with("unknown") + mock_printer.assert_called_once() + call_arg = mock_printer.call_args[0][0] + assert "Unknown command" in call_arg + assert "unknown" in call_arg + assert "most similar" not in call_arg + + def test_generate_exit_command_handler(self, behavior_fabric: BehaviorHandlersFabric, mock_printer: Mock): + handler = behavior_fabric.generate_exit_command_handler("Goodbye!") + + response = Response(ResponseStatus.ALL_FLAGS_VALID) + handler(response) + + mock_printer.assert_called_once_with("Goodbye!") + + def test_generate_description_message_generator(self, behavior_fabric: BehaviorHandlersFabric): + generator = behavior_fabric.generate_description_message_generator() + + result = generator("test", "Test command") + + assert "test" in result + assert "Test command" in result + + +class TestBehaviorHandlersSettersMixin: + def test_initialization(self): + desc_gen = lambda cmd, desc: f"{cmd}: {desc}" + incorrect_handler = lambda raw: None + repeated_handler = lambda raw: None + empty_handler = lambda: None + unknown_handler = lambda cmd: None + exit_handler = lambda resp: None + + mixin = BehaviorHandlersSettersMixin( + desc_gen, + incorrect_handler, + repeated_handler, + empty_handler, + unknown_handler, + exit_handler + ) + + assert mixin._description_message_generator == desc_gen + assert mixin._incorrect_input_syntax_handler == incorrect_handler + assert mixin._repeated_input_flags_handler == repeated_handler + assert mixin._empty_input_command_handler == empty_handler + assert mixin._unknown_command_handler == unknown_handler + assert mixin._exit_command_handler == exit_handler + + def test_set_description_message_pattern(self): + initial_gen = lambda cmd, desc: f"{cmd}: {desc}" + mixin = BehaviorHandlersSettersMixin( + initial_gen, + lambda raw: None, + lambda raw: None, + lambda: None, + lambda cmd: None, + lambda resp: None + ) + + new_gen = lambda cmd, desc: f"{cmd} -> {desc}" + mixin.set_description_message_pattern(new_gen) + + assert mixin._description_message_generator == new_gen + + def test_set_incorrect_input_syntax_handler(self): + initial_handler = lambda raw: None + mixin = BehaviorHandlersSettersMixin( + lambda cmd, desc: f"{cmd}: {desc}", + initial_handler, + lambda raw: None, + lambda: None, + lambda cmd: None, + lambda resp: None + ) + + new_handler = lambda raw: print(f"Error: {raw}") + mixin.set_incorrect_input_syntax_handler(new_handler) + + assert mixin._incorrect_input_syntax_handler == new_handler + + def test_set_repeated_input_flags_handler(self): + initial_handler = lambda raw: None + mixin = BehaviorHandlersSettersMixin( + lambda cmd, desc: f"{cmd}: {desc}", + lambda raw: None, + initial_handler, + lambda: None, + lambda cmd: None, + lambda resp: None + ) + + new_handler = lambda raw: print(f"Repeated: {raw}") + mixin.set_repeated_input_flags_handler(new_handler) + + assert mixin._repeated_input_flags_handler == new_handler + + def test_set_unknown_command_handler(self): + initial_handler = lambda cmd: None + mixin = BehaviorHandlersSettersMixin( + lambda cmd, desc: f"{cmd}: {desc}", + lambda raw: None, + lambda raw: None, + lambda: None, + initial_handler, + lambda resp: None + ) + + new_handler = lambda cmd: print(f"Unknown: {cmd.trigger}") + mixin.set_unknown_command_handler(new_handler) + + assert mixin._unknown_command_handler == new_handler + + def test_set_empty_command_handler(self): + initial_handler = lambda: None + mixin = BehaviorHandlersSettersMixin( + lambda cmd, desc: f"{cmd}: {desc}", + lambda raw: None, + lambda raw: None, + initial_handler, + lambda cmd: None, + lambda resp: None + ) + + new_handler = lambda: print("Empty command") + mixin.set_empty_command_handler(new_handler) + + assert mixin._empty_input_command_handler == new_handler + + def test_set_exit_command_handler(self): + initial_handler = lambda resp: None + mixin = BehaviorHandlersSettersMixin( + lambda cmd, desc: f"{cmd}: {desc}", + lambda raw: None, + lambda raw: None, + lambda: None, + lambda cmd: None, + initial_handler + ) + + new_handler = lambda resp: print("Exiting...") + mixin.set_exit_command_handler(new_handler) + + assert mixin._exit_command_handler == new_handler diff --git a/tests/unit_tests/test_viewers.py b/tests/unit_tests/test_viewers.py index cafb34f..0101650 100644 --- a/tests/unit_tests/test_viewers.py +++ b/tests/unit_tests/test_viewers.py @@ -1,4 +1,5 @@ -from pytest_mock import MockerFixture +import pytest +from unittest.mock import Mock from argenta.app.presentation.viewers import Viewer from argenta.app.presentation.renderers import PlainRenderer @@ -9,45 +10,51 @@ from argenta.response import Response from argenta.router import Router +@pytest.fixture +def mock_printer() -> Mock: + return Mock() + + +@pytest.fixture +def mock_output_generator() -> Mock: + return Mock() + + class TestViewer: - def test_viewer_initialization(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_viewer_initialization(self, mock_printer: Mock): renderer = PlainRenderer() dividing_line = StaticDividingLine() - viewer = Viewer(printer, renderer, dividing_line, False) + viewer = Viewer(mock_printer, renderer, dividing_line, False) - assert viewer._printer == printer + assert viewer._printer == mock_printer assert viewer._renderer == renderer assert viewer._dividing_line == dividing_line assert viewer._override_system_messages is False - def test_view_initial_message(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_view_initial_message(self, mock_printer: Mock): renderer = PlainRenderer() - viewer = Viewer(printer, renderer, None, False) + viewer = Viewer(mock_printer, renderer, None, False) viewer.view_initial_message("Welcome") - printer.assert_called_once_with("Welcome") + mock_printer.assert_called_once_with("Welcome") - def test_view_messages_on_startup(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_view_messages_on_startup(self, mock_printer: Mock): renderer = PlainRenderer() - viewer = Viewer(printer, renderer, None, False) + viewer = Viewer(mock_printer, renderer, None, False) messages = ["Message 1", "Message 2"] viewer.view_messages_on_startup(messages) - printer.assert_called_once() - call_arg = printer.call_args[0][0] + mock_printer.assert_called_once() + call_arg = mock_printer.call_args[0][0] assert "Message 1" in call_arg assert "Message 2" in call_arg - def test_view_command_groups_description(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_view_command_groups_description(self, mock_printer: Mock): renderer = PlainRenderer() - viewer = Viewer(printer, renderer, None, False) + viewer = Viewer(mock_printer, renderer, None, False) router = Router(title="Test Router") @@ -63,37 +70,32 @@ class TestViewer: viewer.view_command_groups_description(desc_gen, registered_routers) - printer.assert_called_once() - call_arg = printer.call_args[0][0] + mock_printer.assert_called_once() + call_arg = mock_printer.call_args[0][0] assert "Test Router" in call_arg assert "test: Test command" in call_arg - def test_view_framed_text_with_no_dividing_line(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_view_framed_text_with_no_dividing_line(self, mock_printer: Mock, mock_output_generator: Mock): renderer = PlainRenderer() - viewer = Viewer(printer, renderer, None, False) + viewer = Viewer(mock_printer, renderer, None, False) - output_generator = mocker.Mock() - viewer.view_framed_text_from_generator(output_generator) + viewer.view_framed_text_from_generator(mock_output_generator) - output_generator.assert_called_once() + mock_output_generator.assert_called_once() - def test_view_framed_text_with_static_dividing_line(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_view_framed_text_with_static_dividing_line(self, mock_printer: Mock, mock_output_generator: Mock): renderer = PlainRenderer() dividing_line = StaticDividingLine("=") - viewer = Viewer(printer, renderer, dividing_line, False) + viewer = Viewer(mock_printer, renderer, dividing_line, False) - output_generator = mocker.Mock() - viewer.view_framed_text_from_generator(output_generator) + viewer.view_framed_text_from_generator(mock_output_generator) - output_generator.assert_called_once() - assert printer.call_count >= 2 + mock_output_generator.assert_called_once() + assert mock_printer.call_count >= 2 - def test_capture_stdout(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_capture_stdout(self, mock_printer: Mock): renderer = PlainRenderer() - viewer = Viewer(printer, renderer, None, False) + viewer = Viewer(mock_printer, renderer, None, False) def test_func(): print("test output") @@ -101,10 +103,9 @@ class TestViewer: result = viewer._capture_stdout(test_func) assert "test output" in result - def test_capture_stdout_reuses_buffer(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_capture_stdout_reuses_buffer(self, mock_printer: Mock): renderer = PlainRenderer() - viewer = Viewer(printer, renderer, None, False) + viewer = Viewer(mock_printer, renderer, None, False) def test_func1(): print("output 1") @@ -119,27 +120,24 @@ class TestViewer: assert "output 1" not in result2 assert "output 2" in result2 - def test_view_framed_text_with_dynamic_dividing_line(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_view_framed_text_with_dynamic_dividing_line(self, mock_printer: Mock): renderer = PlainRenderer() dividing_line = DynamicDividingLine("=") - viewer = Viewer(printer, renderer, dividing_line, False) + viewer = Viewer(mock_printer, renderer, dividing_line, False) def output_generator(): print("test output") viewer.view_framed_text_from_generator(output_generator) - assert printer.call_count >= 2 + assert mock_printer.call_count >= 2 - def test_view_framed_text_with_router_stdout_redirect(self, mocker: MockerFixture): - printer = mocker.Mock() + def test_view_framed_text_with_router_stdout_redirect(self, mock_printer: Mock, mock_output_generator: Mock): renderer = PlainRenderer() dividing_line = DynamicDividingLine("=") - viewer = Viewer(printer, renderer, dividing_line, False) + viewer = Viewer(mock_printer, renderer, dividing_line, False) - output_generator = mocker.Mock() - viewer.view_framed_text_from_generator(output_generator, is_stdout_redirected_by_router=True) + viewer.view_framed_text_from_generator(mock_output_generator, is_stdout_redirected_by_router=True) - output_generator.assert_called_once() - assert printer.call_count >= 2 + mock_output_generator.assert_called_once() + assert mock_printer.call_count >= 2