refactor tests and add new

This commit is contained in:
2025-12-06 09:26:53 +03:00
parent 2423f57000
commit a2ef2652ed
5 changed files with 605 additions and 628 deletions
+91 -93
View File
@@ -1,22 +1,25 @@
from argparse import Namespace
import sys import sys
from argparse import Namespace
from typing import TYPE_CHECKING
from unittest.mock import call from unittest.mock import call
import pytest import pytest
from pytest_mock import MockerFixture
from argenta.orchestrator.argparser.arguments.models import (BaseArgument, from argenta.orchestrator.argparser.arguments.models import (
BaseArgument,
BooleanArgument, BooleanArgument,
InputArgument, InputArgument,
ValueArgument) ValueArgument,
)
from argenta.orchestrator.argparser.entity import ArgParser, ArgSpace from argenta.orchestrator.argparser.entity import ArgParser, ArgSpace
if TYPE_CHECKING:
from pytest_mock.plugin import MockType
class TestArgumentCreation:
"""Tests for the creation and attribute validation of argument model classes."""
def test_value_argument_creation(self): def test_value_argument_creation() -> None:
"""Ensures ValueArgument instances are created with correct attributes.""" arg: ValueArgument = ValueArgument(
arg = ValueArgument(
name="test_arg", name="test_arg",
prefix="--", prefix="--",
help="A test argument.", help="A test argument.",
@@ -35,9 +38,9 @@ class TestArgumentCreation:
assert arg.action == "store" assert arg.action == "store"
assert arg.string_entity == "--test_arg" assert arg.string_entity == "--test_arg"
def test_boolean_argument_creation(self):
"""Ensures BooleanArgument instances are created with correct attributes.""" def test_boolean_argument_creation() -> None:
arg = BooleanArgument( arg: BooleanArgument = BooleanArgument(
name="verbose", prefix="-", help="Enable verbose mode.", is_deprecated=True name="verbose", prefix="-", help="Enable verbose mode.", is_deprecated=True
) )
assert arg.name == "verbose" assert arg.name == "verbose"
@@ -47,9 +50,9 @@ class TestArgumentCreation:
assert arg.action == "store_true" assert arg.action == "store_true"
assert arg.string_entity == "-verbose" assert arg.string_entity == "-verbose"
def test_input_argument_creation(self):
"""Ensures InputArgument instances are created with correct attributes.""" def test_input_argument_creation() -> None:
arg = InputArgument( arg: InputArgument = InputArgument(
name="file", value="/path/to/file", founder_class=ValueArgument name="file", value="/path/to/file", founder_class=ValueArgument
) )
assert arg.name == "file" assert arg.name == "file"
@@ -57,71 +60,68 @@ class TestArgumentCreation:
assert arg.founder_class is ValueArgument assert arg.founder_class is ValueArgument
class TestArgSpace:
"""Tests for the ArgSpace class, which holds parsed argument values."""
@pytest.fixture @pytest.fixture
def mock_arguments(self) -> list[InputArgument]: def mock_arguments() -> list[InputArgument]:
"""Provides a list of mock InputArgument objects for testing."""
return [ return [
InputArgument(name="arg1", value="val1", founder_class=ValueArgument), InputArgument(name="arg1", value="val1", founder_class=ValueArgument),
InputArgument(name="arg2", value=True, founder_class=BooleanArgument), InputArgument(name="arg2", value=True, founder_class=BooleanArgument),
InputArgument(name="arg3", value="val3", founder_class=ValueArgument), InputArgument(name="arg3", value="val3", founder_class=ValueArgument),
] ]
@pytest.fixture @pytest.fixture
def arg_space(self, mock_arguments: list[InputArgument]) -> ArgSpace: def arg_space(mock_arguments: list[InputArgument]) -> ArgSpace:
"""Provides a pre-populated ArgSpace instance."""
return ArgSpace(all_arguments=mock_arguments) 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.""" def test_argspace_initialization(arg_space: ArgSpace, mock_arguments: list[InputArgument]) -> None:
assert len(arg_space.all_arguments) == 3 assert len(arg_space.all_arguments) == 3
assert arg_space.all_arguments == mock_arguments 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.""" def test_argspace_get_by_name(arg_space: ArgSpace, mock_arguments: list[InputArgument]) -> None:
found_arg = arg_space.get_by_name("arg1") found_arg: InputArgument | None = arg_space.get_by_name("arg1")
assert found_arg is not None assert found_arg is not None
assert found_arg == mock_arguments[0] 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.""" def test_argspace_get_by_name_not_found(arg_space: ArgSpace) -> None:
found_arg = arg_space.get_by_name("non_existent_arg") found_arg: InputArgument | None = arg_space.get_by_name("non_existent_arg")
assert found_arg is None 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.""" def test_argspace_get_by_type(arg_space: ArgSpace, mock_arguments: list[InputArgument]) -> None:
value_args = arg_space.get_by_type(ValueArgument) value_args: list[InputArgument] = arg_space.get_by_type(ValueArgument)
assert len(value_args) == 2 assert len(value_args) == 2
assert mock_arguments[0] in value_args assert mock_arguments[0] in value_args
assert mock_arguments[2] in value_args assert mock_arguments[2] in value_args
bool_args = arg_space.get_by_type(BooleanArgument) bool_args: list[InputArgument] = arg_space.get_by_type(BooleanArgument)
assert len(bool_args) == 1 assert len(bool_args) == 1
assert mock_arguments[1] in bool_args 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.""" def test_argspace_get_by_type_not_found(arg_space: ArgSpace) -> None:
class OtherArgument(BaseArgument): class OtherArgument(BaseArgument):
pass pass
other_args = arg_space.get_by_type(OtherArgument)
other_args: list[InputArgument] = arg_space.get_by_type(OtherArgument) # pyright: ignore[reportAssignmentType]
assert other_args == [] assert other_args == []
def test_from_namespace(self):
"""Tests the class method for creating an ArgSpace from an argparse.Namespace.""" def test_argspace_from_namespace() -> None:
namespace = Namespace(config="config.json", debug=True, verbose=False) namespace: Namespace = Namespace(config="config.json", debug=True, verbose=False)
processed_args = [ processed_args: list[ValueArgument | BooleanArgument] = [
ValueArgument(name="config", prefix="--"), ValueArgument(name="config", prefix="--"),
BooleanArgument(name="debug", prefix="-"), BooleanArgument(name="debug", prefix="-"),
BooleanArgument(name="verbose", prefix="-"), BooleanArgument(name="verbose", prefix="-"),
] ]
arg_space = ArgSpace.from_namespace(namespace, processed_args) arg_space: ArgSpace = ArgSpace.from_namespace(namespace, processed_args)
assert len(arg_space.all_arguments) == 3 assert len(arg_space.all_arguments) == 3
config_arg = arg_space.get_by_name('config') config_arg: InputArgument | None = arg_space.get_by_name('config')
debug_arg = arg_space.get_by_name('debug') debug_arg: InputArgument | None = arg_space.get_by_name('debug')
assert config_arg is not None assert config_arg is not None
assert config_arg.value == "config.json" assert config_arg.value == "config.json"
@@ -132,27 +132,29 @@ class TestArgSpace:
assert debug_arg.founder_class is BooleanArgument assert debug_arg.founder_class is BooleanArgument
class TestArgParser: @pytest.fixture
"""Tests for the ArgParser class, which orchestrates argument parsing.""" 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 @pytest.fixture
def value_arg(self) -> ValueArgument: def bool_arg() -> BooleanArgument:
"""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") return BooleanArgument(name="debug", help="Enable debug mode")
@pytest.fixture @pytest.fixture
def processed_args(self, value_arg: ValueArgument, bool_arg: BooleanArgument) -> list: def processed_args(value_arg: ValueArgument, bool_arg: BooleanArgument) -> list[ValueArgument | BooleanArgument]:
"""Provides a list of processed arguments."""
return [value_arg, bool_arg] return [value_arg, bool_arg]
def test_initialization(self, processed_args: list):
"""Tests that the ArgParser constructor correctly assigns attributes.""" def test_argparser_initialization(processed_args: list[ValueArgument | BooleanArgument]) -> None:
parser = ArgParser( parser: ArgParser = ArgParser(
processed_args=processed_args, processed_args=processed_args,
name="TestApp", name="TestApp",
description="A test application.", description="A test application.",
@@ -165,15 +167,16 @@ class TestArgParser:
assert isinstance(parser.parsed_argspace, ArgSpace) assert isinstance(parser.parsed_argspace, ArgSpace)
assert parser.parsed_argspace.all_arguments == [] assert parser.parsed_argspace.all_arguments == []
@pytest.mark.skipif(sys.version_info < (3, 13), reason="requires python3.13 or higher") @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): def test_argparser_register_args_py313(
"""Tests that arguments are correctly registered with the underlying ArgumentParser.""" mocker: MockerFixture, value_arg: ValueArgument, bool_arg: BooleanArgument
mock_add_argument = mocker.patch("argparse.ArgumentParser.add_argument") ) -> None:
mock_add_argument: MockType = mocker.patch("argparse.ArgumentParser.add_argument")
parser = ArgParser(processed_args=[value_arg, bool_arg]) parser: ArgParser = ArgParser(processed_args=[value_arg, bool_arg]) # pyright: ignore[reportUnusedVariable]
expected_calls = [ expected_calls: list[call] = [
# Call for the ValueArgument
call( call(
value_arg.string_entity, value_arg.string_entity,
action=value_arg.action, action=value_arg.action,
@@ -181,64 +184,59 @@ class TestArgParser:
default=value_arg.default, default=value_arg.default,
choices=value_arg.possible_values, choices=value_arg.possible_values,
required=value_arg.is_required, required=value_arg.is_required,
deprecated=value_arg.is_deprecated deprecated=value_arg.is_deprecated,
), ),
# Call for the BooleanArgument
call( call(
bool_arg.string_entity, bool_arg.string_entity,
action=bool_arg.action, action=bool_arg.action,
help=bool_arg.help, help=bool_arg.help,
deprecated=bool_arg.is_deprecated deprecated=bool_arg.is_deprecated,
) ),
] ]
mock_add_argument.assert_has_calls(expected_calls, any_order=True) 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]) @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")
expected_calls = [ parser: ArgParser = ArgParser(processed_args=[value_arg, bool_arg])
# Call for the ValueArgument
expected_calls: list[call] = [
call( call(
value_arg.string_entity, value_arg.string_entity,
action=value_arg.action, action=value_arg.action,
help=value_arg.help, help=value_arg.help,
default=value_arg.default, default=value_arg.default,
choices=value_arg.possible_values, choices=value_arg.possible_values,
required=value_arg.is_required required=value_arg.is_required,
), ),
# Call for the BooleanArgument
call( call(
bool_arg.string_entity, bool_arg.string_entity,
action=bool_arg.action, action=bool_arg.action,
help=bool_arg.help help=bool_arg.help,
) ),
] ]
mock_add_argument.assert_has_calls(expected_calls, any_order=True) 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 def test_argparser_parse_args_populates_argspace(
parser = ArgParser(processed_args=processed_args) mocker: MockerFixture, processed_args: list[ValueArgument | BooleanArgument]
parser._parse_args() # Test the private method that contains the logic ) -> None:
mock_namespace: Namespace = Namespace(config='config.json', debug=True)
mocker.patch('argparse.ArgumentParser.parse_args', return_value=mock_namespace)
# 3. Assert the results parser: ArgParser = ArgParser(processed_args=processed_args)
arg_space = parser.parsed_argspace parser._parse_args()
arg_space: ArgSpace = parser.parsed_argspace
assert isinstance(arg_space, ArgSpace) assert isinstance(arg_space, ArgSpace)
assert len(arg_space.all_arguments) == 2 assert len(arg_space.all_arguments) == 2
config_arg = arg_space.get_by_name('config') config_arg: InputArgument | None = arg_space.get_by_name('config')
debug_arg = arg_space.get_by_name('debug') debug_arg: InputArgument | None = arg_space.get_by_name('debug')
assert config_arg is not None assert config_arg is not None
assert config_arg.value == 'config.json' assert config_arg.value == 'config.json'
+72 -97
View File
@@ -1,25 +1,25 @@
import os import os
from unittest.mock import MagicMock, call, patch
import pytest import pytest
from pyfakefs.fake_filesystem import FakeFilesystem
from pytest_mock import MockerFixture
from pytest_mock.plugin import MockType
from argenta.app.autocompleter.entity import (
AutoCompleter,
_get_history_items,
_is_command_exist,
)
# Since readline is not available on all platforms (e.g., Windows) for testing,
# it is mocked for all tests.
readline_mock = MagicMock()
# We patch the module where it's imported, not where it's defined.
@pytest.fixture @pytest.fixture
def mock_readline(): def mock_readline(mocker: MockerFixture) -> MockType:
"""Fixture to provide a mock of the `readline` module.""" _history: list[str] = []
with patch('argenta.app.autocompleter.entity.readline', readline_mock) as mock:
# This nested state simulates readline's internal history list.
_history = []
def add_history(item: str) -> None: def add_history(item: str) -> None:
_history.append(item) _history.append(item)
def get_history_item(index: int) -> str | None: def get_history_item(index: int) -> str | None:
# readline history is 1-based.
if 1 <= index <= len(_history): if 1 <= index <= len(_history):
return _history[index - 1] return _history[index - 1]
return None return None
@@ -30,166 +30,141 @@ def mock_readline():
def clear_history() -> None: def clear_history() -> None:
_history.clear() _history.clear()
# Reset all mocks and the internal history before each test. mock: MockType = mocker.MagicMock()
mocker.patch('argenta.app.autocompleter.entity.readline', mock)
mock.reset_mock() mock.reset_mock()
clear_history() clear_history()
# Apply side effects to mock functions to simulate real behavior.
mock.add_history.side_effect = add_history mock.add_history.side_effect = add_history
mock.get_history_item.side_effect = get_history_item mock.get_history_item.side_effect = get_history_item
mock.get_current_history_length.side_effect = get_current_history_length mock.get_current_history_length.side_effect = get_current_history_length
# Provide a default return value for functions that are read from.
mock.get_completer_delims.return_value = " " mock.get_completer_delims.return_value = " "
yield mock return mock
# We import the class under test after setting up the patch context if needed,
# or ensure patches target the correct import location.
from argenta.app.autocompleter.entity import (AutoCompleter,
_get_history_items,
_is_command_exist)
class TestAutoCompleter: HISTORY_FILE: str = "test_history.txt"
"""Test suite for the AutoCompleter class.""" COMMANDS: list[str] = ["start", "stop", "status"]
HISTORY_FILE = "test_history.txt"
COMMANDS = ["start", "stop", "status"]
def test_initialization(self):
"""Tests that the constructor correctly assigns attributes.""" def test_initialization() -> None:
completer = AutoCompleter(history_filename=self.HISTORY_FILE, autocomplete_button="tab") completer: AutoCompleter = AutoCompleter(history_filename=HISTORY_FILE, autocomplete_button="tab")
assert completer.history_filename == self.HISTORY_FILE assert completer.history_filename == HISTORY_FILE
assert completer.autocomplete_button == "tab" assert completer.autocomplete_button == "tab"
def test_initial_setup_if_history_file_does_not_exist(self, fs, mock_readline):
"""Tests initial setup creates history from commands when the history file is absent."""
# Ensure the file does not exist in the fake filesystem.
if os.path.exists(self.HISTORY_FILE):
os.remove(self.HISTORY_FILE)
completer = AutoCompleter(history_filename=self.HISTORY_FILE) def test_initial_setup_if_history_file_does_not_exist(fs: FakeFilesystem, mock_readline: MockType) -> None:
completer.initial_setup(self.COMMANDS) if os.path.exists(HISTORY_FILE):
os.remove(HISTORY_FILE)
completer: AutoCompleter = AutoCompleter(history_filename=HISTORY_FILE)
completer.initial_setup(COMMANDS)
mock_readline.read_history_file.assert_not_called() mock_readline.read_history_file.assert_not_called()
expected_calls = [call(cmd) for cmd in self.COMMANDS] assert mock_readline.add_history.call_count == len(COMMANDS)
mock_readline.add_history.assert_has_calls(expected_calls, any_order=True)
assert mock_readline.add_history.call_count == len(self.COMMANDS)
mock_readline.set_completer.assert_called_with(completer._complete) mock_readline.set_completer.assert_called_with(completer._complete)
mock_readline.parse_and_bind.assert_called_with("tab: complete") mock_readline.parse_and_bind.assert_called_with("tab: complete")
def test_initial_setup_if_history_file_exists(self, fs, mock_readline):
"""Tests initial setup reads from an existing history file."""
fs.create_file(self.HISTORY_FILE, contents="previous_command\n")
completer = AutoCompleter(history_filename=self.HISTORY_FILE) def test_initial_setup_if_history_file_exists(fs: FakeFilesystem, mock_readline: MockType) -> None:
completer.initial_setup(self.COMMANDS) fs.create_file(HISTORY_FILE, contents="previous_command\n")
mock_readline.read_history_file.assert_called_once_with(self.HISTORY_FILE) completer: AutoCompleter = AutoCompleter(history_filename=HISTORY_FILE)
completer.initial_setup(COMMANDS)
mock_readline.read_history_file.assert_called_once_with(HISTORY_FILE)
mock_readline.add_history.assert_not_called() mock_readline.add_history.assert_not_called()
mock_readline.set_completer.assert_called_once() mock_readline.set_completer.assert_called_once()
mock_readline.parse_and_bind.assert_called_once() mock_readline.parse_and_bind.assert_called_once()
def test_initial_setup_with_no_history_filename(self, mock_readline):
"""Tests initial setup when no history filename is provided.""" def test_initial_setup_with_no_history_filename(mock_readline: MockType) -> None:
completer = AutoCompleter(history_filename=None) completer: AutoCompleter = AutoCompleter(history_filename=None)
completer.initial_setup(self.COMMANDS) completer.initial_setup(COMMANDS)
mock_readline.read_history_file.assert_not_called() mock_readline.read_history_file.assert_not_called()
expected_calls = [call(cmd) for cmd in self.COMMANDS] assert mock_readline.add_history.call_count == len(COMMANDS)
mock_readline.add_history.assert_has_calls(expected_calls, any_order=True)
def test_exit_setup_writes_and_filters_history(self, fs, mock_readline):
"""Tests that exit_setup writes a filtered and unique history to the file.""" def test_exit_setup_writes_and_filters_history(fs: FakeFilesystem, mock_readline: MockType) -> None:
# 1. Populate the mock readline history. mock_readline.add_history.side_effect = None
mock_readline.add_history.side_effect(None) # Temporarily disable side effect to just record calls
mock_readline.add_history("start server") mock_readline.add_history("start server")
mock_readline.add_history("stop client") mock_readline.add_history("stop client")
mock_readline.add_history("invalid command") mock_readline.add_history("invalid command")
mock_readline.add_history("start server") # Add a duplicate. mock_readline.add_history("start server")
# 2. Simulate the state of the history file after readline.write_history_file would have run. raw_history_content: str = "\n".join(["start server", "stop client", "invalid command", "start server"])
raw_history_content = "\n".join(["start server", "stop client", "invalid command", "start server"]) fs.create_file(HISTORY_FILE, contents=raw_history_content)
fs.create_file(self.HISTORY_FILE, contents=raw_history_content)
# 3. Call the method under test. completer: AutoCompleter = AutoCompleter(history_filename=HISTORY_FILE)
completer = AutoCompleter(history_filename=self.HISTORY_FILE)
completer.exit_setup(all_commands=["start", "stop"], ignore_command_register=False) completer.exit_setup(all_commands=["start", "stop"], ignore_command_register=False)
# 4. Assert that readline's write function was called. mock_readline.write_history_file.assert_called_once_with(HISTORY_FILE)
mock_readline.write_history_file.assert_called_once_with(self.HISTORY_FILE)
# 5. Assert the file was correctly re-written with filtered and unique content. with open(HISTORY_FILE) as f:
with open(self.HISTORY_FILE, "r") as f: content: str = f.read()
content = f.read() lines: list[str] = sorted(content.strip().split("\n"))
lines = sorted(content.strip().split("\n"))
assert lines == ["start server", "stop client"] assert lines == ["start server", "stop client"]
def test_exit_setup_with_no_history_filename(self, mock_readline):
"""Tests that exit_setup does nothing if no filename is provided.""" def test_exit_setup_with_no_history_filename(mock_readline: MockType) -> None:
completer = AutoCompleter(history_filename=None) completer: AutoCompleter = AutoCompleter(history_filename=None)
completer.exit_setup(all_commands=self.COMMANDS, ignore_command_register=False) completer.exit_setup(all_commands=COMMANDS, ignore_command_register=False)
mock_readline.write_history_file.assert_not_called() mock_readline.write_history_file.assert_not_called()
def test_complete_with_no_matches(self, mock_readline):
"""Tests the _complete method when there are no matching history items.""" def test_complete_with_no_matches(mock_readline: MockType) -> None:
cmd: str
for cmd in ["start", "stop"]: for cmd in ["start", "stop"]:
mock_readline.add_history(cmd) mock_readline.add_history(cmd)
completer = AutoCompleter() completer: AutoCompleter = AutoCompleter()
assert completer._complete("run", 0) is None assert completer._complete("run", 0) is None
assert completer._complete("run", 1) is None assert completer._complete("run", 1) is None
def test_complete_with_one_match(self, mock_readline):
"""Tests the _complete method when there is exactly one match.""" def test_complete_with_one_match(mock_readline: MockType) -> None:
mock_readline.add_history("start server") mock_readline.add_history("start server")
mock_readline.add_history("stop server") mock_readline.add_history("stop server")
completer = AutoCompleter() completer: AutoCompleter = AutoCompleter()
assert completer._complete("start", 0) == "start server" assert completer._complete("start", 0) == "start server"
assert completer._complete("start", 1) is None # Subsequent states yield no matches assert completer._complete("start", 1) is None
def test_complete_with_multiple_matches(self, mock_readline):
"""Tests _complete with multiple matches that share a common prefix.""" def test_complete_with_multiple_matches(mock_readline: MockType) -> None:
mock_readline.add_history("status client") mock_readline.add_history("status client")
mock_readline.add_history("status server") mock_readline.add_history("status server")
mock_readline.add_history("stop") mock_readline.add_history("stop")
completer = AutoCompleter() completer: AutoCompleter = AutoCompleter()
# On state 0, it should insert the common prefix via readline and return None. result: str | None = completer._complete("stat", 0)
result = completer._complete("stat", 0)
assert result is None assert result is None
mock_readline.insert_text.assert_called_once_with("us ") # Completes "stat" to "status " mock_readline.insert_text.assert_called_once_with("us ")
mock_readline.redisplay.assert_called_once() mock_readline.redisplay.assert_called_once()
# On subsequent states, it should do nothing.
mock_readline.reset_mock() mock_readline.reset_mock()
result_state_1 = completer._complete("stat", 1) result_state_1: str | None = completer._complete("stat", 1)
assert result_state_1 is None assert result_state_1 is None
mock_readline.insert_text.assert_not_called() mock_readline.insert_text.assert_not_called()
class TestHelperFunctions: def test_is_command_exist() -> None:
"""Test suite for helper functions in the autocompleter module.""" existing: list[str] = ["start", "stop", "status"]
def test_is_command_exist(self):
"""Tests the _is_command_exist helper function."""
existing = ["start", "stop", "status"]
# Case-sensitive check
assert _is_command_exist("start", existing, ignore_command_register=False) is True assert _is_command_exist("start", existing, ignore_command_register=False) is True
assert _is_command_exist("START", existing, ignore_command_register=False) is False assert _is_command_exist("START", existing, ignore_command_register=False) is False
assert _is_command_exist("unknown", existing, ignore_command_register=False) is False assert _is_command_exist("unknown", existing, ignore_command_register=False) is False
# Case-insensitive check
assert _is_command_exist("start", existing, ignore_command_register=True) is True assert _is_command_exist("start", existing, ignore_command_register=True) is True
assert _is_command_exist("START", existing, ignore_command_register=True) is True assert _is_command_exist("START", existing, ignore_command_register=True) is True
assert _is_command_exist("unknown", existing, ignore_command_register=True) is False assert _is_command_exist("unknown", existing, ignore_command_register=True) is False
def test_get_history_items(self, mock_readline):
"""Tests the _get_history_items helper function.""" def test_get_history_items(mock_readline: MockType) -> None:
assert _get_history_items() == [] assert _get_history_items() == []
mock_readline.add_history("first item") mock_readline.add_history("first item")
+40 -28
View File
@@ -1,5 +1,6 @@
import re import re
import unittest
import pytest
from argenta.command.exceptions import (EmptyInputCommandException, from argenta.command.exceptions import (EmptyInputCommandException,
RepeatedInputFlagsException, RepeatedInputFlagsException,
@@ -10,54 +11,65 @@ from argenta.command.flag.models import PossibleValues, ValidationStatus
from argenta.command.models import Command, InputCommand from argenta.command.models import Command, InputCommand
class TestInputCommand(unittest.TestCase): def test_parse_correct_raw_command():
def test_parse_correct_raw_command(self): assert InputCommand.parse('ssh --host 192.168.0.3').trigger == 'ssh'
self.assertEqual(InputCommand.parse('ssh --host 192.168.0.3').trigger, 'ssh')
def test_parse_raw_command_without_flag_name_with_value(self):
with self.assertRaises(UnprocessedInputFlagException): def test_parse_raw_command_without_flag_name_with_value():
with pytest.raises(UnprocessedInputFlagException):
InputCommand.parse('ssh 192.168.0.3') InputCommand.parse('ssh 192.168.0.3')
def test_parse_raw_command_with_repeated_flag_name(self):
with self.assertRaises(RepeatedInputFlagsException): def test_parse_raw_command_with_repeated_flag_name():
with pytest.raises(RepeatedInputFlagsException):
InputCommand.parse('ssh --host 192.168.0.3 --host 172.198.0.43') InputCommand.parse('ssh --host 192.168.0.3 --host 172.198.0.43')
def test_parse_empty_raw_command(self):
with self.assertRaises(EmptyInputCommandException): def test_parse_empty_raw_command():
with pytest.raises(EmptyInputCommandException):
InputCommand.parse('') InputCommand.parse('')
def test_validate_invalid_input_flag1(self):
def test_validate_invalid_input_flag1():
command = Command('some', flags=Flag('test')) command = Command('some', flags=Flag('test'))
self.assertEqual(command.validate_input_flag(InputFlag('test', input_value='', status=None)), ValidationStatus.INVALID) assert command.validate_input_flag(InputFlag('test', input_value='', status=None)) == ValidationStatus.INVALID
def test_validate_valid_input_flag2(self):
def test_validate_valid_input_flag2():
command = Command('some', flags=Flags([Flag('test'), Flag('more')])) command = Command('some', flags=Flags([Flag('test'), Flag('more')]))
self.assertEqual(command.validate_input_flag(InputFlag('more', input_value='random-value', status=None)), ValidationStatus.VALID) assert command.validate_input_flag(InputFlag('more', input_value='random-value', status=None)) == ValidationStatus.VALID
def test_validate_undefined_input_flag1(self):
def test_validate_undefined_input_flag1():
command = Command('some', flags=Flag('test')) command = Command('some', flags=Flag('test'))
self.assertEqual(command.validate_input_flag(InputFlag('more', input_value='', status=None)), ValidationStatus.UNDEFINED) assert command.validate_input_flag(InputFlag('more', input_value='', status=None)) == ValidationStatus.UNDEFINED
def test_validate_undefined_input_flag2(self):
def test_validate_undefined_input_flag2():
command = Command('some', flags=Flags([Flag('test'), Flag('more')])) command = Command('some', flags=Flags([Flag('test'), Flag('more')]))
self.assertEqual(command.validate_input_flag(InputFlag('case', input_value='', status=None)), ValidationStatus.UNDEFINED) assert command.validate_input_flag(InputFlag('case', input_value='', status=None)) == ValidationStatus.UNDEFINED
def test_validate_undefined_input_flag3(self):
def test_validate_undefined_input_flag3():
command = Command('some') command = Command('some')
self.assertEqual(command.validate_input_flag(InputFlag('case', input_value='', status=None)), ValidationStatus.UNDEFINED) assert command.validate_input_flag(InputFlag('case', input_value='', status=None)) == ValidationStatus.UNDEFINED
def test_invalid_input_flag1(self):
def test_invalid_input_flag1():
command = Command('some', flags=Flag('test', possible_values=PossibleValues.NEITHER)) command = Command('some', flags=Flag('test', possible_values=PossibleValues.NEITHER))
self.assertEqual(command.validate_input_flag(InputFlag('test', input_value='example', status=None)), ValidationStatus.INVALID) assert command.validate_input_flag(InputFlag('test', input_value='example', status=None)) == ValidationStatus.INVALID
def test_invalid_input_flag2(self):
def test_invalid_input_flag2():
command = Command('some', flags=Flag('test', possible_values=['some', 'case'])) command = Command('some', flags=Flag('test', possible_values=['some', 'case']))
self.assertEqual(command.validate_input_flag(InputFlag('test', input_value='slay', status=None)), ValidationStatus.INVALID) assert command.validate_input_flag(InputFlag('test', input_value='slay', status=None)) == ValidationStatus.INVALID
def test_invalid_input_flag3(self):
def test_invalid_input_flag3():
command = Command('some', flags=Flag('test', possible_values=re.compile(r'^ex\d{, 2}op$'))) command = Command('some', flags=Flag('test', possible_values=re.compile(r'^ex\d{, 2}op$')))
self.assertEqual(command.validate_input_flag(InputFlag('test', input_value='example', status=None)), ValidationStatus.INVALID) assert command.validate_input_flag(InputFlag('test', input_value='example', status=None)) == ValidationStatus.INVALID
def test_isinstance_parse_correct_raw_command(self):
def test_isinstance_parse_correct_raw_command():
cmd = InputCommand.parse('ssh --host 192.168.0.3') cmd = InputCommand.parse('ssh --host 192.168.0.3')
self.assertIsInstance(cmd, InputCommand) assert isinstance(cmd, InputCommand)
+62 -75
View File
@@ -1,91 +1,99 @@
import re import re
import unittest
from argenta.command.flag import Flag, InputFlag, PossibleValues from argenta.command.flag import Flag, InputFlag, PossibleValues
from argenta.command.flag.flags import Flags, InputFlags from argenta.command.flag.flags import Flags, InputFlags
class TestFlag(unittest.TestCase): def test_get_string_entity():
def test_get_string_entity(self): assert Flag(name='test').string_entity == '--test'
self.assertEqual(Flag(name='test').string_entity,
'--test')
def test_get_string_entity2(self):
self.assertEqual(Flag(name='test',
prefix='---').string_entity,
'---test')
def test_get_flag_name(self): def test_get_string_entity2():
self.assertEqual(Flag(name='test').name, assert Flag(name='test', prefix='---').string_entity == '---test'
'test')
def test_get_flag_prefix(self):
self.assertEqual(Flag(name='test').prefix,
'--')
def test_get_flag_prefix2(self): def test_get_flag_name():
self.assertEqual(Flag(name='test', assert Flag(name='test').name == 'test'
prefix='--').prefix,
'--')
def test_get_flag_value_without_set(self):
self.assertEqual(InputFlag(name='test', input_value='', status=None).input_value,
'')
def test_get_flag_value_with_set(self): def test_get_flag_prefix():
assert Flag(name='test').prefix == '--'
def test_get_flag_prefix2():
assert Flag(name='test', prefix='--').prefix == '--'
def test_get_flag_value_without_set():
assert InputFlag(name='test', input_value='', status=None).input_value == ''
def test_get_flag_value_with_set():
flag = InputFlag(name='test', input_value='example', status=None) flag = InputFlag(name='test', input_value='example', status=None)
self.assertEqual(flag.input_value, 'example') assert flag.input_value == 'example'
def test_validate_incorrect_flag_value_with_list_of_possible_flag_values(self):
def test_validate_incorrect_flag_value_with_list_of_possible_flag_values():
flag = Flag(name='test', possible_values=['1', '2', '3']) flag = Flag(name='test', possible_values=['1', '2', '3'])
self.assertEqual(flag.validate_input_flag_value('bad value'), False) assert flag.validate_input_flag_value('bad value') is False
def test_validate_correct_flag_value_with_list_of_possible_flag_values(self):
def test_validate_correct_flag_value_with_list_of_possible_flag_values():
flag = Flag(name='test', possible_values=['1', '2', '3']) flag = Flag(name='test', possible_values=['1', '2', '3'])
self.assertEqual(flag.validate_input_flag_value('1'), True) assert flag.validate_input_flag_value('1') is True
def test_validate_incorrect_flag_value_with_pattern_of_possible_flag_values(self):
def test_validate_incorrect_flag_value_with_pattern_of_possible_flag_values():
flag = Flag(name='test', possible_values=re.compile(r'192.168.\d+.\d+')) flag = Flag(name='test', possible_values=re.compile(r'192.168.\d+.\d+'))
self.assertEqual(flag.validate_input_flag_value('152.123.9.8'), False) assert flag.validate_input_flag_value('152.123.9.8') is False
def test_validate_correct_flag_value_with_pattern_of_possible_flag_values(self):
def test_validate_correct_flag_value_with_pattern_of_possible_flag_values():
flag = Flag(name='test', possible_values=re.compile(r'192.168.\d+.\d+')) flag = Flag(name='test', possible_values=re.compile(r'192.168.\d+.\d+'))
self.assertEqual(flag.validate_input_flag_value('192.168.9.8'), True) assert flag.validate_input_flag_value('192.168.9.8') is True
def test_validate_correct_empty_flag_value_without_possible_flag_values(self):
def test_validate_correct_empty_flag_value_without_possible_flag_values():
flag = Flag(name='test', possible_values=PossibleValues.NEITHER) flag = Flag(name='test', possible_values=PossibleValues.NEITHER)
self.assertEqual(flag.validate_input_flag_value(''), True) assert flag.validate_input_flag_value('') is True
def test_validate_correct_empty_flag_value_with_possible_flag_values(self):
def test_validate_correct_empty_flag_value_with_possible_flag_values():
flag = Flag(name='test', possible_values=PossibleValues.NEITHER) flag = Flag(name='test', possible_values=PossibleValues.NEITHER)
self.assertEqual(flag.validate_input_flag_value(''), True) assert flag.validate_input_flag_value('') is True
def test_validate_incorrect_random_flag_value_without_possible_flag_values(self):
def test_validate_incorrect_random_flag_value_without_possible_flag_values():
flag = Flag(name='test', possible_values=PossibleValues.NEITHER) flag = Flag(name='test', possible_values=PossibleValues.NEITHER)
self.assertEqual(flag.validate_input_flag_value('random value'), False) assert flag.validate_input_flag_value('random value') is False
def test_validate_correct_random_flag_value_with_possible_flag_values(self):
def test_validate_correct_random_flag_value_with_possible_flag_values():
flag = Flag(name='test', possible_values=PossibleValues.ALL) flag = Flag(name='test', possible_values=PossibleValues.ALL)
self.assertEqual(flag.validate_input_flag_value('random value'), True) assert flag.validate_input_flag_value('random value') is True
def test_get_input_flag1(self):
def test_get_input_flag1():
flag = InputFlag(name='test', input_value='', status=None) flag = InputFlag(name='test', input_value='', status=None)
input_flags = InputFlags([flag]) input_flags = InputFlags([flag])
self.assertEqual(input_flags.get_flag_by_name('test'), flag) assert input_flags.get_flag_by_name('test') == flag
def test_get_input_flag2(self):
def test_get_input_flag2():
flag = InputFlag(name='test', input_value='', status=None) flag = InputFlag(name='test', input_value='', status=None)
flag2 = InputFlag(name='some', input_value='', status=None) flag2 = InputFlag(name='some', input_value='', status=None)
input_flags = InputFlags([flag, flag2]) input_flags = InputFlags([flag, flag2])
self.assertEqual(input_flags.get_flag_by_name('some'), flag2) assert input_flags.get_flag_by_name('some') == flag2
def test_get_undefined_input_flag(self):
def test_get_undefined_input_flag():
flag = InputFlag(name='test', input_value='', status=None) flag = InputFlag(name='test', input_value='', status=None)
flag2 = InputFlag(name='some', input_value='', status=None) flag2 = InputFlag(name='some', input_value='', status=None)
input_flags = InputFlags([flag, flag2]) input_flags = InputFlags([flag, flag2])
self.assertEqual(input_flags.get_flag_by_name('case'), None) assert input_flags.get_flag_by_name('case') is None
def test_get_flags(self):
def test_get_flags():
flags = Flags() flags = Flags()
list_of_flags = [ list_of_flags = [
Flag('test1'), Flag('test1'),
@@ -93,37 +101,16 @@ class TestFlag(unittest.TestCase):
Flag('test3'), Flag('test3'),
] ]
flags.add_flags(list_of_flags) flags.add_flags(list_of_flags)
self.assertEqual(flags.flags, assert flags.flags == list_of_flags
list_of_flags)
def test_add_flag(self):
def test_add_flag():
flags = Flags() flags = Flags()
flags.add_flag(Flag('test')) flags.add_flag(Flag('test'))
self.assertEqual(len(flags.flags), 1) assert len(flags.flags) == 1
def test_add_flags(self):
def test_add_flags():
flags = Flags() flags = Flags()
flags.add_flags([Flag('test'), Flag('test2')]) flags.add_flags([Flag('test'), Flag('test2')])
self.assertEqual(len(flags.flags), 2) assert len(flags.flags) == 2
+56 -51
View File
@@ -1,6 +1,7 @@
import unittest
from datetime import date, datetime from datetime import date, datetime
import pytest
from argenta.data_bridge import DataBridge from argenta.data_bridge import DataBridge
from argenta.command.flag.models import InputFlag from argenta.command.flag.models import InputFlag
from argenta.command.flag.flags.models import InputFlags from argenta.command.flag.flags.models import InputFlags
@@ -8,84 +9,93 @@ from argenta.response.entity import EMPTY_INPUT_FLAGS, Response
from argenta.response.status import ResponseStatus from argenta.response.status import ResponseStatus
class TestDataBridge(unittest.TestCase): @pytest.fixture
def setUp(self): def data_bridge():
"""Create a new DataBridge instance for each test""" """Create a new DataBridge instance for each test"""
self.data_bridge = DataBridge() return DataBridge()
def test_update_data_basic(self):
def test_update_data_basic(data_bridge: DataBridge):
"""Test basic data update functionality""" """Test basic data update functionality"""
test_data = {"key1": "value1", "key2": "value2"} test_data = {"key1": "value1", "key2": "value2"}
self.data_bridge.update(test_data) data_bridge.update(test_data)
self.assertEqual(self.data_bridge.get_all(), test_data) assert data_bridge.get_all() == test_data
def test_update_data_with_datetime(self):
def test_update_data_with_datetime(data_bridge: DataBridge):
"""Test updating data with datetime objects""" """Test updating data with datetime objects"""
test_datetime = datetime(2024, 1, 15, 10, 30, 45) test_datetime = datetime(2024, 1, 15, 10, 30, 45)
test_data = {"created_at": test_datetime, "name": "test"} test_data = {"created_at": test_datetime, "name": "test"}
self.data_bridge.update(test_data) data_bridge.update(test_data)
result = self.data_bridge.get_all() result = data_bridge.get_all()
self.assertEqual(result["created_at"], test_datetime) assert result["created_at"] == test_datetime
self.assertEqual(result["name"], "test") assert result["name"] == "test"
def test_update_data_multiple_calls(self):
def test_update_data_multiple_calls(data_bridge: DataBridge):
"""Test multiple update calls merge data""" """Test multiple update calls merge data"""
first_data = {"key1": "value1"} first_data = {"key1": "value1"}
second_data = {"key2": "value2"} second_data = {"key2": "value2"}
self.data_bridge.update(first_data) data_bridge.update(first_data)
self.data_bridge.update(second_data) data_bridge.update(second_data)
self.assertEqual(len(self.data_bridge.get_all()), 2) assert len(data_bridge.get_all()) == 2
def test_get_data_empty(self):
def test_get_data_empty(data_bridge: DataBridge):
"""Test get_all returns empty dict when no data""" """Test get_all returns empty dict when no data"""
self.assertEqual(self.data_bridge.get_all(), {}) assert data_bridge.get_all() == {}
def test_clear_data(self):
def test_clear_data(data_bridge: DataBridge):
"""Test clear_all removes all data""" """Test clear_all removes all data"""
self.data_bridge.update({"key": "value"}) data_bridge.update({"key": "value"})
self.assertNotEqual(self.data_bridge.get_all(), {}) assert data_bridge.get_all() != {}
self.data_bridge.clear_all() data_bridge.clear_all()
self.assertEqual(self.data_bridge.get_all(), {}) assert data_bridge.get_all() == {}
def test_delete_from_data(self):
def test_delete_from_data(data_bridge: DataBridge):
"""Test delete_by_key removes specific key""" """Test delete_by_key removes specific key"""
test_data = {"key1": "value1", "key2": "value2"} test_data = {"key1": "value1", "key2": "value2"}
self.data_bridge.update(test_data) data_bridge.update(test_data)
self.data_bridge.delete_by_key("key1") data_bridge.delete_by_key("key1")
result = self.data_bridge.get_all() result = data_bridge.get_all()
self.assertNotIn("key1", result) assert "key1" not in result
self.assertIn("key2", result) assert "key2" in result
def test_delete_from_data_nonexistent_key(self):
def test_delete_from_data_nonexistent_key(data_bridge: DataBridge):
"""Test delete_by_key with nonexistent key raises KeyError""" """Test delete_by_key with nonexistent key raises KeyError"""
with self.assertRaises(KeyError): with pytest.raises(KeyError):
self.data_bridge.delete_by_key("nonexistent_key") data_bridge.delete_by_key("nonexistent_key")
def test_get_by_key(self):
def test_get_by_key(data_bridge: DataBridge):
"""Test get_by_key retrieves correct value""" """Test get_by_key retrieves correct value"""
test_data = {"key1": "value1", "key2": date(2024, 1, 1)} test_data = {"key1": "value1", "key2": date(2024, 1, 1)}
self.data_bridge.update(test_data) data_bridge.update(test_data)
self.assertEqual(self.data_bridge.get_by_key("key1"), "value1") assert data_bridge.get_by_key("key1") == "value1"
self.assertEqual(self.data_bridge.get_by_key("key2"), date(2024, 1, 1)) assert data_bridge.get_by_key("key2") == date(2024, 1, 1)
self.assertIsNone(self.data_bridge.get_by_key("nonexistent")) assert data_bridge.get_by_key("nonexistent") is None
class TestResponse(unittest.TestCase): def test_response_initialization_basic():
def test_response_initialization_basic(self):
"""Test basic Response initialization""" """Test basic Response initialization"""
response = Response(ResponseStatus.ALL_FLAGS_VALID) response = Response(ResponseStatus.ALL_FLAGS_VALID)
self.assertEqual(response.status, ResponseStatus.ALL_FLAGS_VALID) assert response.status == ResponseStatus.ALL_FLAGS_VALID
self.assertEqual(response.input_flags, EMPTY_INPUT_FLAGS) assert response.input_flags == EMPTY_INPUT_FLAGS
def test_response_initialization_with_flags(self):
def test_response_initialization_with_flags():
"""Test Response initialization with input flags""" """Test Response initialization with input flags"""
input_flags = InputFlags([InputFlag('test', input_value='value', status=None)]) input_flags = InputFlags([InputFlag('test', input_value='value', status=None)])
response = Response(ResponseStatus.INVALID_VALUE_FLAGS, input_flags) response = Response(ResponseStatus.INVALID_VALUE_FLAGS, input_flags)
self.assertEqual(response.status, ResponseStatus.INVALID_VALUE_FLAGS) assert response.status == ResponseStatus.INVALID_VALUE_FLAGS
self.assertEqual(response.input_flags, input_flags) assert response.input_flags == input_flags
def test_response_status_types(self):
def test_response_status_types():
"""Test Response with different status types""" """Test Response with different status types"""
statuses = [ statuses = [
ResponseStatus.ALL_FLAGS_VALID, ResponseStatus.ALL_FLAGS_VALID,
@@ -94,10 +104,5 @@ class TestResponse(unittest.TestCase):
ResponseStatus.UNDEFINED_AND_INVALID_FLAGS ResponseStatus.UNDEFINED_AND_INVALID_FLAGS
] ]
for status in statuses: for status in statuses:
with self.subTest(status=status):
response = Response(status) response = Response(status)
self.assertEqual(response.status, status) assert response.status == status
if __name__ == '__main__':
unittest.main()