From d60716277858531de81d92658fb5b58d8766de06 Mon Sep 17 00:00:00 2001 From: janosmurai Date: Tue, 30 Apr 2024 17:28:44 +0200 Subject: [PATCH] The del-task command implemented. --- dem/cli/command/add_task_cmd.py | 3 ++ dem/cli/command/del_task_cmd.py | 26 ++++++++++++ dem/cli/main.py | 34 +++++++++++++++- dem/core/dev_env.py | 22 ++++++++++ docs/commands.md | 15 +++++++ mkdocs.yml | 2 + tests/cli/test_add_task_cmd.py | 2 +- tests/cli/test_del_task_cmd.py | 71 +++++++++++++++++++++++++++++++++ tests/cli/test_main.py | 34 ++++++++++++++++ tests/core/test_dev_env.py | 45 +++++++++++++++++++++ 10 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 dem/cli/command/del_task_cmd.py create mode 100644 tests/cli/test_del_task_cmd.py diff --git a/dem/cli/command/add_task_cmd.py b/dem/cli/command/add_task_cmd.py index 3afa75c..03b8980 100644 --- a/dem/cli/command/add_task_cmd.py +++ b/dem/cli/command/add_task_cmd.py @@ -1,3 +1,6 @@ +"""add-task CLI command implementation.""" +# dem/cli/command/add_task_cmd.py + from dem.core.platform import Platform from dem.cli.console import stderr diff --git a/dem/cli/command/del_task_cmd.py b/dem/cli/command/del_task_cmd.py new file mode 100644 index 0000000..e4aefbb --- /dev/null +++ b/dem/cli/command/del_task_cmd.py @@ -0,0 +1,26 @@ +"""del-task CLI command implementation.""" +# dem/cli/command/del_task_cmd.py + +from dem.core.platform import Platform +from dem.cli.console import stderr + +def execute(platform: Platform, dev_env_name: str, task_name: str) -> None: + """ Delete a task from a Development Environment. + + Args: + platform -- the Platform + dev_env_name -- the Development Environment name + task_name -- the task name + """ + dev_env = platform.get_dev_env_by_name(dev_env_name) + if dev_env is None: + stderr.print(f"[red]Error: Development Environment '{dev_env_name}' not found![/]") + return + + try: + dev_env.del_task(task_name) + except KeyError as e: + stderr.print(f"[red] Error: {str(e)}[/]") + return + + platform.flush_dev_env_properties() \ No newline at end of file diff --git a/dem/cli/main.py b/dem/cli/main.py index 42aad98..851fcf1 100644 --- a/dem/cli/main.py +++ b/dem/cli/main.py @@ -10,7 +10,8 @@ delete_cmd, rename_cmd, run_cmd, export_cmd, clone_cmd, add_reg_cmd, \ list_reg_cmd, del_reg_cmd, add_cat_cmd, list_cat_cmd, del_cat_cmd, \ add_host_cmd, set_default_cmd, uninstall_cmd, install_cmd, assign_cmd, \ - init_cmd, list_host_cmd, del_host_cmd, list_tools_cmd, add_task_cmd + init_cmd, list_host_cmd, del_host_cmd, list_tools_cmd, add_task_cmd, \ + del_task_cmd from dem.cli.console import stdout from dem.core.platform import Platform from dem.core.exceptions import InternalError @@ -89,6 +90,23 @@ def autocomplete_host_name(incomplete: str) -> Generator: if host_config["name"].startswith(incomplete) or (incomplete == ""): yield host_config["name"] +def autocomplete_task_name(ctx: typer.Context, incomplete: str) -> Generator: + """ + Autocomplete the input Task name with the available matching Task names. + + Return with the matching Task name by a Generator. + + Args: + incomplete -- the parameter the user supplied so far when the tab was pressed + """ + dev_env_name = ctx.params.get("dev_env_name", None) + if platform is not None and dev_env_name is not None: + for dev_env in platform.local_dev_envs: + if dev_env.name == dev_env_name: + for task_name in dev_env.tasks: + if task_name.startswith(incomplete) or (incomplete == ""): + yield task_name + # DEM commands @typer_cli.command() def add_task(dev_env_name: Annotated[str, typer.Argument(help="Name of the Development Environment to add the task to.", @@ -105,6 +123,20 @@ def add_task(dev_env_name: Annotated[str, typer.Argument(help="Name of the Devel add_task_cmd.execute(platform, dev_env_name, task_name, command) else: raise InternalError("Error: The platform hasn't been initialized properly!") + +@typer_cli.command() +def del_task(dev_env_name: Annotated[str, typer.Argument(help="Name of the Development Environment to delete the task from.", + autocompletion=autocomplete_dev_env_name)], + task_name: Annotated[str, typer.Argument(help="Name of the task to delete.", + autocompletion=autocomplete_task_name)]) -> None: + """ + Delete a task from the Development Environment. + """ + if platform: + del_task_cmd.execute(platform, dev_env_name, task_name) + else: + raise InternalError("Error: The platform hasn't been initialized properly!") + @typer_cli.command() def set_default(dev_env_name: Annotated[str, typer.Argument(help="The name of the Development Environment to set as default.", diff --git a/dem/core/dev_env.py b/dem/core/dev_env.py index 0356500..16d8597 100755 --- a/dem/core/dev_env.py +++ b/dem/core/dev_env.py @@ -69,8 +69,30 @@ def assign_tool_image_instances(self, tool_images: ToolImages) -> None: self.tool_images.append(tool_image) def add_task(self, task_name: str, command: str) -> None: + """ Add a task to the Development Environment. + + If the task already exists, it will be overwritten. + + Args: + task_name -- the task name + command -- the command + """ self.tasks[task_name] = command + def del_task(self, task_name: str) -> None: + """ Delete a task from the Development Environment. + + Args: + task_name -- the task name + + Exceptions: + KeyError -- if the task doesn't exist + """ + if task_name in self.tasks: + del self.tasks[task_name] + else: + raise KeyError(f"Task [bold]{task_name}[/] not found.") + def get_tool_image_status(self) -> Status: """ Get the status of the Tool Images. diff --git a/docs/commands.md b/docs/commands.md index 850bdd6..0b1a272 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -136,6 +136,21 @@ Delete the Dev Env descriptor from the local descriptor storage. If the Dev Env --- +## **`dem del-task DEV_ENV_NAME TASK_NAME`** + +**Description:** + +Delete a task from the Development Environment. + +**Arguments:** + +| Argument | Description | Required | +|------------------|---------------------------------------------------------|:---------------:| +| `DEV_ENV_NAME` | Name of the Development Environment. | :material-check:| +| `TASK_NAME` | Name of the task to delete. | :material-check:| + +--- + ## **`dem export DEV_ENV_NAME [PATH_TO_EXPORT]`** **Description:** diff --git a/mkdocs.yml b/mkdocs.yml index 046414c..80b9e05 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -94,11 +94,13 @@ nav: - 'basics.md' - Commands: - 'DevEnv Management': + - 'add-task': 'commands/#dem-add-task-dev_env_name-task_name-command' - 'assign': 'commands/#dem-assign-dev_env_name-project_path' - 'clone': 'commands/#dem-clone-dev_env_name' - 'cp': 'commands/#dem-cp-dev_env_name-new_dev_env_name' - 'create': 'commands/#dem-create-dev_env_name' - 'delete': 'commands/#dem-delete-dev_env_name' + - 'del-task': 'commands/#dem-del-task-dev_env_name-task_name' - 'export': 'commands/#dem-export-dev_env_name-path_to_export' - 'import': 'commands/#dem-import-path_to_dev_env' - 'info': 'commands/#dem-info-dev_env_name-options-catalog_names' diff --git a/tests/cli/test_add_task_cmd.py b/tests/cli/test_add_task_cmd.py index f578808..33400de 100644 --- a/tests/cli/test_add_task_cmd.py +++ b/tests/cli/test_add_task_cmd.py @@ -7,7 +7,7 @@ # Test framework from typer.testing import CliRunner -from unittest.mock import patch, MagicMock, call +from unittest.mock import patch, MagicMock ## Global test variables diff --git a/tests/cli/test_del_task_cmd.py b/tests/cli/test_del_task_cmd.py new file mode 100644 index 0000000..66de87c --- /dev/null +++ b/tests/cli/test_del_task_cmd.py @@ -0,0 +1,71 @@ +"""Unit tests for the del-task CLI command.""" +# tests/cli/test_del_task_cmd.py + +# Unit under test: +import dem.cli.main as main +import dem.cli.command.del_task_cmd as del_task_cmd + +# Test framework +from typer.testing import CliRunner +from unittest.mock import patch, MagicMock, call + +## Global test variables + +# In order to test stdout and stderr separately, the stderr can't be mixed into +# the stdout. +runner = CliRunner(mix_stderr=False) + +def test_del_task_cmd() -> None: + # Setup + mock_platform = MagicMock() + main.platform = mock_platform + + mock_dev_env = MagicMock() + mock_platform.get_dev_env_by_name.return_value = mock_dev_env + + # Run + result = runner.invoke(main.typer_cli, ["del-task", "my-dev-env", "my-task"]) + + # Check + assert result.exit_code == 0 + + mock_platform.get_dev_env_by_name.assert_called_once_with("my-dev-env") + mock_dev_env.del_task.assert_called_once_with("my-task") + mock_platform.flush_dev_env_properties.assert_called_once() + +@patch("dem.cli.command.del_task_cmd.stderr") +def test_del_task_cmd_dev_env_not_found(mock_stderr: MagicMock) -> None: + # Setup + mock_platform = MagicMock() + main.platform = mock_platform + + mock_platform.get_dev_env_by_name.return_value = None + + # Run + result = runner.invoke(main.typer_cli, ["del-task", "my-dev-env", "my-task"]) + + # Check + assert result.exit_code == 0 + + mock_platform.get_dev_env_by_name.assert_called_once_with("my-dev-env") + mock_stderr.print.assert_called_once_with("[red]Error: Development Environment 'my-dev-env' not found![/]") + +@patch("dem.cli.command.del_task_cmd.stderr") +def test_del_task_cmd_task_not_found(mock_stderr: MagicMock) -> None: + # Setup + mock_platform = MagicMock() + main.platform = mock_platform + + mock_dev_env = MagicMock() + mock_platform.get_dev_env_by_name.return_value = mock_dev_env + mock_dev_env.del_task.side_effect = KeyError("Task [bold]my-task[/] not found.") + + # Run + result = runner.invoke(main.typer_cli, ["del-task", "my-dev-env", "my-task"]) + + # Check + assert result.exit_code == 0 + + mock_platform.get_dev_env_by_name.assert_called_once_with("my-dev-env") + mock_dev_env.del_task.assert_called_once_with("my-task") + mock_stderr.print.assert_called_once_with("[red] Error: \'Task [bold]my-task[/] not found.\'[/]") \ No newline at end of file diff --git a/tests/cli/test_main.py b/tests/cli/test_main.py index 78cecdb..1ed1ef6 100644 --- a/tests/cli/test_main.py +++ b/tests/cli/test_main.py @@ -132,6 +132,39 @@ def test_autocomplete_host_name() -> None: mock_platform.hosts.list_host_configs.assert_called_once() +def test_autocomplete_task_name() -> None: + # Test setup + mock_platform = MagicMock() + mock_dev_env1 = MagicMock() + mock_dev_env1.name = "dev_env_1" + mock_dev_env1.tasks = { + "test": "test", + "task": "command" + } + mock_dev_env2 = MagicMock() + mock_dev_env2.name = "dev_env_2" + mock_platform.local_dev_envs = [ + mock_dev_env1, + mock_dev_env2 + ] + + mock_ctx = MagicMock() + mock_ctx.params = { + "dev_env_name": "dev_env_1" + } + + main.platform = mock_platform + + expected_completions = [mock_dev_env1.tasks["test"]] + + # Run unit under test + actual_completions = [] + for result in main.autocomplete_task_name(mock_ctx, "tes"): + actual_completions.append(result) + + # Check expectations + assert expected_completions == actual_completions + @patch("dem.cli.main.__app_name__", "axem-dem") @patch("dem.cli.main.stdout.print") @patch("dem.cli.main.importlib.metadata.version") @@ -199,6 +232,7 @@ def test_platform_not_initialized() -> None: units_to_test = { main.add_task: [test_dev_env_name, test_name, test_command], + main.del_task: [test_dev_env_name, test_name], main.set_default: [test_dev_env_name], main.list_: [], main.list_tools: [], diff --git a/tests/core/test_dev_env.py b/tests/core/test_dev_env.py index cad901a..3b9c7a1 100644 --- a/tests/core/test_dev_env.py +++ b/tests/core/test_dev_env.py @@ -166,6 +166,51 @@ def test_DevEnv_add_task() -> None: # Check expectations assert test_dev_env.tasks[test_task_name] == test_command +def test_DevEnv_del_task() -> None: + # Test setup + test_descriptor = { + "name": "test_name", + "installed": "True", + "tools": [MagicMock()], + "tasks": { + "test_task_name1": "test_task_command1", + "test_task_name2": "test_task_command2", + "test_task_name3": "test_task_command3" + } + } + test_dev_env = dev_env.DevEnv(test_descriptor) + + test_task_name = "test_task_name2" + + # Run unit under test + test_dev_env.del_task(test_task_name) + + # Check expectations + assert test_task_name not in test_dev_env.tasks + +def test_DevEnv_del_task_not_existing() -> None: + # Test setup + test_descriptor = { + "name": "test_name", + "installed": "True", + "tools": [MagicMock()], + "tasks": { + "test_task_name1": "test_task_command1", + "test_task_name2": "test_task_command2", + "test_task_name3": "test_task_command3" + } + } + test_dev_env = dev_env.DevEnv(test_descriptor) + + test_task_name = "test_task_name4" + + # Run unit under test + with pytest.raises(KeyError) as exc_info: + test_dev_env.del_task(test_task_name) + + # Check expectations + assert str(exc_info.value) == f"\'Task [bold]{test_task_name}[/] not found.\'" + @patch.object(dev_env.DevEnv, "__init__") def test_DevEnv_get_tool_image_status(mock___init__: MagicMock) -> None: # Test setup