From 1fbc1869fdb38765e54974877d16a5e74d8d0e6c Mon Sep 17 00:00:00 2001 From: Romulo Quidute Filho Date: Mon, 14 Sep 2026 14:36:44 -0300 Subject: [PATCH] Add `delete` command to test-run-execution CLI group (#1112) The backend exposes DELETE /api/v1/test_run_executions/{id} to remove a test run execution, but no CLI command called it. Adds `test-run-execution delete --id `, mirroring `project delete`'s confirmation pattern: prompts for confirmation unless --yes is passed, then calls remove_test_run_execution_api_v1_test_run_executions__id__delete and reports success or surfaces the API error. --- tests/test_test_run_execution_delete.py | 92 +++++++++++++++++++++++++ th_cli/commands/test_run_execution.py | 53 +++++++++++++- 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 tests/test_test_run_execution_delete.py diff --git a/tests/test_test_run_execution_delete.py b/tests/test_test_run_execution_delete.py new file mode 100644 index 0000000..cc1ba52 --- /dev/null +++ b/tests/test_test_run_execution_delete.py @@ -0,0 +1,92 @@ +# +# Copyright (c) 2026 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Tests for the `test-run-execution delete` command.""" + +from unittest.mock import Mock, patch + +import pytest +from click.testing import CliRunner + +from th_cli.api_lib_autogen.exceptions import UnexpectedResponse +from th_cli.commands.test_run_execution import test_run_execution + + +@pytest.mark.unit +@pytest.mark.cli +class TestDeleteTestRunExecutionCommand: + """Test cases for the `test-run-execution delete` command.""" + + def test_delete_success_with_yes_flag(self, cli_runner: CliRunner, mock_sync_apis: Mock) -> None: + """Test successful deletion with --yes flag skips confirmation.""" + api = mock_sync_apis.test_run_executions_api + api.remove_test_run_execution_api_v1_test_run_executions__id__delete.return_value = None + + with patch("th_cli.commands.test_run_execution.SyncApis", return_value=mock_sync_apis): + result = cli_runner.invoke(test_run_execution, ["delete", "--id", "1", "--yes"]) + + assert result.exit_code == 0 + assert "Test run execution 1 was deleted." in result.output + api.remove_test_run_execution_api_v1_test_run_executions__id__delete.assert_called_once_with(id=1) + + def test_delete_success_with_confirmation(self, cli_runner: CliRunner, mock_sync_apis: Mock) -> None: + """Test successful deletion when the user confirms the prompt.""" + api = mock_sync_apis.test_run_executions_api + api.remove_test_run_execution_api_v1_test_run_executions__id__delete.return_value = None + + with patch("th_cli.commands.test_run_execution.SyncApis", return_value=mock_sync_apis): + result = cli_runner.invoke(test_run_execution, ["delete", "--id", "1"], input="y\n") + + assert result.exit_code == 0 + assert "Test run execution 1 was deleted." in result.output + + def test_delete_abort_on_no_confirmation(self, cli_runner: CliRunner, mock_sync_apis: Mock) -> None: + """Test deletion is aborted when the user declines confirmation.""" + with patch("th_cli.commands.test_run_execution.SyncApis", return_value=mock_sync_apis): + result = cli_runner.invoke(test_run_execution, ["delete", "--id", "1"], input="n\n") + + assert result.exit_code == 0 # Aborted, not an error + assert "Operation cancelled." in result.output + mock_sync_apis.test_run_executions_api.remove_test_run_execution_api_v1_test_run_executions__id__delete.assert_not_called() # noqa: E501 + + def test_delete_api_error(self, cli_runner: CliRunner, mock_sync_apis: Mock) -> None: + """Test that an API error is surfaced to the user.""" + api = mock_sync_apis.test_run_executions_api + api.remove_test_run_execution_api_v1_test_run_executions__id__delete.side_effect = UnexpectedResponse( + status_code=404, + content=b"Not Found", + ) + + with patch("th_cli.commands.test_run_execution.SyncApis", return_value=mock_sync_apis): + result = cli_runner.invoke(test_run_execution, ["delete", "--id", "1", "--yes"]) + + assert result.exit_code == 1 + assert "Error: Failed to delete test run execution ID '1' (Status: 404) - Not Found" in result.output + + def test_delete_requires_id(self, cli_runner: CliRunner) -> None: + """The --id parameter is required.""" + result = cli_runner.invoke(test_run_execution, ["delete"]) + + assert result.exit_code != 0 + assert "Missing option" in result.output or "--id" in result.output + + def test_delete_help_message(self, cli_runner: CliRunner) -> None: + """Test the help message for the delete command.""" + result = cli_runner.invoke(test_run_execution, ["delete", "--help"]) + + assert result.exit_code == 0 + assert "delete" in result.output + assert "--id" in result.output + assert "--yes" in result.output diff --git a/th_cli/commands/test_run_execution.py b/th_cli/commands/test_run_execution.py index 8de203b..39bd804 100644 --- a/th_cli/commands/test_run_execution.py +++ b/th_cli/commands/test_run_execution.py @@ -20,7 +20,15 @@ from th_cli.api_lib_autogen.api_client import SyncApis from th_cli.api_lib_autogen.exceptions import UnexpectedResponse from th_cli.client import get_client -from th_cli.colorize import colorize_cmd_help, colorize_header, colorize_help, colorize_state, italic +from th_cli.colorize import ( + colorize_cmd_help, + colorize_error, + colorize_header, + colorize_help, + colorize_state, + colorize_success, + italic, +) from th_cli.exceptions import CLIError, handle_api_error from th_cli.utils import __print_json @@ -248,6 +256,49 @@ def pics_export(id: int, output_file: str) -> None: raise # Re-raise CLI Errors as-is +@test_run_execution.command( + name="delete", + short_help=colorize_help("Delete a test run execution"), + help=colorize_cmd_help("delete", "Delete a test run execution"), +) +@click.option( + "--id", + "-i", + required=True, + type=int, + help=colorize_help("Test Run Execution ID to delete"), +) +@click.option( + "--yes", + "-y", + is_flag=True, + help=colorize_help("Delete the test run execution without confirmation"), +) +def delete(id: int, yes: bool) -> None: + """Delete a test run execution""" + if not yes: + if not click.confirm(colorize_error("Are you sure you want to delete the test run execution?")): + click.echo("Operation cancelled.") + return + + try: + with closing(get_client()) as client: + sync_apis = SyncApis(client) + __delete_test_run_execution(sync_apis, id) + + except CLIError: + raise # Re-raise CLI Errors as-is + + +def __delete_test_run_execution(sync_apis: SyncApis, id: int) -> None: + try: + test_run_execution_api = sync_apis.test_run_executions_api + test_run_execution_api.remove_test_run_execution_api_v1_test_run_executions__id__delete(id=id) + click.echo(colorize_success(f"Test run execution {id} was deleted.")) + except UnexpectedResponse as e: + handle_api_error(e, f"delete test run execution ID '{id}'") + + def __test_run_execution_by_id(sync_apis: SyncApis, id: int, json: bool) -> None: try: test_run_execution_api = sync_apis.test_run_executions_api