[Feature] - OpenBB Platform CLI Unit tests (#6397)

* Unit test batch 1

* CLI controller

* Test batch 3

* Test batch 4

* Test batch 5

* clean some workflows and setup actions

* test

* rename wfs

* rename

* update action

* Skip

* fix cli tests

---------

Co-authored-by: Henrique Joaquim <henriquecjoaquim@gmail.com>
Co-authored-by: Diogo Sousa <montezdesousa@gmail.com>
Co-authored-by: montezdesousa <79287829+montezdesousa@users.noreply.github.com>
This commit is contained in:
Igor Radovanovic
2024-05-14 17:38:06 +02:00
committed by GitHub
parent f87c21ed3c
commit a116d9bbf2
35 changed files with 1502 additions and 416 deletions

View File

@@ -0,0 +1,94 @@
"""Test the Argparse Translator."""
from argparse import ArgumentParser
import pytest
from openbb_cli.argparse_translator.argparse_translator import (
ArgparseTranslator,
CustomArgument,
CustomArgumentGroup,
)
# pylint: disable=protected-access
def test_custom_argument_action_validation():
"""Test that CustomArgument raises an error for invalid actions."""
with pytest.raises(ValueError) as excinfo:
CustomArgument(
name="test",
type=bool,
dest="test",
default=False,
required=True,
action="store",
help="Test argument",
nargs=None,
choices=None,
)
assert 'action must be "store_true"' in str(excinfo.value)
def test_custom_argument_remove_props_on_store_true():
"""Test that CustomArgument removes type, nargs, and choices on store_true."""
argument = CustomArgument(
name="verbose",
type=None,
dest="verbose",
default=None,
required=False,
action="store_true",
help="Verbose output",
nargs=None,
choices=None,
)
assert argument.type is None
assert argument.nargs is None
assert argument.choices is None
def test_custom_argument_group():
"""Test the CustomArgumentGroup class."""
args = [
CustomArgument(
name="test",
type=int,
dest="test",
default=1,
required=True,
action="store",
help="Test argument",
nargs=None,
choices=None,
)
]
group = CustomArgumentGroup(name="Test Group", arguments=args)
assert group.name == "Test Group"
assert len(group.arguments) == 1
assert group.arguments[0].name == "test"
def test_argparse_translator_setup():
"""Test the ArgparseTranslator setup."""
def test_function(test_arg: int):
"""A test function."""
return test_arg * 2
translator = ArgparseTranslator(func=test_function)
parser = translator.parser
assert isinstance(parser, ArgumentParser)
assert "--test_arg" in parser._option_string_actions
def test_argparse_translator_execution():
"""Test the ArgparseTranslator execution."""
def test_function(test_arg: int) -> int:
"""A test function."""
return test_arg * 2
translator = ArgparseTranslator(func=test_function)
parsed_args = translator.parser.parse_args(["--test_arg", "3"])
result = translator.execute_func(parsed_args)
assert result == 6

View File

@@ -0,0 +1,89 @@
"""Test OBBject Registry."""
from unittest.mock import Mock
import pytest
from openbb_cli.argparse_translator.obbject_registry import Registry
from openbb_core.app.model.obbject import OBBject
# pylint: disable=redefined-outer-name, protected-access
@pytest.fixture
def registry():
"""Fixture to create a Registry instance for testing."""
return Registry()
@pytest.fixture
def mock_obbject():
"""Fixture to create a mock OBBject for testing."""
class MockModel:
"""Mock model for testing."""
def __init__(self, value):
self.mock_value = value
self._model_json_schema = "mock_json_schema"
def model_json_schema(self):
return self._model_json_schema
obb = Mock(spec=OBBject)
obb.id = "123"
obb.provider = "test_provider"
obb.extra = {"command": "test_command"}
obb._route = "/test/route"
obb._standard_params = Mock()
obb._standard_params.__dict__ = {}
obb.results = [MockModel(1), MockModel(2)]
return obb
def test_listing_all_obbjects(registry, mock_obbject):
"""Test listing all obbjects with additional properties."""
registry.register(mock_obbject)
all_obbjects = registry.all
assert len(all_obbjects) == 1
assert all_obbjects[0]["command"] == "test_command"
assert all_obbjects[0]["provider"] == "test_provider"
def test_registry_initialization(registry):
"""Test the Registry is initialized correctly."""
assert registry.obbjects == []
def test_register_new_obbject(registry, mock_obbject):
"""Test registering a new OBBject."""
registry.register(mock_obbject)
assert mock_obbject in registry.obbjects
def test_register_duplicate_obbject(registry, mock_obbject):
"""Test that duplicate OBBjects are not added."""
registry.register(mock_obbject)
registry.register(mock_obbject)
assert len(registry.obbjects) == 1
def test_get_obbject_by_index(registry, mock_obbject):
"""Test retrieving an obbject by its index."""
registry.register(mock_obbject)
retrieved = registry.get(0)
assert retrieved == mock_obbject
def test_remove_obbject_by_index(registry, mock_obbject):
"""Test removing an obbject by index."""
registry.register(mock_obbject)
registry.remove(0)
assert mock_obbject not in registry.obbjects
def test_remove_last_obbject_by_default(registry, mock_obbject):
"""Test removing the last obbject by default."""
registry.register(mock_obbject)
registry.remove()
assert not registry.obbjects

45
cli/tests/test_cli.py Normal file
View File

@@ -0,0 +1,45 @@
"""Test the CLI module."""
from unittest.mock import patch
from openbb_cli.cli import main
@patch("openbb_cli.cli.bootstrap")
@patch("openbb_cli.cli.launch")
@patch("sys.argv", ["openbb", "--dev", "--debug"])
def test_main_with_dev_and_debug(mock_launch, mock_bootstrap):
"""Test the main function with dev and debug flags."""
main()
mock_bootstrap.assert_called_once()
mock_launch.assert_called_once_with(True, True)
@patch("openbb_cli.cli.bootstrap")
@patch("openbb_cli.cli.launch")
@patch("sys.argv", ["openbb"])
def test_main_without_arguments(mock_launch, mock_bootstrap):
"""Test the main function without arguments."""
main()
mock_bootstrap.assert_called_once()
mock_launch.assert_called_once_with(False, False)
@patch("openbb_cli.cli.bootstrap")
@patch("openbb_cli.cli.launch")
@patch("sys.argv", ["openbb", "--dev"])
def test_main_with_dev_only(mock_launch, mock_bootstrap):
"""Test the main function with dev flag only."""
main()
mock_bootstrap.assert_called_once()
mock_launch.assert_called_once_with(True, False)
@patch("openbb_cli.cli.bootstrap")
@patch("openbb_cli.cli.launch")
@patch("sys.argv", ["openbb", "--debug"])
def test_main_with_debug_only(mock_launch, mock_bootstrap):
"""Test the main function with debug flag only."""
main()
mock_bootstrap.assert_called_once()
mock_launch.assert_called_once_with(False, True)

View File

@@ -0,0 +1,78 @@
"""Test the Config completer."""
import pytest
from openbb_cli.config.completer import WordCompleter
from prompt_toolkit.completion import CompleteEvent
from prompt_toolkit.document import Document
# pylint: disable=redefined-outer-name, import-outside-toplevel
@pytest.fixture
def word_completer():
"""Return a simple word completer."""
words = ["test", "example", "demo"]
return WordCompleter(words, ignore_case=True)
def test_word_completer_simple(word_completer):
"""Test the word completer with a simple word list."""
doc = Document(text="ex", cursor_position=2)
completions = list(word_completer.get_completions(doc, CompleteEvent()))
assert len(completions) == 1
assert completions[0].text == "example"
def test_word_completer_case_insensitive(word_completer):
"""Test the word completer with case-insensitive matching."""
doc = Document(text="Ex", cursor_position=2)
completions = list(word_completer.get_completions(doc, CompleteEvent()))
assert len(completions) == 1
assert completions[0].text == "example"
def test_word_completer_no_match(word_completer):
"""Test the word completer with no matches."""
doc = Document(text="xyz", cursor_position=3)
completions = list(word_completer.get_completions(doc, CompleteEvent()))
assert len(completions) == 0
@pytest.fixture
def nested_completer():
"""Return a nested completer."""
from openbb_cli.config.completer import NestedCompleter
data = {
"show": {
"version": None,
"interfaces": None,
"clock": None,
"ip": {"interface": {"brief": None}},
},
"exit": None,
"enable": None,
}
return NestedCompleter.from_nested_dict(data)
def test_nested_completer_root_command(nested_completer):
"""Test the nested completer with a root command."""
doc = Document(text="sh", cursor_position=2)
completions = list(nested_completer.get_completions(doc, CompleteEvent()))
assert "show" in [c.text for c in completions]
def test_nested_completer_sub_command(nested_completer):
"""Test the nested completer with a sub-command."""
doc = Document(text="show ", cursor_position=5)
completions = list(nested_completer.get_completions(doc, CompleteEvent()))
assert "version" in [c.text for c in completions]
assert "interfaces" in [c.text for c in completions]
def test_nested_completer_no_match(nested_completer):
"""Test the nested completer with no matches."""
doc = Document(text="random ", cursor_position=7)
completions = list(nested_completer.get_completions(doc, CompleteEvent()))
assert len(completions) == 0

View File

@@ -0,0 +1,47 @@
"""Test Config Console."""
from unittest.mock import patch
import pytest
from openbb_cli.config.console import Console
from rich.text import Text
# pylint: disable=redefined-outer-name, unused-argument, unused-variable, protected-access
@pytest.fixture
def mock_settings():
"""Mock settings to inject into Console."""
class MockSettings:
TEST_MODE = False
ENABLE_RICH_PANEL = True
SHOW_VERSION = True
VERSION = "1.0"
return MockSettings()
@pytest.fixture
def console(mock_settings):
"""Create a Console instance with mocked settings."""
with patch("rich.console.Console") as MockRichConsole: # noqa: F841
return Console(settings=mock_settings)
def test_print_without_panel(console, mock_settings):
"""Test printing without a rich panel when disabled."""
mock_settings.ENABLE_RICH_PANEL = False
with patch.object(console._console, "print") as mock_print:
console.print(text="Hello, world!", menu="Home Menu")
mock_print.assert_called_once_with("Hello, world!")
def test_blend_text():
"""Test blending text colors."""
message = "Hello"
color1 = (255, 0, 0) # Red
color2 = (0, 0, 255) # Blue
blended_text = Console._blend_text(message, color1, color2)
assert isinstance(blended_text, Text)
assert "Hello" in blended_text.plain

View File

@@ -0,0 +1,68 @@
"""Test Config Menu Text."""
import pytest
from openbb_cli.config.menu_text import MenuText
# pylint: disable=redefined-outer-name, protected-access
@pytest.fixture
def menu_text():
"""Fixture to create a MenuText instance for testing."""
return MenuText(path="/test/path")
def test_initialization(menu_text):
"""Test initialization of the MenuText class."""
assert menu_text.menu_text == ""
assert menu_text.menu_path == "/test/path"
assert menu_text.warnings == []
def test_add_raw(menu_text):
"""Test adding raw text."""
menu_text.add_raw("Example raw text")
assert "Example raw text" in menu_text.menu_text
def test_add_info(menu_text):
"""Test adding informational text."""
menu_text.add_info("Info text")
assert "[info]Info text:[/info]" in menu_text.menu_text
def test_add_cmd(menu_text):
"""Test adding a command."""
menu_text.add_cmd("command", "Performs an action")
assert "command" in menu_text.menu_text
assert "Performs an action" in menu_text.menu_text
def test_format_cmd_name(menu_text):
"""Test formatting of command names that are too long."""
long_name = "x" * 50 # Assuming CMD_NAME_LENGTH is 23
formatted_name = menu_text._format_cmd_name(long_name)
assert len(formatted_name) <= menu_text.CMD_NAME_LENGTH
assert menu_text.warnings # Check that a warning was added
def test_format_cmd_description(menu_text):
"""Test truncation of long descriptions."""
long_description = "y" * 100 # Assuming CMD_DESCRIPTION_LENGTH is 65
formatted_description = menu_text._format_cmd_description("cmd", long_description)
assert len(formatted_description) <= menu_text.CMD_DESCRIPTION_LENGTH
def test_add_menu(menu_text):
"""Test adding a menu item."""
menu_text.add_menu("Settings", "Configure your settings")
assert "Settings" in menu_text.menu_text
assert "Configure your settings" in menu_text.menu_text
def test_add_setting(menu_text):
"""Test adding a setting."""
menu_text.add_setting("Enable Feature", True, "Feature description")
assert "Enable Feature" in menu_text.menu_text
assert "Feature description" in menu_text.menu_text
assert "[green]" in menu_text.menu_text

View File

@@ -0,0 +1,49 @@
"""Test the Config Setup."""
from unittest.mock import patch
import pytest
from openbb_cli.config.setup import bootstrap
# pylint: disable=unused-variable
def test_bootstrap_creates_directory_and_file():
"""Test that bootstrap creates the settings directory and environment file."""
with patch("pathlib.Path.mkdir") as mock_mkdir, patch(
"pathlib.Path.touch"
) as mock_touch:
bootstrap()
mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
mock_touch.assert_called_once_with(exist_ok=True)
def test_bootstrap_directory_exists():
"""Test bootstrap when the directory already exists."""
with patch("pathlib.Path.mkdir") as mock_mkdir, patch(
"pathlib.Path.touch"
) as mock_touch:
bootstrap()
mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
mock_touch.assert_called_once_with(exist_ok=True)
def test_bootstrap_file_exists():
"""Test bootstrap when the environment file already exists."""
with patch("pathlib.Path.mkdir") as mock_mkdir, patch(
"pathlib.Path.touch"
) as mock_touch:
bootstrap()
mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
mock_touch.assert_called_once_with(exist_ok=True)
def test_bootstrap_permission_error():
"""Test bootstrap handles permission errors gracefully."""
with patch("pathlib.Path.mkdir") as mock_mkdir, patch(
"pathlib.Path.touch"
) as mock_touch, pytest.raises( # noqa: F841
PermissionError
):
mock_mkdir.side_effect = PermissionError("No permission to create directory")
bootstrap() # Expecting to raise a PermissionError and be caught by pytest.raises

View File

@@ -0,0 +1,60 @@
"""Test Config Style."""
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from openbb_cli.config.style import Style
# pylint: disable=redefined-outer-name, protected-access
@pytest.fixture
def mock_style_directory(tmp_path):
"""Fixture to create a mock styles directory."""
(tmp_path / "styles" / "default").mkdir(parents=True, exist_ok=True)
return tmp_path / "styles"
@pytest.fixture
def style(mock_style_directory):
"""Fixture to create a Style instance for testing."""
return Style(directory=mock_style_directory)
def test_initialization(style):
"""Test that Style class initializes with default properties."""
assert style.line_width == 1.5
assert isinstance(style.console_style, dict)
@patch("pathlib.Path.exists", MagicMock(return_value=True))
@patch("pathlib.Path.rglob")
def test_load_styles(mock_rglob, style, mock_style_directory):
"""Test loading styles from directories."""
mock_rglob.return_value = [mock_style_directory / "default" / "dark.richstyle.json"]
style._load(mock_style_directory)
assert "dark" in style.console_styles_available
@patch("builtins.open", new_callable=MagicMock)
@patch("json.load", MagicMock(return_value={"background": "black"}))
def test_from_json(mock_open, style, mock_style_directory):
"""Test loading style from a JSON file."""
json_file = mock_style_directory / "dark.richstyle.json"
result = style._from_json(json_file)
assert result == {"background": "black"}
mock_open.assert_called_once_with(json_file)
def test_apply_invalid_style(style, mock_style_directory, capsys):
"""Test applying an invalid style and falling back to default."""
style.apply("nonexistent", mock_style_directory)
captured = capsys.readouterr()
assert "Invalid console style" in captured.out
def test_available_styles(style):
"""Test listing available styles."""
style.console_styles_available = {"dark": Path("/path/to/dark.richstyle.json")}
assert "dark" in style.available_styles

View File

@@ -0,0 +1,79 @@
"""Test the base controller."""
from unittest.mock import MagicMock, patch
import pytest
from openbb_cli.controllers.base_controller import BaseController
# pylint: disable=unused-argument, unused-variable
class TestableBaseController(BaseController):
"""Testable Base Controller."""
def __init__(self, queue=None):
"""Initialize the TestableBaseController."""
self.PATH = "/valid/path/"
super().__init__(queue=queue)
def print_help(self):
"""Print help."""
def test_base_controller_initialization():
"""Test the initialization of the base controller."""
with patch.object(TestableBaseController, "check_path", return_value=None):
controller = TestableBaseController()
assert controller.path == ["valid", "path"] # Checking for correct path split
def test_path_validation():
"""Test the path validation method."""
controller = TestableBaseController()
with pytest.raises(ValueError):
controller.PATH = "invalid/path"
controller.check_path()
with pytest.raises(ValueError):
controller.PATH = "/invalid/path"
controller.check_path()
with pytest.raises(ValueError):
controller.PATH = "/Invalid/Path/"
controller.check_path()
controller.PATH = "/valid/path/"
def test_parse_input():
"""Test the parse input method."""
controller = TestableBaseController()
input_str = "cmd1/cmd2/cmd3"
expected = ["cmd1", "cmd2", "cmd3"]
result = controller.parse_input(input_str)
assert result == expected
def test_switch():
"""Test the switch method."""
controller = TestableBaseController()
with patch.object(controller, "call_exit", MagicMock()) as mock_exit:
controller.queue = ["exit"]
controller.switch("exit")
mock_exit.assert_called_once()
def test_call_help():
"""Test the call help method."""
controller = TestableBaseController()
with patch("openbb_cli.controllers.base_controller.session.console.print"):
controller.call_help(None)
def test_call_exit():
"""Test the call exit method."""
controller = TestableBaseController()
with patch.object(controller, "save_class", MagicMock()):
controller.queue = ["quit"]
controller.call_exit(None)

View File

@@ -0,0 +1,70 @@
"""Test the BasePlatformController."""
from unittest.mock import MagicMock, patch
import pytest
from openbb_cli.controllers.base_platform_controller import PlatformController, Session
# pylint: disable=redefined-outer-name, protected-access, unused-argument, unused-variable
@pytest.fixture
def mock_session():
"""Mock session fixture."""
with patch(
"openbb_cli.controllers.base_platform_controller.session",
MagicMock(spec=Session),
) as mock:
yield mock
def test_initialization_with_valid_params(mock_session):
"""Test the initialization of the BasePlatformController."""
translators = {"dummy_translator": MagicMock()}
controller = PlatformController(
name="test", parent_path=["parent"], translators=translators
)
assert controller._name == "test"
assert controller.translators == translators
def test_initialization_without_required_params():
"""Test the initialization of the BasePlatformController without required params."""
with pytest.raises(ValueError):
PlatformController(name="test", parent_path=["parent"])
def test_command_generation(mock_session):
"""Test the command generation method."""
translator = MagicMock()
translators = {"test_command": translator}
controller = PlatformController(
name="test", parent_path=["parent"], translators=translators
)
# Check if command function is correctly linked
assert "test_command" in controller.translators
def test_print_help(mock_session):
"""Test the print help method."""
translators = {"test_command": MagicMock()}
controller = PlatformController(
name="test", parent_path=["parent"], translators=translators
)
with patch(
"openbb_cli.controllers.base_platform_controller.MenuText"
) as mock_menu_text:
controller.print_help()
mock_menu_text.assert_called_once_with("/parent/test/")
def test_sub_controller_generation(mock_session):
"""Test the sub controller generation method."""
translators = {"test_menu_item": MagicMock()}
controller = PlatformController(
name="test", parent_path=["parent"], translators=translators
)
assert "test_menu_item" in controller.translators

View File

@@ -0,0 +1,51 @@
"""Test the choices controller."""
from argparse import ArgumentParser
from unittest.mock import patch
import pytest
from openbb_cli.controllers.choices import (
build_controller_choice_map,
)
# pylint: disable=redefined-outer-name, protected-access, unused-argument, unused-variable
class MockController:
"""Mock controller class for testing."""
CHOICES_COMMANDS = ["test_command"]
controller_choices = ["test_command", "help"]
def call_test_command(self, args):
"""Mock function for test_command."""
parser = ArgumentParser()
parser.add_argument(
"--example", choices=["option1", "option2"], help="Example argument."
)
return parser.parse_args(args)
@pytest.fixture
def mock_controller():
"""Mock controller fixture."""
return MockController()
def test_build_command_choice_map(mock_controller):
"""Test the building of a command choice map."""
with patch(
"openbb_cli.controllers.choices._get_argument_parser"
) as mock_get_parser:
parser = ArgumentParser()
parser.add_argument(
"--option", choices=["opt1", "opt2"], help="A choice option."
)
mock_get_parser.return_value = parser
choice_map = build_controller_choice_map(controller=mock_controller)
assert "test_command" in choice_map
assert "--option" in choice_map["test_command"]
assert "opt1" in choice_map["test_command"]["--option"]
assert "opt2" in choice_map["test_command"]["--option"]

View File

@@ -0,0 +1,79 @@
"""Test the CLI controller."""
from unittest.mock import MagicMock, patch
import pytest
from openbb_cli.controllers.cli_controller import (
CLIController,
handle_job_cmds,
parse_and_split_input,
run_cli,
)
# pylint: disable=redefined-outer-name, unused-argument
def test_parse_and_split_input_custom_filters():
"""Test the parse_and_split_input function with custom filters."""
input_cmd = "query -q AAPL/P"
result = parse_and_split_input(
input_cmd, custom_filters=[r"((\ -q |\ --question|\ ).*?(/))"]
)
assert (
"AAPL/P" not in result
), "Should filter out terms that look like a sorting parameter"
@patch("openbb_cli.controllers.cli_controller.CLIController.print_help")
def test_cli_controller_print_help(mock_print_help):
"""Test the CLIController print_help method."""
controller = CLIController()
controller.print_help()
mock_print_help.assert_called_once()
@pytest.mark.parametrize(
"controller_input, expected_output",
[
("settings", True),
("random_command", False),
],
)
def test_CLIController_has_command(controller_input, expected_output):
"""Test the CLIController has_command method."""
controller = CLIController()
assert hasattr(controller, f"call_{controller_input}") == expected_output
def test_handle_job_cmds_with_export_path():
"""Test the handle_job_cmds function with an export path."""
jobs_cmds = ["export /path/to/export some_command"]
result = handle_job_cmds(jobs_cmds)
expected = "some_command"
assert expected in result[0] # type: ignore
@patch("openbb_cli.controllers.cli_controller.CLIController.switch", return_value=[])
@patch("openbb_cli.controllers.cli_controller.print_goodbye")
def test_run_cli_quit_command(mock_print_goodbye, mock_switch):
"""Test the run_cli function with the quit command."""
run_cli(["quit"], test_mode=True)
mock_print_goodbye.assert_called_once()
@pytest.mark.skip("This test is not working as expected")
def test_execute_openbb_routine_with_mocked_requests():
"""Test the call_exe function with mocked requests."""
with patch("requests.get") as mock_get:
response = MagicMock()
response.status_code = 200
response.json.return_value = {"script": "print('Hello World')"}
mock_get.return_value = response
# Here we need to call the correct function, assuming it's something like `call_exe` for URL-based scripts
controller = CLIController()
controller.call_exe(
["--url", "https://my.openbb.co/u/test/routine/test.openbb"]
)
mock_get.assert_called_with(
"https://my.openbb.co/u/test/routine/test.openbb?raw=true", timeout=10
)

View File

@@ -0,0 +1,61 @@
"""Test the Controller Factory."""
from unittest.mock import MagicMock, patch
import pytest
from openbb_cli.controllers.platform_controller_factory import (
PlatformControllerFactory,
)
# pylint: disable=redefined-outer-name, unused-argument
@pytest.fixture
def mock_processor():
"""Fixture to mock ArgparseClassProcessor."""
with patch(
"openbb_cli.controllers.platform_controller_factory.ArgparseClassProcessor"
) as mock:
instance = mock.return_value
instance.paths = {"settings": "menu"}
instance.translators = {"test_router_settings": MagicMock()}
yield instance
@pytest.fixture
def platform_router():
"""Fixture to provide a mock platform_router class."""
class MockRouter:
pass
return MockRouter
@pytest.fixture
def factory(platform_router, mock_processor):
"""Fixture to create a PlatformControllerFactory with mocked dependencies."""
return PlatformControllerFactory(
platform_router=platform_router, reference={"test": "ref"}
)
def test_init(mock_processor):
"""Test the initialization of the PlatformControllerFactory."""
factory = PlatformControllerFactory(
platform_router=MagicMock(), reference={"test": "ref"}
)
assert factory.router_name.lower() == "magicmock"
assert factory.controller_name == "MagicmockController"
def test_create_controller(factory):
"""Test the creation of a controller class."""
ControllerClass = factory.create()
assert "PlatformController" in [base.__name__ for base in ControllerClass.__bases__]
assert ControllerClass.CHOICES_GENERATION
assert "settings" in ControllerClass.CHOICES_MENUS
assert "test_router_settings" not in [
cmd.replace("test_router_", "") for cmd in ControllerClass.CHOICES_COMMANDS
]

View File

@@ -0,0 +1,106 @@
"""Test Script parser."""
from datetime import datetime, timedelta
import pytest
from openbb_cli.controllers.script_parser import (
match_and_return_openbb_keyword_date,
parse_openbb_script,
)
# pylint: disable=import-outside-toplevel, unused-variable, line-too-long
@pytest.mark.parametrize(
"command, expected",
[
("reset", True),
("r", True),
("r\n", True),
("restart", False),
],
)
def test_is_reset(command, expected):
"""Test the is_reset function."""
from openbb_cli.controllers.script_parser import is_reset
assert is_reset(command) == expected
@pytest.mark.parametrize(
"keyword, expected_date",
[
(
"$LASTFRIDAY",
(
datetime.now()
- timedelta(days=((datetime.now().weekday() - 4) % 7 + 7) % 7)
).strftime("%Y-%m-%d"),
),
],
)
def test_match_and_return_openbb_keyword_date(keyword, expected_date):
"""Test the match_and_return_openbb_keyword_date function."""
result = match_and_return_openbb_keyword_date(keyword)
assert result == expected_date
def test_parse_openbb_script_basic():
"""Test the parse_openbb_script function."""
raw_lines = ["echo 'Hello World'"]
error, script = parse_openbb_script(raw_lines)
assert error == ""
assert script == "/echo 'Hello World'"
def test_parse_openbb_script_with_variable():
"""Test the parse_openbb_script function."""
raw_lines = ["$VAR = 2022-01-01", "echo $VAR"]
error, script = parse_openbb_script(raw_lines)
assert error == ""
assert script == "/echo 2022-01-01"
def test_parse_openbb_script_with_foreach_loop():
"""Test the parse_openbb_script function."""
raw_lines = ["foreach $$DATE in 2022-01-01,2022-01-02", "echo $$DATE", "end"]
error, script = parse_openbb_script(raw_lines)
assert error == ""
assert script == "/echo 2022-01-01/echo 2022-01-02"
def test_parse_openbb_script_with_error():
"""Test the parse_openbb_script function."""
raw_lines = ["$VAR = ", "echo $VAR"]
error, script = parse_openbb_script(raw_lines)
assert "Variable $VAR not given" in error
@pytest.mark.parametrize(
"line, expected",
[
(
"foreach $$VAR in 2022-01-01",
"[red]The script has a foreach loop that doesn't terminate. Add the keyword 'end' to explicitly terminate loop[/red]", # noqa: E501
),
("echo Hello World", ""),
(
"end",
"[red]The script has a foreach loop that terminates before it gets started. Add the keyword 'foreach' to explicitly start loop[/red]", # noqa: E501
),
],
)
def test_parse_openbb_script_foreach_errors(line, expected):
"""Test the parse_openbb_script function."""
error, script = parse_openbb_script([line])
assert error == expected
def test_date_keyword_last_friday():
"""Test the match_and_return_openbb_keyword_date function."""
today = datetime.now()
last_friday = today - timedelta(days=(today.weekday() - 4 + 7) % 7)
if last_friday > today:
last_friday -= timedelta(days=7)
expected_date = last_friday.strftime("%Y-%m-%d")
assert match_and_return_openbb_keyword_date("$LASTFRIDAY") == expected_date

View File

@@ -0,0 +1,135 @@
"""Test the SettingsController class."""
from unittest.mock import MagicMock, patch
import pytest
from openbb_cli.controllers.settings_controller import SettingsController
# pylint: disable=redefined-outer-name, unused-argument
@pytest.fixture
def mock_session():
with patch("openbb_cli.controllers.settings_controller.session") as mock:
mock.settings.USE_INTERACTIVE_DF = False
mock.settings.ALLOWED_NUMBER_OF_ROWS = 20
mock.settings.TIMEZONE = "UTC"
mock.settings.set_item = MagicMock()
yield mock
def test_call_interactive(mock_session):
controller = SettingsController()
controller.call_interactive(None)
mock_session.settings.set_item.assert_called_once_with("USE_INTERACTIVE_DF", True)
@pytest.mark.parametrize(
"input_rows, expected",
[
(10, 10),
(15, 15),
],
)
def test_call_n_rows(input_rows, expected, mock_session):
controller = SettingsController()
args = ["--rows", str(input_rows)]
controller.call_n_rows(args)
mock_session.settings.set_item.assert_called_with(
"ALLOWED_NUMBER_OF_ROWS", expected
)
def test_call_n_rows_no_args_provided(mock_session):
controller = SettingsController()
controller.call_n_rows([])
mock_session.console.print.assert_called_with("Current number of rows: 20")
@pytest.mark.parametrize(
"timezone, valid",
[
("UTC", True),
("Mars/Phobos", False),
],
)
def test_call_timezone(timezone, valid, mock_session):
with patch(
"openbb_cli.controllers.settings_controller.is_timezone_valid",
return_value=valid,
):
controller = SettingsController()
args = ["--timezone", timezone]
controller.call_timezone(args)
if valid:
mock_session.settings.set_item.assert_called_with("TIMEZONE", timezone)
else:
mock_session.settings.set_item.assert_not_called()
def test_call_console_style(mock_session):
controller = SettingsController()
args = ["--style", "dark"]
controller.call_console_style(args)
mock_session.console.print.assert_called()
def test_call_console_style_no_args(mock_session):
mock_session.settings.RICH_STYLE = "default"
controller = SettingsController()
controller.call_console_style([])
mock_session.console.print.assert_called_with("Current console style: default")
def test_call_flair(mock_session):
controller = SettingsController()
args = ["--flair", "rocket"]
controller.call_flair(args)
def test_call_flair_no_args(mock_session):
mock_session.settings.FLAIR = "star"
controller = SettingsController()
controller.call_flair([])
mock_session.console.print.assert_called_with("Current flair: star")
def test_call_obbject_display(mock_session):
controller = SettingsController()
args = ["--number", "5"]
controller.call_obbject_display(args)
mock_session.settings.set_item.assert_called_once_with(
"N_TO_DISPLAY_OBBJECT_REGISTRY", 5
)
def test_call_obbject_display_no_args(mock_session):
mock_session.settings.N_TO_DISPLAY_OBBJECT_REGISTRY = 10
controller = SettingsController()
controller.call_obbject_display([])
mock_session.console.print.assert_called_with(
"Current number of results to display from the OBBject registry: 10"
)
@pytest.mark.parametrize(
"args, expected",
[
(["--rows", "50"], 50),
(["--rows", "100"], 100),
([], 20),
],
)
def test_call_n_rows_v2(args, expected, mock_session):
mock_session.settings.ALLOWED_NUMBER_OF_ROWS = 20
controller = SettingsController()
controller.call_n_rows(args)
if args:
mock_session.settings.set_item.assert_called_with(
"ALLOWED_NUMBER_OF_ROWS", expected
)
else:
mock_session.console.print.assert_called_with("Current number of rows: 20")

View File

@@ -0,0 +1,157 @@
"""Test the Controller utils."""
import argparse
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from openbb_cli.controllers.utils import (
check_non_negative,
check_positive,
get_flair_and_username,
get_user_agent,
parse_and_split_input,
print_goodbye,
print_guest_block_msg,
remove_file,
reset,
welcome_message,
)
# pylint: disable=redefined-outer-name, unused-argument
@pytest.fixture
def mock_session():
"""Mock the session and its dependencies."""
with patch("openbb_cli.controllers.utils.Session", autospec=True) as mock:
mock.return_value.console.print = MagicMock()
mock.return_value.is_local = MagicMock(return_value=True)
mock.return_value.settings.VERSION = "1.0"
mock.return_value.user.profile.hub_session.username = "testuser"
mock.return_value.settings.FLAIR = "rocket"
yield mock
def test_remove_file_existing_file():
"""Test removing an existing file."""
with patch("os.path.isfile", return_value=True), patch("os.remove") as mock_remove:
assert remove_file(Path("/path/to/file"))
mock_remove.assert_called_once()
def test_remove_file_directory():
"""Test removing a directory."""
with patch("os.path.isfile", return_value=False), patch(
"os.path.isdir", return_value=True
), patch("shutil.rmtree") as mock_rmtree:
assert remove_file(Path("/path/to/directory"))
mock_rmtree.assert_called_once()
def test_remove_file_failure(mock_session):
"""Test removing a file that fails."""
with patch("os.path.isfile", return_value=True), patch(
"os.remove", side_effect=Exception("Error")
):
assert not remove_file(Path("/path/to/file"))
mock_session.return_value.console.print.assert_called()
def test_print_goodbye(mock_session):
"""Test printing the goodbye message."""
print_goodbye()
mock_session.return_value.console.print.assert_called()
def test_reset(mock_session):
"""Test resetting the CLI."""
with patch("openbb_cli.controllers.utils.remove_file"), patch(
"sys.modules", new_callable=dict
):
reset()
mock_session.return_value.console.print.assert_called()
def test_parse_and_split_input():
"""Test parsing and splitting user input."""
user_input = "ls -f /home/user/docs/document.xlsx"
result = parse_and_split_input(user_input, [])
assert "ls" in result[0]
@pytest.mark.parametrize(
"input_command, expected_output",
[
("/", ["home"]),
("ls -f /path/to/file.txt", ["ls -f ", "path", "to", "file.txt"]),
("rm -f /home/user/docs", ["rm -f ", "home", "user", "docs"]),
],
)
def test_parse_and_split_input_special_cases(input_command, expected_output):
"""Test parsing and splitting user input with special cases."""
result = parse_and_split_input(input_command, [])
assert result == expected_output
def test_print_guest_block_msg(mock_session):
"""Test printing the guest block message."""
print_guest_block_msg()
mock_session.return_value.console.print.assert_called()
def test_welcome_message(mock_session):
"""Test printing the welcome message."""
welcome_message()
mock_session.return_value.console.print.assert_called_with(
"\nWelcome to OpenBB Platform CLI v1.0"
)
def test_get_flair_and_username(mock_session):
"""Test getting the flair and username."""
result = get_flair_and_username()
assert "testuser" in result
assert "rocket" in result #
@pytest.mark.parametrize(
"value, expected",
[
("10", 10),
("0", 0),
("-1", pytest.raises(argparse.ArgumentTypeError)),
("text", pytest.raises(ValueError)),
],
)
def test_check_non_negative(value, expected):
"""Test checking for a non-negative value."""
if isinstance(expected, int):
assert check_non_negative(value) == expected
else:
with expected:
check_non_negative(value)
@pytest.mark.parametrize(
"value, expected",
[
("1", 1),
("0", pytest.raises(argparse.ArgumentTypeError)),
("-1", pytest.raises(argparse.ArgumentTypeError)),
("text", pytest.raises(ValueError)),
],
)
def test_check_positive(value, expected):
"""Test checking for a positive value."""
if isinstance(expected, int):
assert check_positive(value) == expected
else:
with expected:
check_positive(value)
def test_get_user_agent():
"""Test getting the user agent."""
result = get_user_agent()
assert result.startswith("Mozilla/5.0")

View File

@@ -0,0 +1,72 @@
"""Test the Models Settings module."""
from unittest.mock import mock_open, patch
from openbb_cli.models.settings import Settings
# pylint: disable=unused-argument
def test_default_values():
"""Test the default values of the settings model."""
settings = Settings()
assert settings.VERSION == "1.0.0"
assert settings.TEST_MODE is False
assert settings.DEBUG_MODE is False
assert settings.DEV_BACKEND is False
assert settings.FILE_OVERWRITE is False
assert settings.SHOW_VERSION is True
assert settings.USE_INTERACTIVE_DF is True
assert settings.USE_CLEAR_AFTER_CMD is False
assert settings.USE_DATETIME is True
assert settings.USE_PROMPT_TOOLKIT is True
assert settings.ENABLE_EXIT_AUTO_HELP is True
assert settings.REMEMBER_CONTEXTS is True
assert settings.ENABLE_RICH_PANEL is True
assert settings.TOOLBAR_HINT is True
assert settings.SHOW_MSG_OBBJECT_REGISTRY is False
assert settings.TIMEZONE == "America/New_York"
assert settings.FLAIR == ":openbb"
assert settings.PREVIOUS_USE is False
assert settings.N_TO_KEEP_OBBJECT_REGISTRY == 10
assert settings.N_TO_DISPLAY_OBBJECT_REGISTRY == 5
assert settings.RICH_STYLE == "dark"
assert settings.ALLOWED_NUMBER_OF_ROWS == 20
assert settings.ALLOWED_NUMBER_OF_COLUMNS == 5
assert settings.HUB_URL == "https://my.openbb.co"
assert settings.BASE_URL == "https://payments.openbb.co"
# Test __repr__ output
def test_repr():
"""Test the __repr__ method of the settings model."""
settings = Settings()
repr_str = settings.__repr__() # pylint: disable=C2801
assert "Settings\n\n" in repr_str
assert "VERSION: 1.0.0" in repr_str
# Test loading from environment variables
@patch(
"openbb_cli.models.settings.dotenv_values",
return_value={"OPENBB_TEST_MODE": "True", "OPENBB_VERSION": "2.0.0"},
)
def test_from_env(mock_dotenv_values):
"""Test loading settings from environment variables."""
settings = Settings.from_env({}) # type: ignore
assert settings["TEST_MODE"] == "True"
assert settings["VERSION"] == "2.0.0"
# Test setting an item and updating .env
@patch("openbb_cli.models.settings.set_key")
@patch(
"openbb_cli.models.settings.open",
new_callable=mock_open,
read_data="TEST_MODE=False\n",
)
def test_set_item(mock_file, mock_set_key):
"""Test setting an item and updating the .env file."""
settings = Settings()
settings.set_item("TEST_MODE", True)
assert settings.TEST_MODE is True

44
cli/tests/test_session.py Normal file
View File

@@ -0,0 +1,44 @@
"Test the Session class."
from unittest.mock import MagicMock, patch
import pytest
from openbb_cli.models.settings import Settings
from openbb_cli.session import Session, sys
# pylint: disable=redefined-outer-name, unused-argument, protected-access
def mock_isatty(return_value):
"""Mock the isatty method."""
original_isatty = sys.stdin.isatty
sys.stdin.isatty = MagicMock(return_value=return_value) # type: ignore
return original_isatty
@pytest.fixture
def session():
"""Session fixture."""
return Session()
def test_session_initialization(session):
"""Test the initialization of the Session class."""
assert session.settings is not None
assert session.style is not None
assert session.console is not None
assert session.obbject_registry is not None
assert isinstance(session.settings, Settings)
@patch("sys.stdin.isatty", return_value=True)
def test_get_prompt_session_true(mock_isatty, session):
"Test get_prompt_session method."
prompt_session = session._get_prompt_session()
assert prompt_session is not None
@patch("sys.stdin.isatty", return_value=False)
def test_get_prompt_session_false(mock_isatty, session):
"Test get_prompt_session method."
prompt_session = session._get_prompt_session()
assert prompt_session is None