feat: impl docs (#4)

The entire public api is covered with documentation in two languages - Russian and English.

the library now supports the latest three versions of python - 3.12, 3.13 and 3.14

minor design changes: now, when a Boolean flag is entered, its value is an empty string, not None.

tests have been adapted to the supported versions of python, readmi has been redesigned in two languages, German is no longer available.
This commit is contained in:
kolo
2025-12-04 21:55:19 +03:00
committed by GitHub
parent a2ac6a608f
commit ce7e24b924
210 changed files with 13770 additions and 1183 deletions
+2 -3
View File
@@ -1,8 +1,7 @@
from argenta.command.models import InputCommand, Command
from argenta.app import App
import unittest
from argenta.app import App
from argenta.command.models import Command, InputCommand
from argenta.router import Router
+201 -116
View File
@@ -1,18 +1,21 @@
import unittest
from unittest.mock import MagicMock, patch
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
from argenta.orchestrator.argparser.arguments.models import (
ValueArgument,
BooleanArgument,
InputArgument,
BaseArgument
)
class TestArgumentClasses(unittest.TestCase):
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="--",
@@ -20,145 +23,227 @@ class TestArgumentClasses(unittest.TestCase):
possible_values=["one", "two"],
default="one",
is_required=True,
is_deprecated=False
is_deprecated=False,
)
self.assertEqual(arg.name, "test_arg")
self.assertEqual(arg.prefix, "--")
self.assertEqual(arg.help, "A test argument.")
self.assertEqual(arg.possible_values, ["one", "two"])
self.assertEqual(arg.default, "one")
self.assertTrue(arg.is_required)
self.assertFalse(arg.is_deprecated)
self.assertEqual(arg.action, "store")
self.assertEqual(arg.string_entity, "--test_arg")
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
name="verbose", prefix="-", help="Enable verbose mode.", is_deprecated=True
)
self.assertEqual(arg.name, "verbose")
self.assertEqual(arg.prefix, "-")
self.assertEqual(arg.help, "Enable verbose mode.")
self.assertTrue(arg.is_deprecated)
self.assertEqual(arg.action, "store_true")
self.assertEqual(arg.string_entity, "-verbose")
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
name="file", value="/path/to/file", founder_class=ValueArgument
)
self.assertEqual(arg.name, "file")
self.assertEqual(arg.value, "/path/to/file")
self.assertEqual(arg.founder_class, ValueArgument)
assert arg.name == "file"
assert arg.value == "/path/to/file"
assert arg.founder_class is ValueArgument
class TestArgParser(unittest.TestCase):
def setUp(self):
self.value_arg = ValueArgument(name="config", help="Path to config file")
self.bool_arg = BooleanArgument(name="debug", help="Enable debug mode")
self.processed_args = [self.value_arg, self.bool_arg]
class TestArgSpace:
"""Tests for the ArgSpace class, which holds parsed argument values."""
def test_argparser_initialization(self):
parser = ArgParser(
processed_args=self.processed_args,
name="TestApp",
description="A test application.",
epilog="Test epilog."
)
self.assertEqual(parser.name, "TestApp")
self.assertEqual(parser.description, "A test application.")
self.assertEqual(parser.epilog, "Test epilog.")
self.assertEqual(parser.processed_args, self.processed_args)
@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),
]
@patch('argenta.orchestrator.argparser.entity.ArgumentParser.parse_args')
def test_parse_args(self, mock_parse_args: MagicMock):
mock_namespace = Namespace(config='config.json', debug=True)
mock_parse_args.return_value = mock_namespace
@pytest.fixture
def arg_space(self, mock_arguments: list[InputArgument]) -> ArgSpace:
"""Provides a pre-populated ArgSpace instance."""
return ArgSpace(all_arguments=mock_arguments)
parser = ArgParser(processed_args=self.processed_args)
arg_space = parser.parse_args()
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
self.assertIsInstance(arg_space, ArgSpace)
self.assertEqual(len(arg_space.all_arguments), 2)
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')
self.assertIsNotNone(config_arg)
if config_arg:
self.assertEqual(config_arg.value, 'config.json')
self.assertEqual(config_arg.founder_class, ValueArgument)
assert config_arg is not None
assert config_arg.value == "config.json"
assert config_arg.founder_class is ValueArgument
self.assertIsNotNone(debug_arg)
if debug_arg:
self.assertTrue(debug_arg.value)
self.assertEqual(debug_arg.founder_class, BooleanArgument)
assert debug_arg is not None
assert debug_arg.value is True
assert debug_arg.founder_class is BooleanArgument
class TestArgSpace(unittest.TestCase):
def setUp(self):
self.input_arg1 = InputArgument(name="arg1", value="val1", founder_class=ValueArgument)
self.input_arg2 = InputArgument(name="arg2", value="val2", founder_class=BooleanArgument)
self.input_arg3 = InputArgument(name="arg3", value="val3", founder_class=ValueArgument)
self.arg_space = ArgSpace(all_arguments=[self.input_arg1, self.input_arg2, self.input_arg3])
class TestArgParser:
"""Tests for the ArgParser class, which orchestrates argument parsing."""
def test_argspace_initialization(self):
self.assertEqual(len(self.arg_space.all_arguments), 3)
self.assertIn(self.input_arg1, self.arg_space.all_arguments)
self.assertIn(self.input_arg2, self.arg_space.all_arguments)
self.assertIn(self.input_arg3, self.arg_space.all_arguments)
@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"])
def test_get_by_name(self):
found_arg = self.arg_space.get_by_name("arg1")
self.assertIsNotNone(found_arg)
if found_arg:
self.assertEqual(found_arg, self.input_arg1)
@pytest.fixture
def bool_arg(self) -> BooleanArgument:
"""Provides a sample BooleanArgument."""
return BooleanArgument(name="debug", help="Enable debug mode")
def test_get_by_name_not_found(self):
found_arg = self.arg_space.get_by_name("non_existent_arg")
self.assertIsNone(found_arg)
@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_get_by_type(self):
value_args = self.arg_space.get_by_type(ValueArgument)
self.assertEqual(len(value_args), 2)
self.assertIn(self.input_arg1, value_args)
self.assertIn(self.input_arg3, value_args)
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 == []
bool_args = self.arg_space.get_by_type(BooleanArgument)
self.assertEqual(len(bool_args), 1)
self.assertIn(self.input_arg2, bool_args)
@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")
def test_get_by_type_not_found(self):
class OtherArgument(BaseArgument):
pass
parser = ArgParser(processed_args=[value_arg, bool_arg])
other_args = self.arg_space.get_by_type(OtherArgument)
self.assertEqual(len(other_args), 0)
def test_from_namespace(self):
namespace = Namespace(arg1="val1", debug=True)
processed_args = [
ValueArgument(name="arg1", prefix="--"),
BooleanArgument(name="debug", prefix="-")
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")
arg_space = ArgSpace.from_namespace(namespace, processed_args)
self.assertEqual(len(arg_space.all_arguments), 2)
parser = ArgParser(processed_args=[value_arg, bool_arg])
arg1 = arg_space.get_by_name('arg1')
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')
self.assertIsNotNone(arg1)
if arg1:
self.assertEqual(arg1.value, "val1")
self.assertEqual(arg1.founder_class, ValueArgument)
assert config_arg is not None
assert config_arg.value == 'config.json'
assert config_arg.founder_class is ValueArgument
self.assertIsNotNone(debug_arg)
if debug_arg:
self.assertTrue(debug_arg.value)
self.assertEqual(debug_arg.founder_class, BooleanArgument)
assert debug_arg is not None
assert debug_arg.value is True
assert debug_arg.founder_class is BooleanArgument
+198
View File
@@ -0,0 +1,198 @@
import os
from unittest.mock import MagicMock, call, patch
import pytest
# 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
def mock_readline():
"""Fixture to provide a mock of the `readline` module."""
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:
_history.append(item)
def get_history_item(index: int) -> str | None:
# readline history is 1-based.
if 1 <= index <= len(_history):
return _history[index - 1]
return None
def get_current_history_length() -> int:
return len(_history)
def clear_history() -> None:
_history.clear()
# Reset all mocks and the internal history before each test.
mock.reset_mock()
clear_history()
# Apply side effects to mock functions to simulate real behavior.
mock.add_history.side_effect = add_history
mock.get_history_item.side_effect = get_history_item
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 = " "
yield 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:
"""Test suite for the AutoCompleter class."""
HISTORY_FILE = "test_history.txt"
COMMANDS = ["start", "stop", "status"]
def test_initialization(self):
"""Tests that the constructor correctly assigns attributes."""
completer = AutoCompleter(history_filename=self.HISTORY_FILE, autocomplete_button="tab")
assert completer.history_filename == self.HISTORY_FILE
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)
completer.initial_setup(self.COMMANDS)
mock_readline.read_history_file.assert_not_called()
expected_calls = [call(cmd) for cmd in self.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.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)
completer.initial_setup(self.COMMANDS)
mock_readline.read_history_file.assert_called_once_with(self.HISTORY_FILE)
mock_readline.add_history.assert_not_called()
mock_readline.set_completer.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."""
completer = AutoCompleter(history_filename=None)
completer.initial_setup(self.COMMANDS)
mock_readline.read_history_file.assert_not_called()
expected_calls = [call(cmd) for cmd in self.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."""
# 1. Populate the mock readline history.
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("stop client")
mock_readline.add_history("invalid command")
mock_readline.add_history("start server") # Add a duplicate.
# 2. Simulate the state of the history file after readline.write_history_file would have run.
raw_history_content = "\n".join(["start server", "stop client", "invalid command", "start server"])
fs.create_file(self.HISTORY_FILE, contents=raw_history_content)
# 3. Call the method under test.
completer = AutoCompleter(history_filename=self.HISTORY_FILE)
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(self.HISTORY_FILE)
# 5. Assert the file was correctly re-written with filtered and unique content.
with open(self.HISTORY_FILE, "r") as f:
content = f.read()
lines = sorted(content.strip().split("\n"))
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."""
completer = AutoCompleter(history_filename=None)
completer.exit_setup(all_commands=self.COMMANDS, ignore_command_register=False)
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."""
for cmd in ["start", "stop"]:
mock_readline.add_history(cmd)
completer = AutoCompleter()
assert completer._complete("run", 0) 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."""
mock_readline.add_history("start server")
mock_readline.add_history("stop server")
completer = AutoCompleter()
assert completer._complete("start", 0) == "start server"
assert completer._complete("start", 1) is None # Subsequent states yield no matches
def test_complete_with_multiple_matches(self, mock_readline):
"""Tests _complete with multiple matches that share a common prefix."""
mock_readline.add_history("status client")
mock_readline.add_history("status server")
mock_readline.add_history("stop")
completer = AutoCompleter()
# On state 0, it should insert the common prefix via readline and return None.
result = completer._complete("stat", 0)
assert result is None
mock_readline.insert_text.assert_called_once_with("us ") # Completes "stat" to "status "
mock_readline.redisplay.assert_called_once()
# On subsequent states, it should do nothing.
mock_readline.reset_mock()
result_state_1 = completer._complete("stat", 1)
assert result_state_1 is None
mock_readline.insert_text.assert_not_called()
class TestHelperFunctions:
"""Test suite for helper functions in the autocompleter module."""
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 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("unknown", existing, ignore_command_register=True) is False
def test_get_history_items(self, mock_readline):
"""Tests the _get_history_items helper function."""
assert _get_history_items() == []
mock_readline.add_history("first item")
mock_readline.add_history("second item")
assert _get_history_items() == ["first item", "second item"]
+14 -15
View File
@@ -1,13 +1,13 @@
import re
import unittest
from argenta.command.exceptions import (EmptyInputCommandException,
RepeatedInputFlagsException,
UnprocessedInputFlagException)
from argenta.command.flag import Flag, InputFlag
from argenta.command.flag.flags import Flags
from argenta.command.flag.models import PossibleValues
from argenta.command.models import InputCommand, Command, ValidationStatus
from argenta.command.exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException)
import unittest
import re
from argenta.command.flag.models import PossibleValues, ValidationStatus
from argenta.command.models import Command, InputCommand
class TestInputCommand(unittest.TestCase):
@@ -26,25 +26,25 @@ class TestInputCommand(unittest.TestCase):
with self.assertRaises(EmptyInputCommandException):
InputCommand.parse('')
def test_validate_valid_input_flag1(self):
def test_validate_invalid_input_flag1(self):
command = Command('some', flags=Flag('test'))
self.assertEqual(command.validate_input_flag(InputFlag('test', input_value=None, status=None)), ValidationStatus.VALID)
self.assertEqual(command.validate_input_flag(InputFlag('test', input_value='', status=None)), ValidationStatus.INVALID)
def test_validate_valid_input_flag2(self):
command = Command('some', flags=Flags([Flag('test'), Flag('more')]))
self.assertEqual(command.validate_input_flag(InputFlag('more', input_value=None, status=None)), ValidationStatus.VALID)
self.assertEqual(command.validate_input_flag(InputFlag('more', input_value='random-value', status=None)), ValidationStatus.VALID)
def test_validate_undefined_input_flag1(self):
command = Command('some', flags=Flag('test'))
self.assertEqual(command.validate_input_flag(InputFlag('more', input_value=None, status=None)), ValidationStatus.UNDEFINED)
self.assertEqual(command.validate_input_flag(InputFlag('more', input_value='', status=None)), ValidationStatus.UNDEFINED)
def test_validate_undefined_input_flag2(self):
command = Command('some', flags=Flags([Flag('test'), Flag('more')]))
self.assertEqual(command.validate_input_flag(InputFlag('case', input_value=None, status=None)), ValidationStatus.UNDEFINED)
self.assertEqual(command.validate_input_flag(InputFlag('case', input_value='', status=None)), ValidationStatus.UNDEFINED)
def test_validate_undefined_input_flag3(self):
command = Command('some')
self.assertEqual(command.validate_input_flag(InputFlag('case', input_value=None, status=None)), ValidationStatus.UNDEFINED)
self.assertEqual(command.validate_input_flag(InputFlag('case', input_value='', status=None)), ValidationStatus.UNDEFINED)
def test_invalid_input_flag1(self):
command = Command('some', flags=Flag('test', possible_values=PossibleValues.NEITHER))
@@ -61,4 +61,3 @@ class TestInputCommand(unittest.TestCase):
def test_isinstance_parse_correct_raw_command(self):
cmd = InputCommand.parse('ssh --host 192.168.0.3')
self.assertIsInstance(cmd, InputCommand)
+2 -2
View File
@@ -1,7 +1,7 @@
from argenta.app.dividing_line import DynamicDividingLine, StaticDividingLine
import unittest
from argenta.app.dividing_line import DynamicDividingLine, StaticDividingLine
class TestDividingLine(unittest.TestCase):
def test_get_static_dividing_line_full_line(self):
+13 -13
View File
@@ -1,8 +1,8 @@
from argenta.command.flag import Flag, InputFlag, PossibleValues
from argenta.command.flag.flags import InputFlags, Flags
import unittest
import re
import unittest
from argenta.command.flag import Flag, InputFlag, PossibleValues
from argenta.command.flag.flags import Flags, InputFlags
class TestFlag(unittest.TestCase):
@@ -29,8 +29,8 @@ class TestFlag(unittest.TestCase):
'--')
def test_get_flag_value_without_set(self):
self.assertEqual(InputFlag(name='test', input_value=None, status=None).input_value,
None)
self.assertEqual(InputFlag(name='test', input_value='', status=None).input_value,
'')
def test_get_flag_value_with_set(self):
flag = InputFlag(name='test', input_value='example', status=None)
@@ -54,11 +54,11 @@ class TestFlag(unittest.TestCase):
def test_validate_correct_empty_flag_value_without_possible_flag_values(self):
flag = Flag(name='test', possible_values=PossibleValues.NEITHER)
self.assertEqual(flag.validate_input_flag_value(None), True)
self.assertEqual(flag.validate_input_flag_value(''), True)
def test_validate_correct_empty_flag_value_with_possible_flag_values(self):
flag = Flag(name='test', possible_values=PossibleValues.NEITHER)
self.assertEqual(flag.validate_input_flag_value(None), True)
self.assertEqual(flag.validate_input_flag_value(''), True)
def test_validate_incorrect_random_flag_value_without_possible_flag_values(self):
flag = Flag(name='test', possible_values=PossibleValues.NEITHER)
@@ -69,19 +69,19 @@ class TestFlag(unittest.TestCase):
self.assertEqual(flag.validate_input_flag_value('random value'), True)
def test_get_input_flag1(self):
flag = InputFlag(name='test', input_value=None, status=None)
flag = InputFlag(name='test', input_value='', status=None)
input_flags = InputFlags([flag])
self.assertEqual(input_flags.get_flag_by_name('test'), flag)
def test_get_input_flag2(self):
flag = InputFlag(name='test', input_value=None, status=None)
flag2 = InputFlag(name='some', input_value=None, status=None)
flag = InputFlag(name='test', input_value='', status=None)
flag2 = InputFlag(name='some', input_value='', status=None)
input_flags = InputFlags([flag, flag2])
self.assertEqual(input_flags.get_flag_by_name('some'), flag2)
def test_get_undefined_input_flag(self):
flag = InputFlag(name='test', input_value=None, status=None)
flag2 = InputFlag(name='some', input_value=None, status=None)
flag = InputFlag(name='test', input_value='', status=None)
flag2 = InputFlag(name='some', input_value='', status=None)
input_flags = InputFlags([flag, flag2])
self.assertEqual(input_flags.get_flag_by_name('case'), None)
+43 -240
View File
@@ -1,131 +1,80 @@
import unittest
from datetime import datetime, date
from datetime import date, datetime
from argenta.response.entity import Response, DataBridge, EMPTY_INPUT_FLAGS
from argenta.response.status import ResponseStatus
from argenta.data_bridge import DataBridge
from argenta.command.flag.models import InputFlag
from argenta.command.flag.flags.models import InputFlags
from argenta.command.flag import InputFlag
from argenta.response.entity import EMPTY_INPUT_FLAGS, Response
from argenta.response.status import ResponseStatus
class TestDataBridge(unittest.TestCase):
def setUp(self):
"""Clear data before each test"""
DataBridge.clear_data()
def tearDown(self):
"""Clear data after each test"""
DataBridge.clear_data()
"""Create a new DataBridge instance for each test"""
self.data_bridge = DataBridge()
def test_update_data_basic(self):
"""Test basic data update functionality"""
test_data = {"key1": "value1", "key2": "value2"}
DataBridge.update_data(test_data)
self.assertEqual(DataBridge.get_data(), test_data)
self.data_bridge.update(test_data)
self.assertEqual(self.data_bridge.get_all(), test_data)
def test_update_data_with_datetime(self):
"""Test updating data with datetime objects"""
test_datetime = datetime(2024, 1, 15, 10, 30, 45)
test_data = {"created_at": test_datetime, "name": "test"}
DataBridge.update_data(test_data)
result = DataBridge.get_data()
self.data_bridge.update(test_data)
result = self.data_bridge.get_all()
self.assertEqual(result["created_at"], test_datetime)
self.assertEqual(result["name"], "test")
def test_update_data_with_date(self):
"""Test updating data with date objects"""
test_date = date(2024, 1, 15)
test_data = {"birth_date": test_date, "active": True}
DataBridge.update_data(test_data)
result = DataBridge.get_data()
self.assertEqual(result["birth_date"], test_date)
self.assertEqual(result["active"], True)
def test_update_data_multiple_calls(self):
"""Test multiple update_data calls merge data"""
first_data = {"key1": "value1", "date1": date(2024, 1, 1)}
second_data = {"key2": "value2", "date2": datetime(2024, 2, 1, 12, 0)}
DataBridge.update_data(first_data)
DataBridge.update_data(second_data)
result = DataBridge.get_data()
self.assertEqual(len(result), 4)
self.assertEqual(result["key1"], "value1")
self.assertEqual(result["key2"], "value2")
self.assertEqual(result["date1"], date(2024, 1, 1))
self.assertEqual(result["date2"], datetime(2024, 2, 1, 12, 0))
def test_update_data_overwrites_existing_keys(self):
"""Test that update_data overwrites existing keys"""
initial_data = {"key": "old_value", "date": date(2024, 1, 1)}
updated_data = {"key": "new_value", "date": date(2024, 2, 1)}
DataBridge.update_data(initial_data)
DataBridge.update_data(updated_data)
result = DataBridge.get_data()
self.assertEqual(result["key"], "new_value")
self.assertEqual(result["date"], date(2024, 2, 1))
"""Test multiple update calls merge data"""
first_data = {"key1": "value1"}
second_data = {"key2": "value2"}
self.data_bridge.update(first_data)
self.data_bridge.update(second_data)
self.assertEqual(len(self.data_bridge.get_all()), 2)
def test_get_data_empty(self):
"""Test get_data returns empty dict when no data"""
result = DataBridge.get_data()
self.assertEqual(result, {})
"""Test get_all returns empty dict when no data"""
self.assertEqual(self.data_bridge.get_all(), {})
def test_clear_data(self):
"""Test clear_data removes all data"""
test_data = {"key": "value", "timestamp": datetime.now()}
DataBridge.update_data(test_data)
# Verify data exists
self.assertNotEqual(DataBridge.get_data(), {})
# Clear and verify
DataBridge.clear_data()
self.assertEqual(DataBridge.get_data(), {})
"""Test clear_all removes all data"""
self.data_bridge.update({"key": "value"})
self.assertNotEqual(self.data_bridge.get_all(), {})
self.data_bridge.clear_all()
self.assertEqual(self.data_bridge.get_all(), {})
def test_delete_from_data(self):
"""Test delete_from_data removes specific key"""
test_data = {
"key1": "value1",
"key2": "value2",
"created_at": datetime(2024, 1, 1, 10, 0)
}
DataBridge.update_data(test_data)
# Delete one key
DataBridge.delete_from_data("key1")
result = DataBridge.get_data()
self.assertEqual(len(result), 2)
"""Test delete_by_key removes specific key"""
test_data = {"key1": "value1", "key2": "value2"}
self.data_bridge.update(test_data)
self.data_bridge.delete_by_key("key1")
result = self.data_bridge.get_all()
self.assertNotIn("key1", result)
self.assertIn("key2", result)
self.assertIn("created_at", result)
def test_delete_from_data_nonexistent_key(self):
"""Test delete_from_data with nonexistent key raises KeyError"""
test_data = {"existing_key": "value"}
DataBridge.update_data(test_data)
"""Test delete_by_key with nonexistent key raises KeyError"""
with self.assertRaises(KeyError):
DataBridge.delete_from_data("nonexistent_key")
self.data_bridge.delete_by_key("nonexistent_key")
def test_get_by_key(self):
"""Test get_by_key retrieves correct value"""
test_data = {"key1": "value1", "key2": date(2024, 1, 1)}
self.data_bridge.update(test_data)
self.assertEqual(self.data_bridge.get_by_key("key1"), "value1")
self.assertEqual(self.data_bridge.get_by_key("key2"), date(2024, 1, 1))
self.assertIsNone(self.data_bridge.get_by_key("nonexistent"))
class TestResponse(unittest.TestCase):
def setUp(self):
"""Clear data before each test"""
DataBridge.clear_data()
def tearDown(self):
"""Clear data after each test"""
DataBridge.clear_data()
def test_response_initialization_basic(self):
"""Test basic Response initialization"""
response = Response(ResponseStatus.ALL_FLAGS_VALID)
self.assertEqual(response.status, ResponseStatus.ALL_FLAGS_VALID)
self.assertEqual(response.input_flags, EMPTY_INPUT_FLAGS)
@@ -133,151 +82,9 @@ class TestResponse(unittest.TestCase):
"""Test Response initialization with input flags"""
input_flags = InputFlags([InputFlag('test', input_value='value', status=None)])
response = Response(ResponseStatus.INVALID_VALUE_FLAGS, input_flags)
self.assertEqual(response.status, ResponseStatus.INVALID_VALUE_FLAGS)
self.assertEqual(response.input_flags, input_flags)
def test_response_inherits_databridge_functionality(self):
"""Test that Response inherits DataBridge methods"""
response = Response(ResponseStatus.ALL_FLAGS_VALID)
test_data = {"message": "hello", "timestamp": datetime.now()}
# Test update_data
response.update_data(test_data)
result = response.get_data()
self.assertEqual(result["message"], "hello")
self.assertIsInstance(result["timestamp"], datetime)
def test_response_data_passing_with_dates(self):
"""Test passing date and datetime objects through Response"""
response = Response(ResponseStatus.ALL_FLAGS_VALID)
current_time = datetime.now()
today = date.today()
date_data = {
"current_datetime": current_time,
"current_date": today,
"custom_datetime": datetime(2024, 3, 15, 14, 30, 0),
"custom_date": date(2023, 12, 25),
"metadata": {"created": current_time, "updated": today}
}
response.update_data(date_data)
retrieved_data = response.get_data()
# Verify datetime objects are preserved
self.assertEqual(retrieved_data["current_datetime"], current_time)
self.assertEqual(retrieved_data["current_date"], today)
self.assertEqual(retrieved_data["custom_datetime"], datetime(2024, 3, 15, 14, 30, 0))
self.assertEqual(retrieved_data["custom_date"], date(2023, 12, 25))
# Verify nested datetime objects
self.assertEqual(retrieved_data["metadata"]["created"], current_time)
self.assertEqual(retrieved_data["metadata"]["updated"], today)
def test_response_data_persistence_across_instances(self):
"""Test that data persists across different Response instances"""
# First response instance
response1 = Response(ResponseStatus.ALL_FLAGS_VALID)
test_datetime = datetime(2024, 1, 1, 12, 0, 0)
response1.update_data({"session_start": test_datetime})
# Second response instance
response2 = Response(ResponseStatus.UNDEFINED_FLAGS)
retrieved_data = response2.get_data()
# Data should persist
self.assertEqual(retrieved_data["session_start"], test_datetime)
def test_response_data_complex_date_scenarios(self):
"""Test complex scenarios with date/datetime handling"""
response = Response(ResponseStatus.ALL_FLAGS_VALID)
# Create complex data structure with various date formats
complex_data = {
"user": {
"name": "John Doe",
"birth_date": date(1990, 5, 15),
"last_login": datetime(2024, 1, 15, 10, 30, 45),
"preferences": {
"timezone": "UTC",
"date_format": "%Y-%m-%d",
"created_at": datetime(2023, 1, 1, 0, 0, 0)
}
},
"events": [
{"name": "login", "timestamp": datetime(2024, 1, 15, 10, 30, 45)},
{"name": "logout", "timestamp": datetime(2024, 1, 15, 18, 45, 30)},
],
"dates_list": [
date(2024, 1, 1),
date(2024, 1, 2),
date(2024, 1, 3)
]
}
response.update_data(complex_data)
retrieved_data = response.get_data()
# Verify all date/datetime objects are correctly preserved
self.assertEqual(retrieved_data["user"]["birth_date"], date(1990, 5, 15))
self.assertEqual(retrieved_data["user"]["last_login"], datetime(2024, 1, 15, 10, 30, 45))
self.assertEqual(retrieved_data["user"]["preferences"]["created_at"], datetime(2023, 1, 1, 0, 0, 0))
# Verify dates in lists
self.assertEqual(len(retrieved_data["events"]), 2)
self.assertEqual(retrieved_data["events"][0]["timestamp"], datetime(2024, 1, 15, 10, 30, 45))
self.assertEqual(retrieved_data["events"][1]["timestamp"], datetime(2024, 1, 15, 18, 45, 30))
# Verify date list
self.assertEqual(len(retrieved_data["dates_list"]), 3)
self.assertEqual(retrieved_data["dates_list"][0], date(2024, 1, 1))
self.assertEqual(retrieved_data["dates_list"][1], date(2024, 1, 2))
self.assertEqual(retrieved_data["dates_list"][2], date(2024, 1, 3))
def test_response_clear_data_functionality(self):
"""Test clearing data functionality through Response"""
response = Response(ResponseStatus.ALL_FLAGS_VALID)
# Add some data with dates
response.update_data({
"timestamp": datetime.now(),
"date": date.today(),
"message": "test"
})
# Verify data exists
self.assertNotEqual(response.get_data(), {})
# Clear data
response.clear_data()
# Verify data is cleared
self.assertEqual(response.get_data(), {})
def test_response_delete_specific_date_data(self):
"""Test deleting specific date-related data"""
response = Response(ResponseStatus.ALL_FLAGS_VALID)
# Add mixed data
test_data = {
"start_date": date(2024, 1, 1),
"end_date": date(2024, 12, 31),
"created_at": datetime.now(),
"name": "test_session"
}
response.update_data(test_data)
# Delete specific date field
response.delete_from_data("start_date")
result = response.get_data()
self.assertNotIn("start_date", result)
self.assertIn("end_date", result)
self.assertIn("created_at", result)
self.assertIn("name", result)
def test_response_status_types(self):
"""Test Response with different status types"""
statuses = [
@@ -286,14 +93,10 @@ class TestResponse(unittest.TestCase):
ResponseStatus.INVALID_VALUE_FLAGS,
ResponseStatus.UNDEFINED_AND_INVALID_FLAGS
]
for status in statuses:
response = Response(status)
response.update_data({"timestamp": datetime.now(), "status_test": True})
self.assertEqual(response.status, status)
self.assertIn("timestamp", response.get_data())
self.assertIn("status_test", response.get_data())
with self.subTest(status=status):
response = Response(status)
self.assertEqual(response.status, status)
if __name__ == '__main__':
+9 -9
View File
@@ -1,16 +1,16 @@
from argenta.command.flag import InputFlag, Flag
import re
import unittest
from argenta.command import Command
from argenta.command.flag import Flag, InputFlag
from argenta.command.flag.flags import Flags, InputFlags
from argenta.command.flag.models import PossibleValues, ValidationStatus
from argenta.response.entity import Response
from argenta.router import Router
from argenta.command import Command
from argenta.router.entity import _structuring_input_flags, _validate_command, _validate_func_args # pyright: ignore[reportPrivateUsage]
from argenta.router.exceptions import (TriggerContainSpacesException,
RepeatedFlagNameException,
RequiredArgumentNotPassedException)
import unittest
import re
from argenta.router.entity import _structuring_input_flags, _validate_command, _validate_func_args # pyright: ignore[reportPrivateUsage]
from argenta.router.exceptions import (RepeatedFlagNameException,
RequiredArgumentNotPassedException,
TriggerContainSpacesException)
class TestRouter(unittest.TestCase):