From 570a0673591fe1bcbc2e1a728391e89a33ca1302 Mon Sep 17 00:00:00 2001 From: Romulo Quidute Filho Date: Tue, 15 Sep 2026 08:44:21 -0300 Subject: [PATCH 1/2] Add `rename` command to test-run-execution CLI group (#1113) The backend exposes PUT /api/v1/test_run_executions/{id}/rename to rename a test run execution, but no CLI command called it. Adds `test-run-execution rename --id --name `, calling rename_test_run_execution_api_v1_test_run_executions__id__rename_put and reporting success or surfacing the API error, mirroring the `delete` command added for #1112. --- tests/test_test_run_execution_rename.py | 81 +++++++++++++++++++++++++ th_cli/commands/test_run_execution.py | 41 +++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 tests/test_test_run_execution_rename.py diff --git a/tests/test_test_run_execution_rename.py b/tests/test_test_run_execution_rename.py new file mode 100644 index 0000000..126dc0a --- /dev/null +++ b/tests/test_test_run_execution_rename.py @@ -0,0 +1,81 @@ +# +# 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 rename` 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 TestRenameTestRunExecutionCommand: + """Test cases for the `test-run-execution rename` command.""" + + def test_rename_success(self, cli_runner: CliRunner, mock_sync_apis: Mock) -> None: + """Test successful rename.""" + api = mock_sync_apis.test_run_executions_api + api.rename_test_run_execution_api_v1_test_run_executions__id__rename_put.return_value = None + + with patch("th_cli.commands.test_run_execution.SyncApis", return_value=mock_sync_apis): + result = cli_runner.invoke(test_run_execution, ["rename", "--id", "1", "--name", "New Name"]) + + assert result.exit_code == 0 + assert "Test run execution 1 was renamed to 'New Name'." in result.output + api.rename_test_run_execution_api_v1_test_run_executions__id__rename_put.assert_called_once_with( + id=1, new_execution_name="New Name" + ) + + def test_rename_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.rename_test_run_execution_api_v1_test_run_executions__id__rename_put.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, ["rename", "--id", "1", "--name", "New Name"]) + + assert result.exit_code == 1 + assert "Error: Failed to rename test run execution ID '1' (Status: 404) - Not Found" in result.output + + def test_rename_requires_id(self, cli_runner: CliRunner) -> None: + """The --id parameter is required.""" + result = cli_runner.invoke(test_run_execution, ["rename", "--name", "New Name"]) + + assert result.exit_code != 0 + assert "Missing option" in result.output or "--id" in result.output + + def test_rename_requires_name(self, cli_runner: CliRunner) -> None: + """The --name parameter is required.""" + result = cli_runner.invoke(test_run_execution, ["rename", "--id", "1"]) + + assert result.exit_code != 0 + assert "Missing option" in result.output or "--name" in result.output + + def test_rename_help_message(self, cli_runner: CliRunner) -> None: + """Test the help message for the rename command.""" + result = cli_runner.invoke(test_run_execution, ["rename", "--help"]) + + assert result.exit_code == 0 + assert "rename" in result.output + assert "--id" in result.output + assert "--name" in result.output diff --git a/th_cli/commands/test_run_execution.py b/th_cli/commands/test_run_execution.py index 39bd804..b716770 100644 --- a/th_cli/commands/test_run_execution.py +++ b/th_cli/commands/test_run_execution.py @@ -299,6 +299,47 @@ def __delete_test_run_execution(sync_apis: SyncApis, id: int) -> None: handle_api_error(e, f"delete test run execution ID '{id}'") +@test_run_execution.command( + name="rename", + short_help=colorize_help("Rename a test run execution"), + help=colorize_cmd_help("rename", "Rename a test run execution"), +) +@click.option( + "--id", + "-i", + required=True, + type=int, + help=colorize_help("Test Run Execution ID to rename"), +) +@click.option( + "--name", + "-n", + required=True, + type=str, + help=colorize_help("New name for the test run execution"), +) +def rename(id: int, name: str) -> None: + """Rename a test run execution""" + try: + with closing(get_client()) as client: + sync_apis = SyncApis(client) + __rename_test_run_execution(sync_apis, id, name) + + except CLIError: + raise # Re-raise CLI Errors as-is + + +def __rename_test_run_execution(sync_apis: SyncApis, id: int, name: str) -> None: + try: + test_run_execution_api = sync_apis.test_run_executions_api + test_run_execution_api.rename_test_run_execution_api_v1_test_run_executions__id__rename_put( + id=id, new_execution_name=name + ) + click.echo(colorize_success(f"Test run execution {id} was renamed to '{name}'.")) + except UnexpectedResponse as e: + handle_api_error(e, f"rename 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 From a998bbc0c0a9a3aaf3f930e8f34f6cae9849b9a0 Mon Sep 17 00:00:00 2001 From: Romulo Quidute Filho Date: Tue, 15 Sep 2026 09:46:53 -0300 Subject: [PATCH 2/2] Echo the API response's title in rename success message The backend strips whitespace from new_execution_name before persisting, so echoing the raw --name input could show a different value than what was actually saved (e.g. --name " New Name " persists as "New Name"). Use the response's .title instead, matching how repeat/import already handle this. Addresses review feedback from @oxesoft on #121. --- tests/test_test_run_execution_rename.py | 21 ++++++++++++++++++++- th_cli/commands/test_run_execution.py | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/test_test_run_execution_rename.py b/tests/test_test_run_execution_rename.py index 126dc0a..fda1240 100644 --- a/tests/test_test_run_execution_rename.py +++ b/tests/test_test_run_execution_rename.py @@ -20,6 +20,7 @@ import pytest from click.testing import CliRunner +from th_cli.api_lib_autogen import models as api_models from th_cli.api_lib_autogen.exceptions import UnexpectedResponse from th_cli.commands.test_run_execution import test_run_execution @@ -32,7 +33,9 @@ class TestRenameTestRunExecutionCommand: def test_rename_success(self, cli_runner: CliRunner, mock_sync_apis: Mock) -> None: """Test successful rename.""" api = mock_sync_apis.test_run_executions_api - api.rename_test_run_execution_api_v1_test_run_executions__id__rename_put.return_value = None + api.rename_test_run_execution_api_v1_test_run_executions__id__rename_put.return_value = ( + api_models.TestRunExecutionWithChildren(id=1, title="New Name", state=api_models.TestStateEnum.pending) + ) with patch("th_cli.commands.test_run_execution.SyncApis", return_value=mock_sync_apis): result = cli_runner.invoke(test_run_execution, ["rename", "--id", "1", "--name", "New Name"]) @@ -43,6 +46,22 @@ def test_rename_success(self, cli_runner: CliRunner, mock_sync_apis: Mock) -> No id=1, new_execution_name="New Name" ) + def test_rename_echoes_stripped_title_from_response(self, cli_runner: CliRunner, mock_sync_apis: Mock) -> None: + """The backend strips whitespace from new_execution_name before persisting; the + success message must echo the response's title, not the raw --name input, so it + reflects what was actually saved.""" + api = mock_sync_apis.test_run_executions_api + api.rename_test_run_execution_api_v1_test_run_executions__id__rename_put.return_value = ( + api_models.TestRunExecutionWithChildren(id=1, title="New Name", state=api_models.TestStateEnum.pending) + ) + + with patch("th_cli.commands.test_run_execution.SyncApis", return_value=mock_sync_apis): + result = cli_runner.invoke(test_run_execution, ["rename", "--id", "1", "--name", " New Name "]) + + assert result.exit_code == 0 + assert "Test run execution 1 was renamed to 'New Name'." in result.output + assert "' New Name '" not in result.output + def test_rename_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 diff --git a/th_cli/commands/test_run_execution.py b/th_cli/commands/test_run_execution.py index b716770..dafda51 100644 --- a/th_cli/commands/test_run_execution.py +++ b/th_cli/commands/test_run_execution.py @@ -332,10 +332,10 @@ def rename(id: int, name: str) -> None: def __rename_test_run_execution(sync_apis: SyncApis, id: int, name: str) -> None: try: test_run_execution_api = sync_apis.test_run_executions_api - test_run_execution_api.rename_test_run_execution_api_v1_test_run_executions__id__rename_put( + response = test_run_execution_api.rename_test_run_execution_api_v1_test_run_executions__id__rename_put( id=id, new_execution_name=name ) - click.echo(colorize_success(f"Test run execution {id} was renamed to '{name}'.")) + click.echo(colorize_success(f"Test run execution {id} was renamed to '{response.title}'.")) except UnexpectedResponse as e: handle_api_error(e, f"rename test run execution ID '{id}'")