import sys from argparse import Namespace from typing import TYPE_CHECKING from unittest.mock import call import pytest from pytest_mock import MockerFixture from argenta.orchestrator.argparser.arguments.models import ( BaseArgument, BooleanArgument, InputArgument, ValueArgument, ) from argenta.orchestrator.argparser.entity import ArgParser, ArgSpace if TYPE_CHECKING: from pytest_mock.plugin import MockType def test_value_argument_creation() -> None: arg: ValueArgument = ValueArgument( name="test_arg", prefix="--", help="A test argument.", possible_values=["one", "two"], default="one", is_required=True, is_deprecated=False, ) assert arg.name == "test_arg" assert arg.prefix == "--" assert arg.help == "A test argument." assert arg.possible_values == ["one", "two"] assert arg.default == "one" assert arg.is_required is True assert arg.is_deprecated is False assert arg.action == "store" assert arg.string_entity == "--test_arg" def test_boolean_argument_creation() -> None: arg: BooleanArgument = BooleanArgument( name="verbose", prefix="-", help="Enable verbose mode.", is_deprecated=True ) assert arg.name == "verbose" assert arg.prefix == "-" assert arg.help == "Enable verbose mode." assert arg.is_deprecated is True assert arg.action == "store_true" assert arg.string_entity == "-verbose" def test_input_argument_creation() -> None: arg: InputArgument = InputArgument( name="file", value="/path/to/file", founder_class=ValueArgument ) assert arg.name == "file" assert arg.value == "/path/to/file" assert arg.founder_class is ValueArgument @pytest.fixture def mock_arguments() -> list[InputArgument]: return [ InputArgument(name="arg1", value="val1", founder_class=ValueArgument), InputArgument(name="arg2", value=True, founder_class=BooleanArgument), InputArgument(name="arg3", value="val3", founder_class=ValueArgument), ] @pytest.fixture def arg_space(mock_arguments: list[InputArgument]) -> ArgSpace: return ArgSpace(all_arguments=mock_arguments) def test_argspace_initialization(arg_space: ArgSpace, mock_arguments: list[InputArgument]) -> None: assert len(arg_space.all_arguments) == 3 assert arg_space.all_arguments == mock_arguments def test_argspace_get_by_name(arg_space: ArgSpace, mock_arguments: list[InputArgument]) -> None: found_arg: InputArgument | None = arg_space.get_by_name("arg1") assert found_arg is not None assert found_arg == mock_arguments[0] def test_argspace_get_by_name_not_found(arg_space: ArgSpace) -> None: found_arg: InputArgument | None = arg_space.get_by_name("non_existent_arg") assert found_arg is None def test_argspace_get_by_type(arg_space: ArgSpace, mock_arguments: list[InputArgument]) -> None: value_args: list[InputArgument] = arg_space.get_by_type(ValueArgument) assert len(value_args) == 2 assert mock_arguments[0] in value_args assert mock_arguments[2] in value_args bool_args: list[InputArgument] = arg_space.get_by_type(BooleanArgument) assert len(bool_args) == 1 assert mock_arguments[1] in bool_args def test_argspace_get_by_type_not_found(arg_space: ArgSpace) -> None: class OtherArgument(BaseArgument): pass other_args: list[InputArgument] = arg_space.get_by_type(OtherArgument) # pyright: ignore[reportAssignmentType] assert other_args == [] def test_argspace_from_namespace() -> None: namespace: Namespace = Namespace(config="config.json", debug=True, verbose=False) processed_args: list[ValueArgument | BooleanArgument] = [ ValueArgument(name="config", prefix="--"), BooleanArgument(name="debug", prefix="-"), BooleanArgument(name="verbose", prefix="-"), ] arg_space: ArgSpace = ArgSpace.from_namespace(namespace, processed_args) assert len(arg_space.all_arguments) == 3 config_arg: InputArgument | None = arg_space.get_by_name('config') debug_arg: InputArgument | None = arg_space.get_by_name('debug') assert config_arg is not None assert config_arg.value == "config.json" assert config_arg.founder_class is ValueArgument assert debug_arg is not None assert debug_arg.value is True assert debug_arg.founder_class is BooleanArgument @pytest.fixture def value_arg() -> ValueArgument: return ValueArgument( name="config", help="Path to config file", default="dev.json", is_required=False, possible_values=["dev.json", "prod.json"], ) @pytest.fixture def bool_arg() -> BooleanArgument: return BooleanArgument(name="debug", help="Enable debug mode") @pytest.fixture def processed_args(value_arg: ValueArgument, bool_arg: BooleanArgument) -> list[ValueArgument | BooleanArgument]: return [value_arg, bool_arg] def test_argparser_initialization(processed_args: list[ValueArgument | BooleanArgument]) -> None: parser: ArgParser = ArgParser( processed_args=processed_args, name="TestApp", description="A test application.", epilog="Test epilog.", ) assert parser.name == "TestApp" assert parser.description == "A test application." assert parser.epilog == "Test epilog." assert parser.processed_args == processed_args assert isinstance(parser.parsed_argspace, ArgSpace) assert parser.parsed_argspace.all_arguments == [] @pytest.mark.skipif(sys.version_info < (3, 13), reason="requires python3.13 or higher") def test_argparser_register_args_py313( mocker: MockerFixture, value_arg: ValueArgument, bool_arg: BooleanArgument ) -> None: mock_add_argument: MockType = mocker.patch("argparse.ArgumentParser.add_argument") parser: ArgParser = ArgParser(processed_args=[value_arg, bool_arg]) # pyright: ignore[reportUnusedVariable] expected_calls: list[call] = [ call( value_arg.string_entity, action=value_arg.action, help=value_arg.help, default=value_arg.default, choices=value_arg.possible_values, required=value_arg.is_required, deprecated=value_arg.is_deprecated, ), call( bool_arg.string_entity, action=bool_arg.action, help=bool_arg.help, deprecated=bool_arg.is_deprecated, ), ] mock_add_argument.assert_has_calls(expected_calls, any_order=True) @pytest.mark.skipif(sys.version_info > (3, 12), reason="for more latest python version has been other test") def test_argparser_register_args_py312( mocker: MockerFixture, value_arg: ValueArgument, bool_arg: BooleanArgument ) -> None: mock_add_argument: MockType = mocker.patch("argparse.ArgumentParser.add_argument") parser: ArgParser = ArgParser(processed_args=[value_arg, bool_arg]) expected_calls: list[call] = [ call( value_arg.string_entity, action=value_arg.action, help=value_arg.help, default=value_arg.default, choices=value_arg.possible_values, required=value_arg.is_required, ), call( bool_arg.string_entity, action=bool_arg.action, help=bool_arg.help, ), ] mock_add_argument.assert_has_calls(expected_calls, any_order=True) def test_argparser_parse_args_populates_argspace( mocker: MockerFixture, processed_args: list[ValueArgument | BooleanArgument] ) -> None: mock_namespace: Namespace = Namespace(config='config.json', debug=True) mocker.patch('argparse.ArgumentParser.parse_args', return_value=mock_namespace) parser: ArgParser = ArgParser(processed_args=processed_args) parser._parse_args() arg_space: ArgSpace = parser.parsed_argspace assert isinstance(arg_space, ArgSpace) assert len(arg_space.all_arguments) == 2 config_arg: InputArgument | None = arg_space.get_by_name('config') debug_arg: InputArgument | None = arg_space.get_by_name('debug') assert config_arg is not None assert config_arg.value == 'config.json' assert config_arg.founder_class is ValueArgument assert debug_arg is not None assert debug_arg.value is True assert debug_arg.founder_class is BooleanArgument