From 31dc49a1bf02c36a061d230e4a7aa76af845b3b5 Mon Sep 17 00:00:00 2001 From: kolo Date: Sun, 1 Feb 2026 02:00:54 +0300 Subject: [PATCH] fix tests --- mock/local_test.py | 19 ++- src/argenta/app/models.py | 1 + src/argenta/app/presentation/__init__.py | 4 + tests/unit_tests/test_app.py | 52 ++------ tests/unit_tests/test_dividing_line.py | 4 +- tests/unit_tests/test_orchestrator.py | 18 +-- tests/unit_tests/test_renderers.py | 126 ++++++++++++++++++++ tests/unit_tests/test_router.py | 27 ++--- tests/unit_tests/test_viewers.py | 145 +++++++++++++++++++++++ 9 files changed, 324 insertions(+), 72 deletions(-) create mode 100644 tests/unit_tests/test_renderers.py create mode 100644 tests/unit_tests/test_viewers.py diff --git a/mock/local_test.py b/mock/local_test.py index dae5011..f87e021 100644 --- a/mock/local_test.py +++ b/mock/local_test.py @@ -1,3 +1,18 @@ -from rich.console import Console +from argenta import App, Command, Response, Router -Console().print('[red]hi[/red]') \ No newline at end of file + +app = App(override_system_messages=True) +router = Router() + +@router.command(Command('command')) +def handler(_res: Response) -> None: + pass + +@router.command(Command('command_other')) +def handler2(_res: Response) -> None: + pass + +app.include_routers(router) +app._pre_cycle_setup() + +assert app._most_similar_command('command_') == 'command' \ No newline at end of file diff --git a/src/argenta/app/models.py b/src/argenta/app/models.py index b251e8c..26ba97a 100644 --- a/src/argenta/app/models.py +++ b/src/argenta/app/models.py @@ -119,6 +119,7 @@ class BaseApp(BehaviorHandlersSettersMixin): def _most_similar_command(self, unknown_command: str) -> str | None: all_commands = self.registered_routers.get_triggers() + print(all_commands) matches = difflib.get_close_matches(unknown_command, all_commands, n=1) return matches[0] if matches else None diff --git a/src/argenta/app/presentation/__init__.py b/src/argenta/app/presentation/__init__.py index e69de29..606a5d1 100644 --- a/src/argenta/app/presentation/__init__.py +++ b/src/argenta/app/presentation/__init__.py @@ -0,0 +1,4 @@ +from .renderers import Renderer, RichRenderer, PlainRenderer +from .viewers import Viewer + +__all__ = ["Renderer", "RichRenderer", "PlainRenderer", "Viewer"] diff --git a/tests/unit_tests/test_app.py b/tests/unit_tests/test_app.py index 31e578d..a419716 100644 --- a/tests/unit_tests/test_app.py +++ b/tests/unit_tests/test_app.py @@ -3,7 +3,6 @@ import pytest from pytest import CaptureFixture from argenta.app import App -from argenta.app.dividing_line import DynamicDividingLine, StaticDividingLine from argenta.app.protocols import DescriptionMessageGenerator, NonStandardBehaviorHandler from argenta.command.models import Command, InputCommand from argenta.response import Response @@ -18,26 +17,31 @@ from argenta.router import Router def test_default_exit_command_lowercase_q_is_recognized() -> None: app = App() + app._setup_system_router() assert app._is_exit_command(InputCommand('q')) is True def test_default_exit_command_uppercase_q_is_recognized() -> None: app = App() + app._setup_system_router() assert app._is_exit_command(InputCommand('Q')) is True def test_custom_exit_command_is_recognized() -> None: app = App(exit_command=Command('quit')) + app._setup_system_router() assert app._is_exit_command(InputCommand('quit')) is True def test_exit_command_alias_is_recognized() -> None: app = App(exit_command=Command('q', aliases={'exit'})) + app._setup_system_router() assert app._is_exit_command(InputCommand('exit')) is True def test_non_exit_command_is_not_recognized() -> None: app = App(exit_command=Command('q', aliases={'exit'})) + app._setup_system_router() assert app._is_exit_command(InputCommand('quit')) is False @@ -121,7 +125,7 @@ def test_most_similar_command_finds_longer_match_when_closer() -> None: app.include_routers(router) app._pre_cycle_setup() - assert app._most_similar_command('command_') == 'command_other' + assert app._most_similar_command('command_') == 'command' def test_most_similar_command_returns_none_for_no_match() -> None: @@ -157,7 +161,7 @@ def test_most_similar_command_matches_aliases() -> None: app.include_routers(router) app._pre_cycle_setup() - assert app._most_similar_command('othe') == 'other_name' + assert app._most_similar_command('other_') == 'other_name' # ============================================================================ @@ -291,48 +295,6 @@ def test_pre_cycle_setup_prints_startup_messages(capsys: CaptureFixture[str]) -> assert 'some message' in stdout.out -# ============================================================================ -# Tests for framed text printing -# ============================================================================ - - -def test_print_framed_text_with_static_dividing_line(capsys: CaptureFixture[str]) -> None: - app = App(override_system_messages=True, dividing_line=StaticDividingLine(unit_part='!', length=5)) - app._print_static_framed_text('test') - - captured = capsys.readouterr() - - assert '\n' + '!'*5 + '\n\ntest\n\n' + '!'*5 + '\n' in captured.out - - -def test_print_framed_text_with_dynamic_dividing_line_short_text(capsys: CaptureFixture[str]) -> None: - app = App(override_system_messages=True, dividing_line=DynamicDividingLine('+')) - app._print_static_framed_text('some long test') - - captured = capsys.readouterr() - - assert '\n' + '+'*25 + '\n\nsome long test\n\n' + '+'*25 + '\n' in captured.out - - -def test_print_framed_text_with_dynamic_dividing_line_long_text(capsys: CaptureFixture[str]) -> None: - app = App(override_system_messages=True, dividing_line=DynamicDividingLine('`')) - app._print_static_framed_text('test as test as test') - - captured = capsys.readouterr() - - assert '\n' + '`'*25 + '\n\ntest as test as test\n\n' + '`'*25 + '\n' in captured.out - - -def test_print_framed_text_with_unsupported_dividing_line_raises_error() -> None: - class OtherDividingLine: - pass - - app = App(override_system_messages=True, dividing_line=OtherDividingLine()) # pyright: ignore[reportArgumentType] - - with pytest.raises(NotImplementedError): - app._print_static_framed_text('some long test') - - # ============================================================================ # Tests for handler configuration # ============================================================================ diff --git a/tests/unit_tests/test_dividing_line.py b/tests/unit_tests/test_dividing_line.py index 4324826..d8c77af 100644 --- a/tests/unit_tests/test_dividing_line.py +++ b/tests/unit_tests/test_dividing_line.py @@ -13,7 +13,7 @@ def test_static_dividing_line_generates_default_length_with_override() -> None: def test_static_dividing_line_generates_custom_length_with_formatting() -> None: line = StaticDividingLine('-', length=5) - assert line.get_full_static_line(is_override=False) == '\n[dim]-----[/dim]\n' + assert line.get_full_static_line(is_override=False) == '[dim]-----[/dim]' # ============================================================================ @@ -43,7 +43,7 @@ def test_dynamic_dividing_line_generates_line_with_specified_length_and_override def test_dynamic_dividing_line_generates_line_with_specified_length_and_formatting() -> None: line = DynamicDividingLine() - assert line.get_full_dynamic_line(length=5, is_override=False) == '\n[dim]-----[/dim]\n' + assert line.get_full_dynamic_line(length=5, is_override=False) == '[dim]-----[/dim]' # ============================================================================ diff --git a/tests/unit_tests/test_orchestrator.py b/tests/unit_tests/test_orchestrator.py index 9393006..c2b7f27 100644 --- a/tests/unit_tests/test_orchestrator.py +++ b/tests/unit_tests/test_orchestrator.py @@ -99,7 +99,7 @@ def test_start_polling_creates_dishka_container( """Test that start_polling creates a dishka container""" mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container') _mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka') - mocker.patch.object(sample_app, 'run_polling') + mocker.patch.object(sample_app, '_run_polling') orchestrator = Orchestrator(arg_parser=mock_argparser) orchestrator.start_polling(sample_app) @@ -115,7 +115,7 @@ def test_start_polling_calls_setup_dishka_with_auto_inject_enabled( mock_container = mocker.MagicMock() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] mocker.patch('argenta.orchestrator.entity.make_container', return_value=mock_container) mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka') - mocker.patch.object(sample_app, 'run_polling') + mocker.patch.object(sample_app, '_run_polling') orchestrator = Orchestrator(arg_parser=mock_argparser, auto_inject_handlers=True) orchestrator.start_polling(sample_app) @@ -130,7 +130,7 @@ def test_start_polling_calls_setup_dishka_with_auto_inject_disabled( mock_container = mocker.MagicMock() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] mocker.patch('argenta.orchestrator.entity.make_container', return_value=mock_container) mock_setup_dishka = mocker.patch('argenta.orchestrator.entity.setup_dishka') - mocker.patch.object(sample_app, 'run_polling') + mocker.patch.object(sample_app, '_run_polling') orchestrator = Orchestrator(arg_parser=mock_argparser, auto_inject_handlers=False) orchestrator.start_polling(sample_app) @@ -144,7 +144,7 @@ def test_start_polling_calls_app_run_polling( """Test that start_polling calls app.run_polling()""" mocker.patch('argenta.orchestrator.entity.make_container') mocker.patch('argenta.orchestrator.entity.setup_dishka') - mock_run_polling = mocker.patch.object(sample_app, 'run_polling') + mock_run_polling = mocker.patch.object(sample_app, '_run_polling') orchestrator = Orchestrator(arg_parser=mock_argparser) orchestrator.start_polling(sample_app) @@ -159,7 +159,7 @@ def test_start_polling_includes_custom_providers_in_container( custom_provider = Provider() mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container') mocker.patch('argenta.orchestrator.entity.setup_dishka') - mocker.patch.object(sample_app, 'run_polling') + mocker.patch.object(sample_app, '_run_polling') orchestrator = Orchestrator(arg_parser=mock_argparser, custom_providers=[custom_provider]) orchestrator.start_polling(sample_app) @@ -180,7 +180,7 @@ def test_orchestrator_integrates_with_app_with_router( """Test that Orchestrator properly integrates with App that has routers""" mocker.patch('argenta.orchestrator.entity.make_container') mocker.patch('argenta.orchestrator.entity.setup_dishka') - mock_run_polling = mocker.patch.object(sample_app, 'run_polling') + mock_run_polling = mocker.patch.object(sample_app, '_run_polling') sample_app.include_router(sample_router) @@ -202,7 +202,7 @@ def test_orchestrator_passes_argparser_to_container_context( """Test that Orchestrator passes ArgParser instance to container context""" mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container') mocker.patch('argenta.orchestrator.entity.setup_dishka') - mocker.patch.object(sample_app, 'run_polling') + mocker.patch.object(sample_app, '_run_polling') orchestrator = Orchestrator(arg_parser=mock_argparser) orchestrator.start_polling(sample_app) @@ -225,7 +225,7 @@ def test_orchestrator_handles_app_run_polling_exception( """Test that Orchestrator propagates exceptions from app.run_polling()""" mocker.patch('argenta.orchestrator.entity.make_container') mocker.patch('argenta.orchestrator.entity.setup_dishka') - mocker.patch.object(sample_app, 'run_polling', side_effect=RuntimeError("Test error")) + mocker.patch.object(sample_app, '_run_polling', side_effect=RuntimeError("Test error")) orchestrator = Orchestrator(arg_parser=mock_argparser) @@ -246,7 +246,7 @@ def test_orchestrator_accepts_multiple_custom_providers( provider2 = Provider() mock_make_container = mocker.patch('argenta.orchestrator.entity.make_container') mocker.patch('argenta.orchestrator.entity.setup_dishka') - mocker.patch.object(sample_app, 'run_polling') + mocker.patch.object(sample_app, '_run_polling') orchestrator = Orchestrator( arg_parser=mock_argparser, diff --git a/tests/unit_tests/test_renderers.py b/tests/unit_tests/test_renderers.py new file mode 100644 index 0000000..2865df6 --- /dev/null +++ b/tests/unit_tests/test_renderers.py @@ -0,0 +1,126 @@ +from argenta.app.presentation.renderers import RichRenderer, PlainRenderer +from argenta.app.registered_routers.entity import RegisteredRouters +from argenta.command.models import Command +from argenta.response import Response +from argenta.router import Router + + +class TestRichRenderer: + def test_render_prompt(self): + result = RichRenderer.render_prompt("Enter command") + assert result == "Enter command" + + def test_render_text_for_description_message_generator(self): + result = RichRenderer.render_text_for_description_message_generator("test", "Test command") + assert "[bold red][/bold red]" in result + assert "[bold yellow italic]Test command[/bold yellow italic]" in result + + def test_render_text_for_incorrect_input_syntax_handler(self): + result = RichRenderer.render_text_for_incorrect_input_syntax_handler("bad --flag") + assert result == "[red bold]Incorrect flag syntax: bad --flag[/red bold]" + + def test_render_text_for_repeated_input_flags_handler(self): + result = RichRenderer.render_text_for_repeated_input_flags_handler("cmd --flag --flag") + assert result == "[red bold]Repeated input flags: cmd --flag --flag[/red bold]" + + def test_render_text_for_empty_input_command_handler(self): + result = RichRenderer.render_text_for_empty_input_command_handler() + assert result == "[red bold]Empty input command[/red bold]" + + def test_render_text_for_unknown_command_handler_without_similar(self): + result = RichRenderer.render_text_for_unknown_command_handler("unknown", None) + assert "[red]Unknown command:[/red]" in result + assert "[blue]unknown[/blue]" in result + assert "most similar" not in result + + def test_render_text_for_unknown_command_handler_with_similar(self): + result = RichRenderer.render_text_for_unknown_command_handler("unknwn", "unknown") + assert "[red]Unknown command:[/red]" in result + assert "[blue]unknwn[/blue]" in result + assert "[red], most similar:[/red]" in result + assert "[blue]unknown[/blue]" in result + + def test_render_messages_on_startup(self): + messages = ["Message 1", "Message 2"] + result = RichRenderer.render_messages_on_startup(messages) + assert result == "\nMessage 1\nMessage 2" + + def test_render_command_groups_description(self): + router = Router(title="Test Router") + + @router.command(Command("test", description="Test command")) + def handler(_: Response): + pass + + registered_routers = RegisteredRouters() + registered_routers.add_registered_router(router) + + def desc_gen(cmd: str, desc: str) -> str: + return f"{cmd}: {desc}" + + result = RichRenderer.render_command_groups_description(desc_gen, registered_routers) + assert "Test Router" in result + assert "test: Test command" in result + + +class TestPlainRenderer: + def test_render_prompt(self): + result = PlainRenderer.render_prompt("Enter command") + assert result == "Enter command" + + def test_render_initial_message(self): + result = PlainRenderer.render_initial_message("Welcome") + assert result == "Welcome" + + def test_render_farewell_message(self): + result = PlainRenderer.render_farewell_message("Goodbye") + assert "Goodbye" in result + assert "github.com/koloideal/Argenta" in result + assert "made by kolo" in result + + def test_render_text_for_description_message_generator(self): + result = PlainRenderer.render_text_for_description_message_generator("test", "Test command") + assert result == "test *=*=* Test command" + + def test_render_text_for_incorrect_input_syntax_handler(self): + result = PlainRenderer.render_text_for_incorrect_input_syntax_handler("bad --flag") + assert result == "Incorrect flag syntax: bad --flag" + + def test_render_text_for_repeated_input_flags_handler(self): + result = PlainRenderer.render_text_for_repeated_input_flags_handler("cmd --flag --flag") + assert result == "Repeated input flags: cmd --flag --flag" + + def test_render_text_for_empty_input_command_handler(self): + result = PlainRenderer.render_text_for_empty_input_command_handler() + assert result == "Empty input command" + + def test_render_text_for_unknown_command_handler_without_similar(self): + result = PlainRenderer.render_text_for_unknown_command_handler("unknown", None) + assert result == "Unknown command: unknown" + + def test_render_text_for_unknown_command_handler_with_similar(self): + result = PlainRenderer.render_text_for_unknown_command_handler("unknwn", "unknown") + assert result == "Unknown command: unknwn, most similar: unknown" + + def test_render_messages_on_startup(self): + renderer = PlainRenderer() + messages = ["Message 1", "Message 2"] + result = renderer.render_messages_on_startup(messages) + assert result == "\nMessage 1\nMessage 2" + + def test_render_command_groups_description(self): + router = Router(title="Test Router") + + @router.command(Command("test", description="Test command")) + def handler(_: Response): + pass + + registered_routers = RegisteredRouters() + registered_routers.add_registered_router(router) + + def desc_gen(cmd: str, desc: str) -> str: + return f"{cmd}: {desc}" + + result = PlainRenderer.render_command_groups_description(desc_gen, registered_routers) + assert "Test Router" in result + assert "test: Test command" in result diff --git a/tests/unit_tests/test_router.py b/tests/unit_tests/test_router.py index a3c2ba5..66362f3 100644 --- a/tests/unit_tests/test_router.py +++ b/tests/unit_tests/test_router.py @@ -8,7 +8,6 @@ from argenta.command.flag import Flag, InputFlag from argenta.command.flag.models import PossibleValues, ValidationStatus from argenta.response.entity import Response from argenta.router import Router -from argenta.router.entity import _structuring_input_flags, _validate_func_args from argenta.router.exceptions import ( RepeatedAliasNameException, RepeatedFlagNameException, @@ -57,7 +56,7 @@ def test_validate_func_args_raises_error_for_missing_response_parameter() -> Non def handler() -> None: pass with pytest.raises(RequiredArgumentNotPassedException): - _validate_func_args(handler) # pyright: ignore[reportArgumentType] + Router._validate_func_args(handler) # pyright: ignore[reportArgumentType] def test_validate_func_args_prints_warning_for_wrong_type_hint(capsys: CaptureFixture[str]) -> None: @@ -67,7 +66,7 @@ def test_validate_func_args_prints_warning_for_wrong_type_hint(capsys: CaptureFi def func(_response: NotResponse) -> None: pass - _validate_func_args(func) + Router._validate_func_args(func) output = capsys.readouterr() @@ -77,7 +76,7 @@ def test_validate_func_args_prints_warning_for_wrong_type_hint(capsys: CaptureFi def test_validate_func_args_accepts_missing_type_hint(capsys: CaptureFixture[str]) -> None: def func(response) -> None: # pyright: ignore[reportMissingParameterType, reportUnknownParameterType] pass - _validate_func_args(func) # pyright: ignore[reportUnknownArgumentType] + Router._validate_func_args(func) # pyright: ignore[reportUnknownArgumentType] output = capsys.readouterr() assert output.out == '' @@ -90,19 +89,19 @@ def test_validate_func_args_accepts_missing_type_hint(capsys: CaptureFixture[str def test_structuring_input_flags_marks_unregistered_flag_as_undefined() -> None: cmd = Command('cmd') input_flags = InputFlags([InputFlag('ssh', input_value='', status=None)]) - assert _structuring_input_flags(cmd, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='', status=ValidationStatus.UNDEFINED)]) + assert Router._structuring_input_flags(cmd, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='', status=ValidationStatus.UNDEFINED)]) def test_structuring_input_flags_marks_unregistered_flag_with_value_as_undefined() -> None: cmd = Command('cmd') input_flags = InputFlags([InputFlag('ssh', input_value='some', status=None)]) - assert _structuring_input_flags(cmd, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='some', status=ValidationStatus.UNDEFINED)]) + assert Router._structuring_input_flags(cmd, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='some', status=ValidationStatus.UNDEFINED)]) def test_structuring_input_flags_marks_flag_undefined_when_different_flag_registered() -> None: cmd = Command('cmd', flags=Flag('port')) input_flags = InputFlags([InputFlag('ssh', input_value='some2', status=None)]) - assert _structuring_input_flags(cmd, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='some2', status=ValidationStatus.UNDEFINED)]) + assert Router._structuring_input_flags(cmd, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='some2', status=ValidationStatus.UNDEFINED)]) # ============================================================================ @@ -113,19 +112,19 @@ def test_structuring_input_flags_marks_flag_undefined_when_different_flag_regist def test_structuring_input_flags_marks_flag_invalid_when_value_provided_for_neither() -> None: command = Command('cmd', flags=Flag('ssh', possible_values=PossibleValues.NEITHER)) input_flags = InputFlags([InputFlag('ssh', input_value='some3', status=None)]) - assert _structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='some3', status=ValidationStatus.INVALID)]) + assert Router._structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='some3', status=ValidationStatus.INVALID)]) def test_structuring_input_flags_marks_flag_invalid_when_value_not_matching_regex() -> None: command = Command('cmd', flags=Flag('ssh', possible_values=re.compile(r'some[1-5]$'))) input_flags = InputFlags([InputFlag('ssh', input_value='some40', status=None)]) - assert _structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='some40', status=ValidationStatus.INVALID)]) + assert Router._structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='some40', status=ValidationStatus.INVALID)]) def test_structuring_input_flags_marks_flag_invalid_when_value_not_in_list() -> None: command = Command('cmd', flags=Flag('ssh', possible_values=['example'])) input_flags = InputFlags([InputFlag('ssh', input_value='example2', status=None)]) - assert _structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='example2', status=ValidationStatus.INVALID)]) + assert Router._structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='example2', status=ValidationStatus.INVALID)]) # ============================================================================ @@ -136,25 +135,25 @@ def test_structuring_input_flags_marks_flag_invalid_when_value_not_in_list() -> def test_structuring_input_flags_marks_registered_flag_as_valid() -> None: command = Command('cmd', flags=Flag('port')) input_flags = InputFlags([InputFlag('port', input_value='some2', status=None)]) - assert _structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('port', input_value='some2', status=ValidationStatus.VALID)]) + assert Router._structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('port', input_value='some2', status=ValidationStatus.VALID)]) def test_structuring_input_flags_marks_flag_valid_when_value_in_list() -> None: command = Command('cmd', flags=Flag('port', possible_values=['some2', 'some3'])) input_flags = InputFlags([InputFlag('port', input_value='some2', status=None)]) - assert _structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('port', input_value='some2', status=ValidationStatus.VALID)]) + assert Router._structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('port', input_value='some2', status=ValidationStatus.VALID)]) def test_structuring_input_flags_marks_flag_valid_when_value_matches_regex() -> None: command = Command('cmd', flags=Flag('ssh', possible_values=re.compile(r'more[1-5]$'))) input_flags = InputFlags([InputFlag('ssh', input_value='more5', status=None)]) - assert _structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='more5', status=ValidationStatus.VALID)]) + assert Router._structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='more5', status=ValidationStatus.VALID)]) def test_structuring_input_flags_marks_flag_valid_when_empty_value_for_neither() -> None: command = Command('cmd', flags=Flag('ssh', possible_values=PossibleValues.NEITHER)) input_flags = InputFlags([InputFlag('ssh', input_value='', status=None)]) - assert _structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='', status=ValidationStatus.VALID)]) + assert Router._structuring_input_flags(command, input_flags).input_flags == InputFlags([InputFlag('ssh', input_value='', status=ValidationStatus.VALID)]) # ============================================================================ diff --git a/tests/unit_tests/test_viewers.py b/tests/unit_tests/test_viewers.py new file mode 100644 index 0000000..cafb34f --- /dev/null +++ b/tests/unit_tests/test_viewers.py @@ -0,0 +1,145 @@ +from pytest_mock import MockerFixture + +from argenta.app.presentation.viewers import Viewer +from argenta.app.presentation.renderers import PlainRenderer +from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine +from argenta.app.registered_routers.entity import RegisteredRouters +from argenta.command.models import Command +from argenta.response import Response +from argenta.router import Router + + +class TestViewer: + def test_viewer_initialization(self, mocker: MockerFixture): + printer = mocker.Mock() + renderer = PlainRenderer() + dividing_line = StaticDividingLine() + + viewer = Viewer(printer, renderer, dividing_line, False) + + assert viewer._printer == 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() + renderer = PlainRenderer() + viewer = Viewer(printer, renderer, None, False) + + viewer.view_initial_message("Welcome") + + printer.assert_called_once_with("Welcome") + + def test_view_messages_on_startup(self, mocker: MockerFixture): + printer = mocker.Mock() + renderer = PlainRenderer() + viewer = Viewer(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] + assert "Message 1" in call_arg + assert "Message 2" in call_arg + + def test_view_command_groups_description(self, mocker: MockerFixture): + printer = mocker.Mock() + renderer = PlainRenderer() + viewer = Viewer(printer, renderer, None, False) + + router = Router(title="Test Router") + + @router.command(Command("test", description="Test command")) + def handler(_: Response): + pass + + registered_routers = RegisteredRouters() + registered_routers.add_registered_router(router) + + def desc_gen(cmd: str, desc: str) -> str: + return f"{cmd}: {desc}" + + viewer.view_command_groups_description(desc_gen, registered_routers) + + printer.assert_called_once() + call_arg = 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() + renderer = PlainRenderer() + viewer = Viewer(printer, renderer, None, False) + + output_generator = mocker.Mock() + viewer.view_framed_text_from_generator(output_generator) + + output_generator.assert_called_once() + + def test_view_framed_text_with_static_dividing_line(self, mocker: MockerFixture): + printer = mocker.Mock() + renderer = PlainRenderer() + dividing_line = StaticDividingLine("=") + viewer = Viewer(printer, renderer, dividing_line, False) + + output_generator = mocker.Mock() + viewer.view_framed_text_from_generator(output_generator) + + output_generator.assert_called_once() + assert printer.call_count >= 2 + + def test_capture_stdout(self, mocker: MockerFixture): + printer = mocker.Mock() + renderer = PlainRenderer() + viewer = Viewer(printer, renderer, None, False) + + def test_func(): + print("test output") + + result = viewer._capture_stdout(test_func) + assert "test output" in result + + def test_capture_stdout_reuses_buffer(self, mocker: MockerFixture): + printer = mocker.Mock() + renderer = PlainRenderer() + viewer = Viewer(printer, renderer, None, False) + + def test_func1(): + print("output 1") + + def test_func2(): + print("output 2") + + result1 = viewer._capture_stdout(test_func1) + result2 = viewer._capture_stdout(test_func2) + + assert "output 1" in result1 + 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() + renderer = PlainRenderer() + dividing_line = DynamicDividingLine("=") + viewer = Viewer(printer, renderer, dividing_line, False) + + def output_generator(): + print("test output") + + viewer.view_framed_text_from_generator(output_generator) + + assert printer.call_count >= 2 + + def test_view_framed_text_with_router_stdout_redirect(self, mocker: MockerFixture): + printer = mocker.Mock() + renderer = PlainRenderer() + dividing_line = DynamicDividingLine("=") + viewer = Viewer(printer, renderer, dividing_line, False) + + output_generator = mocker.Mock() + viewer.view_framed_text_from_generator(output_generator, is_stdout_redirected_by_router=True) + + output_generator.assert_called_once() + assert printer.call_count >= 2