from argparse import Namespace import sys from unittest.mock import call import pytest from argenta.orchestrator.argparser.arguments.models import (BaseArgument, BooleanArgument, InputArgument, ValueArgument) from argenta.orchestrator.argparser.entity import ArgParser, ArgSpace class TestArgumentCreation: """Tests for the creation and attribute validation of argument model classes.""" def test_value_argument_creation(self): """Ensures ValueArgument instances are created with correct attributes.""" arg = 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(self): """Ensures BooleanArgument instances are created with correct attributes.""" arg = 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(self): """Ensures InputArgument instances are created with correct attributes.""" arg = 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 class TestArgSpace: """Tests for the ArgSpace class, which holds parsed argument values.""" @pytest.fixture def mock_arguments(self) -> list[InputArgument]: """Provides a list of mock InputArgument objects for testing.""" 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(self, mock_arguments: list[InputArgument]) -> ArgSpace: """Provides a pre-populated ArgSpace instance.""" return ArgSpace(all_arguments=mock_arguments) def test_initialization(self, arg_space: ArgSpace, mock_arguments: list[InputArgument]): """Tests if ArgSpace is initialized correctly with a list of arguments.""" assert len(arg_space.all_arguments) == 3 assert arg_space.all_arguments == mock_arguments def test_get_by_name(self, arg_space: ArgSpace, mock_arguments: list[InputArgument]): """Tests retrieving an argument by its name.""" found_arg = arg_space.get_by_name("arg1") assert found_arg is not None assert found_arg == mock_arguments[0] def test_get_by_name_not_found(self, arg_space: ArgSpace): """Tests that get_by_name returns None for a non-existent argument.""" found_arg = arg_space.get_by_name("non_existent_arg") assert found_arg is None def test_get_by_type(self, arg_space: ArgSpace, mock_arguments: list[InputArgument]): """Tests retrieving arguments based on their founder class type.""" value_args = 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 = arg_space.get_by_type(BooleanArgument) assert len(bool_args) == 1 assert mock_arguments[1] in bool_args def test_get_by_type_not_found(self, arg_space: ArgSpace): """Tests that get_by_type returns an empty list for an unused argument type.""" class OtherArgument(BaseArgument): pass other_args = arg_space.get_by_type(OtherArgument) assert other_args == [] def test_from_namespace(self): """Tests the class method for creating an ArgSpace from an argparse.Namespace.""" namespace = Namespace(config="config.json", debug=True, verbose=False) processed_args = [ ValueArgument(name="config", prefix="--"), BooleanArgument(name="debug", prefix="-"), BooleanArgument(name="verbose", prefix="-"), ] arg_space = ArgSpace.from_namespace(namespace, processed_args) assert len(arg_space.all_arguments) == 3 config_arg = arg_space.get_by_name('config') debug_arg = 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 class TestArgParser: """Tests for the ArgParser class, which orchestrates argument parsing.""" @pytest.fixture def value_arg(self) -> ValueArgument: """Provides a sample 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(self) -> BooleanArgument: """Provides a sample BooleanArgument.""" return BooleanArgument(name="debug", help="Enable debug mode") @pytest.fixture def processed_args(self, value_arg: ValueArgument, bool_arg: BooleanArgument) -> list: """Provides a list of processed arguments.""" return [value_arg, bool_arg] def test_initialization(self, processed_args: list): """Tests that the ArgParser constructor correctly assigns attributes.""" parser = 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_register_args(self, mocker, value_arg: ValueArgument, bool_arg: BooleanArgument): """Tests that arguments are correctly registered with the underlying ArgumentParser.""" mock_add_argument = mocker.patch("argparse.ArgumentParser.add_argument") parser = ArgParser(processed_args=[value_arg, bool_arg]) expected_calls = [ # Call for the ValueArgument 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 for the BooleanArgument 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_register_args(self, mocker, value_arg: ValueArgument, bool_arg: BooleanArgument): """Tests that arguments are correctly registered with the underlying ArgumentParser.""" mock_add_argument = mocker.patch("argparse.ArgumentParser.add_argument") parser = ArgParser(processed_args=[value_arg, bool_arg]) expected_calls = [ # Call for the ValueArgument 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 for the BooleanArgument 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_parse_args_populates_argspace(self, mocker, processed_args: list[ValueArgument | BooleanArgument]): """Tests that _parse_args correctly calls the parser and populates the ArgSpace.""" # 1. Mock the return value of the internal argparse instance mock_namespace = Namespace(config='config.json', debug=True) mocker.patch( 'argparse.ArgumentParser.parse_args', return_value=mock_namespace ) # 2. Initialize the parser and call the method under test parser = ArgParser(processed_args=processed_args) parser._parse_args() # Test the private method that contains the logic # 3. Assert the results arg_space = parser.parsed_argspace assert isinstance(arg_space, ArgSpace) assert len(arg_space.all_arguments) == 2 config_arg = arg_space.get_by_name('config') debug_arg = 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