From f4acde443804aa5fb85415771a998b35660780db Mon Sep 17 00:00:00 2001 From: acuanico-tr-galt Date: Tue, 16 Jun 2026 17:31:05 +0800 Subject: [PATCH 1/3] TRCLI-282: Added statuses command for listing case and test statuses --- trcli/api/api_request_handler.py | 2 + trcli/api/status_handler.py | 46 ++++++++ trcli/commands/cmd_statuses.py | 174 +++++++++++++++++++++++++++++++ trcli/constants.py | 2 + 4 files changed, 224 insertions(+) create mode 100644 trcli/api/status_handler.py create mode 100644 trcli/commands/cmd_statuses.py diff --git a/trcli/api/api_request_handler.py b/trcli/api/api_request_handler.py index 5fc1656..562fdc2 100644 --- a/trcli/api/api_request_handler.py +++ b/trcli/api/api_request_handler.py @@ -17,6 +17,7 @@ from trcli.api.case_handler import CaseHandler from trcli.api.plan_handler import PlanHandler from trcli.api.milestone_handler import MilestoneHandler +from trcli.api.status_handler import StatusHandler from trcli.cli import Environment from trcli.constants import ( ProjectErrors, @@ -88,6 +89,7 @@ def __init__( ) self.plan_handler = PlanHandler(api_client, environment) self.milestone_handler = MilestoneHandler(api_client) + self.status_handler = StatusHandler(api_client) # BDD case cache for feature name matching (shared by CucumberParser and JunitParser) # Structure: {"{project_id}_{suite_id}": {normalized_name: [case_dict, case_dict, ...]}} diff --git a/trcli/api/status_handler.py b/trcli/api/status_handler.py new file mode 100644 index 0000000..431b959 --- /dev/null +++ b/trcli/api/status_handler.py @@ -0,0 +1,46 @@ +""" +StatusHandler - Handles all status-related operations for TestRail + +It manages status query operations including: +- Retrieving all test result statuses +- Retrieving all case statuses (TestRail Enterprise 7.3+) +""" + +from beartype.typing import Tuple + +from trcli.api.api_client import APIClient + + +class StatusHandler: + """Handles all status-related operations for TestRail""" + + def __init__(self, client: APIClient): + """ + Initialize the StatusHandler + + :param client: APIClient instance for making API calls + """ + self.client = client + + def get_statuses(self, project_id: int) -> Tuple[list, str]: + """ + Retrieve all test result statuses for a project + + :param project_id: TestRail project ID + :returns: Tuple with (statuses_list, error_message) + """ + response = self.client.send_get(f"get_statuses/{project_id}") + if response.error_message: + return [], response.error_message + return response.response_text, "" + + def get_case_statuses(self) -> Tuple[list, str]: + """ + Retrieve all case statuses (TestRail Enterprise 7.3+) + + :returns: Tuple with (case_statuses_list, error_message) + """ + response = self.client.send_get("get_case_statuses") + if response.error_message: + return [], response.error_message + return response.response_text, "" diff --git a/trcli/commands/cmd_statuses.py b/trcli/commands/cmd_statuses.py new file mode 100644 index 0000000..d1eafd2 --- /dev/null +++ b/trcli/commands/cmd_statuses.py @@ -0,0 +1,174 @@ +import click +import json + +from trcli.api.project_based_client import ProjectBasedClient +from trcli.cli import pass_environment, CONTEXT_SETTINGS, Environment +from trcli.data_classes.dataclass_testrail import TestRailSuite + + +def print_config(env: Environment, action: str): + env.log( + f"Statuses {action} Execution Parameters" + f"\n> TestRail instance: {env.host} (user: {env.username})" + f"\n> Project: {env.project if env.project else env.project_id}" + ) + + +@click.group(context_settings=CONTEXT_SETTINGS) +@click.pass_context +@pass_environment +def cli(environment: Environment, context: click.Context, *args, **kwargs): + """Manage test statuses in TestRail""" + environment.cmd = "statuses" + environment.set_parameters(context) + + +@cli.command() +@click.option("--json-output", is_flag=True, help="Output statuses as raw JSON from API.") +@click.option("--show-all-fields", is_flag=True, help="Show all fields for each status.") +@click.pass_context +@pass_environment +def all( + environment: Environment, + context: click.Context, + json_output: bool, + show_all_fields: bool, + *args, + **kwargs, +): + """List all test result statuses from TestRail""" + environment.check_for_required_parameters() + + print_config(environment, "List (Test Result Statuses)") + + # Create ProjectBasedClient to resolve project + project_client = ProjectBasedClient( + environment=environment, + suite=TestRailSuite(name=environment.suite_name, suite_id=environment.suite_id), + ) + + # Resolve project (converts name to ID if needed) + project_client.resolve_project() + + environment.log(f"Retrieving test result statuses for project ID {project_client.project.project_id}...") + + # Retrieve statuses using StatusHandler from ProjectBasedClient + statuses_data, error_message = project_client.api_request_handler.status_handler.get_statuses( + project_id=project_client.project.project_id + ) + + if error_message: + environment.elog(f"Error: Failed to retrieve statuses: {error_message}") + raise SystemExit(1) + + # Handle output format + if json_output: + # Output prettified JSON response + print(json.dumps(statuses_data, indent=2)) + else: + # Display statuses line by line + if not statuses_data: + environment.log("No statuses found.") + else: + environment.log(f"Found {len(statuses_data)} test result status(es):") + environment.log("") + + for status in statuses_data: + status_id = status.get("id", "N/A") + name = status.get("name", "N/A") + label = status.get("label", "N/A") + is_system = status.get("is_system", False) + is_untested = status.get("is_untested", False) + is_final = status.get("is_final", False) + + environment.log(f"Status ID: {status_id}") + environment.log(f" Name: {name}") + environment.log(f" Label: {label}") + environment.log(f" System Status: {'Yes' if is_system else 'No'}") + environment.log(f" Is Untested: {'Yes' if is_untested else 'No'}") + environment.log(f" Is Final: {'Yes' if is_final else 'No'}") + + if show_all_fields: + color_dark = status.get("color_dark", "N/A") + color_medium = status.get("color_medium", "N/A") + color_bright = status.get("color_bright", "N/A") + environment.log(f" Colors: Dark={color_dark}, Medium={color_medium}, Bright={color_bright}") + + environment.log("") + + environment.log("") + environment.log("Status listing completed successfully.") + + +@cli.command() +@click.option("--json-output", is_flag=True, help="Output case statuses as raw JSON from API.") +@click.option("--show-all-fields", is_flag=True, help="Show all fields for each case status.") +@click.pass_context +@pass_environment +def case( + environment: Environment, + context: click.Context, + json_output: bool, + show_all_fields: bool, + *args, + **kwargs, +): + """List all case statuses from TestRail (Enterprise 7.3+)""" + environment.check_for_required_parameters() + + print_config(environment, "List (Case Statuses)") + + # Create ProjectBasedClient (no need to resolve project for case statuses) + project_client = ProjectBasedClient( + environment=environment, + suite=TestRailSuite(name=environment.suite_name, suite_id=environment.suite_id), + ) + + environment.log("Retrieving case statuses...") + environment.log("Note: This command requires TestRail Enterprise 7.3 or later.") + + # Retrieve case statuses using StatusHandler from ProjectBasedClient + case_statuses_data, error_message = project_client.api_request_handler.status_handler.get_case_statuses() + + if error_message: + environment.elog(f"Error: Failed to retrieve case statuses: {error_message}") + environment.elog( + "Note: get_case_statuses requires TestRail Enterprise 7.3+. " + "If you have an older version, this endpoint may not be available." + ) + raise SystemExit(1) + + # Handle output format + if json_output: + # Output prettified JSON response + print(json.dumps(case_statuses_data, indent=2)) + else: + # Display case statuses line by line + if not case_statuses_data: + environment.log("No case statuses found.") + else: + environment.log(f"Found {len(case_statuses_data)} case status(es):") + environment.log("") + + for case_status in case_statuses_data: + case_status_id = case_status.get("case_status_id", "N/A") + name = case_status.get("name", "N/A") + is_default = case_status.get("is_default", False) + is_approved = case_status.get("is_approved", False) + + environment.log(f"Case Status ID: {case_status_id}") + environment.log(f" Name: {name}") + environment.log(f" Is Default: {'Yes' if is_default else 'No'}") + environment.log(f" Is Approved: {'Yes' if is_approved else 'No'}") + + if show_all_fields: + abbreviation = case_status.get("abbreviation") + if abbreviation: + environment.log(f" Abbreviation: {abbreviation}") + else: + environment.log(" Abbreviation: (none)") + + environment.log("") + + environment.log("") + environment.log("Case status listing completed successfully.") diff --git a/trcli/constants.py b/trcli/constants.py index ccfb3a0..8cc1872 100644 --- a/trcli/constants.py +++ b/trcli/constants.py @@ -96,6 +96,7 @@ sections=dict(**FAULT_MAPPING), runs=dict(**FAULT_MAPPING), milestones=dict(**FAULT_MAPPING), + statuses=dict(**FAULT_MAPPING), ) PROMPT_MESSAGES = dict( @@ -128,6 +129,7 @@ - plans: Query test plans - runs: Query test runs - milestones: Query milestones + - statuses: Query test statuses - results: Query and update test results""" MISSING_COMMAND_SLOGAN = """Usage: trcli [OPTIONS] COMMAND [ARGS]...\nTry 'trcli --help' for help. From 1ab7c31c455c84cc5d2099b4e75d74478dfb7bd3 Mon Sep 17 00:00:00 2001 From: acuanico-tr-galt Date: Tue, 16 Jun 2026 17:33:01 +0800 Subject: [PATCH 2/3] TRCLI-282: Updated README and CHANGELOG for statuses command, also updated unit tests --- CHANGELOG.MD | 1 + README.md | 152 ++++++++++++++++ tests/test_cmd_statuses.py | 297 +++++++++++++++++++++++++++++++ tests/test_data/cli_test_data.py | 1 + 4 files changed, 451 insertions(+) create mode 100644 tests/test_cmd_statuses.py diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 237c148..929822c 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -13,6 +13,7 @@ _released 06-24-2026 ### Added - **New `runs` command**: Query and retrieve test run information from TestRail with `get` and `list` subcommands. Provides execution metrics, status tracking, and configuration details. Note: Only returns standalone runs (not runs within test plans). - **New `milestones` command**: Query and retrieve milestone information from TestRail with `get` and `list` subcommands. Track project milestones, monitor completion status, due dates, and references. + - **New `statuses` command**: Query and retrieve status information from TestRail with `all`: List all test result statuses (Passed, Failed, Blocked, etc.) with system/custom flags, colors, and behavior properties and `case`: List all case statuses (Approved, Draft, etc.) for case workflow management (requires TestRail Enterprise 7.3+) ## [1.15.0] diff --git a/README.md b/README.md index 18912ac..eab6670 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ Commands: suites Manage test suites in TestRail runs Manage test runs in TestRail milestones Manage milestones in TestRail + statuses Manage test statuses in TestRail update Update TRCLI to the latest version from PyPI. ``` @@ -2186,6 +2187,157 @@ $ trcli -c config.yml milestones list --json-output | jq '.milestones[].name' $ trcli -c config.yml milestones list --show-all-fields ``` +### Statuses Command + +The TestRail CLI provides the `statuses` command for retrieving status information from TestRail. This includes both test result statuses (Passed, Failed, Blocked, etc.) and case statuses (Approved, Draft, etc.). These statuses are crucial for mapping test results correctly and managing test case workflows. + +#### Statuses Command Overview + +The `statuses` command supports two subcommands: + +| Subcommand | Purpose | API Endpoint | +|------------|---------|--------------| +| `all` | List all test result statuses | Retrieve available test result statuses (Passed, Failed, Blocked, etc.) | +| `case` | List all case statuses (Enterprise 7.3+) | Retrieve test case statuses (Approved, Draft, etc.) for case workflows | + +#### Reference + +```shell +$ trcli statuses --help + +Usage: trcli statuses [OPTIONS] COMMAND [ARGS]... + Manage test statuses in TestRail + +Options: + --help Show this message and exit. + +Commands: + all List all test result statuses from TestRail + case List all case statuses from TestRail (Enterprise 7.3+) +``` + +##### Listing Test Result Statuses + +List all test result statuses available for a project: + +```shell +# List all test result statuses (using config file) +$ trcli -c config.yml statuses all + +# List all statuses with all parameters +$ trcli statuses all \ + --host https://yourinstance.testrail.io \ + --username \ + --password \ + --project "Your Project" + +# Show all fields including color values +$ trcli -c config.yml statuses all --show-all-fields + +# JSON output for integration with other tools +$ trcli -c config.yml statuses all --json-output | jq '.[].name' +``` + +**Example Output:** + +```shell +$ trcli -c config.yml statuses all + +Statuses List (Test Result Statuses) Execution Parameters +> TestRail instance: https://yourinstance.testrail.io (user: test@example.com) +> Project: Your Project + +Retrieving test result statuses for project ID 1... +Found 5 test result status(es): + +Status ID: 1 + Name: passed + Label: Passed + System Status: Yes + Is Untested: No + Is Final: Yes + +Status ID: 2 + Name: blocked + Label: Blocked + System Status: Yes + Is Untested: No + Is Final: No + +Status ID: 3 + Name: untested + Label: Untested + System Status: Yes + Is Untested: Yes + Is Final: No + +Status ID: 4 + Name: retest + Label: Retest + System Status: Yes + Is Untested: No + Is Final: No + +Status ID: 5 + Name: failed + Label: Failed + System Status: Yes + Is Untested: No + Is Final: Yes + + +Status listing completed successfully. +``` + +##### Listing Case Statuses + +List all case statuses (requires TestRail Enterprise 7.3+): + +```shell +# List all case statuses (using config file) +$ trcli -c config.yml statuses case + +# List case statuses with all parameters +$ trcli statuses case \ + --host https://yourinstance.testrail.io \ + --username \ + --password \ + --project "Your Project" + +# Show all fields including abbreviations +$ trcli -c config.yml statuses case --show-all-fields + +# JSON output for integration +$ trcli -c config.yml statuses case --json-output | jq '.[].name' +``` + +**Example Output:** + +```shell +$ trcli -c config.yml statuses case + +Statuses List (Case Statuses) Execution Parameters +> TestRail instance: https://yourinstance.testrail.io (user: test@example.com) +> Project: Your Project + +Retrieving case statuses... +Note: This command requires TestRail Enterprise 7.3 or later. +Found 2 case status(es): + +Case Status ID: 1 + Name: Approved + Is Default: No + Is Approved: Yes + +Case Status ID: 2 + Name: Draft + Is Default: Yes + Is Approved: No + + +Case status listing completed successfully. +``` + #### Labels Management The TestRail CLI provides comprehensive label management capabilities using the `labels` command. Labels help categorize and organize your test management assets efficiently, making it easier to filter and manage test cases, runs, and projects. diff --git a/tests/test_cmd_statuses.py b/tests/test_cmd_statuses.py new file mode 100644 index 0000000..0962f54 --- /dev/null +++ b/tests/test_cmd_statuses.py @@ -0,0 +1,297 @@ +import pytest +from unittest import mock +from unittest.mock import MagicMock, patch +from click.testing import CliRunner + +from trcli.cli import Environment +from trcli.commands import cmd_statuses + + +class TestCmdStatuses: + """Test class for statuses command functionality""" + + def setup_method(self): + """Set up test environment""" + self.runner = CliRunner() + self.environment = Environment(cmd="statuses") + self.environment.host = "https://test.testrail.com" + self.environment.username = "test@example.com" + self.environment.password = "password" + self.environment.project = "Test Project" + self.environment.project_id = 1 + + def _setup_project_client_mock(self, mock_project_client, project_id=1): + """Helper to setup ProjectBasedClient mock""" + mock_client_instance = MagicMock() + mock_project_client.return_value = mock_client_instance + mock_client_instance.project.project_id = project_id + return mock_client_instance + + # Tests for 'statuses all' subcommand + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_all_statuses_success(self, mock_project_client): + """Test successful test result statuses listing""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_statuses.return_value = ( + [ + { + "id": 1, + "name": "passed", + "label": "Passed", + "is_system": True, + "is_untested": False, + "is_final": True, + "color_dark": 12709313, + "color_medium": 14527786, + "color_bright": 15394764, + }, + { + "id": 5, + "name": "failed", + "label": "Failed", + "is_system": True, + "is_untested": False, + "is_final": True, + "color_dark": 12396910, + "color_medium": 15829135, + "color_bright": 16434723, + }, + ], + "", + ) + + with patch.object(self.environment, "log") as mock_log, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.all, [], obj=self.environment) + + assert result.exit_code == 0 + mock_client.api_request_handler.status_handler.get_statuses.assert_called_once_with(project_id=1) + assert mock_log.called + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_all_statuses_json_output(self, mock_project_client): + """Test statuses listing with JSON output""" + mock_client = self._setup_project_client_mock(mock_project_client) + statuses_data = [ + { + "id": 1, + "name": "passed", + "label": "Passed", + "is_system": True, + "is_untested": False, + "is_final": True, + "color_dark": 12709313, + "color_medium": 14527786, + "color_bright": 15394764, + }, + ] + mock_client.api_request_handler.status_handler.get_statuses.return_value = (statuses_data, "") + + with patch.object(self.environment, "set_parameters"), patch.object( + self.environment, "check_for_required_parameters" + ): + result = self.runner.invoke(cmd_statuses.all, ["--json-output"], obj=self.environment) + + assert result.exit_code == 0 + assert '"id": 1' in result.output + assert '"name": "passed"' in result.output + assert "\n" in result.output + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_all_statuses_show_all_fields(self, mock_project_client): + """Test statuses listing with show all fields""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_statuses.return_value = ( + [ + { + "id": 1, + "name": "passed", + "label": "Passed", + "is_system": True, + "is_untested": False, + "is_final": True, + "color_dark": 12709313, + "color_medium": 14527786, + "color_bright": 15394764, + } + ], + "", + ) + + with patch.object(self.environment, "log") as mock_log, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.all, ["--show-all-fields"], obj=self.environment) + + assert result.exit_code == 0 + assert mock_log.called + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_all_statuses_empty_result(self, mock_project_client): + """Test statuses listing with no statuses""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_statuses.return_value = ([], "") + + with patch.object(self.environment, "log") as mock_log, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.all, [], obj=self.environment) + + assert result.exit_code == 0 + assert mock_log.called + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_all_statuses_api_error(self, mock_project_client): + """Test statuses listing with API error""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_statuses.return_value = ([], "Project not found") + + with patch.object(self.environment, "elog") as mock_elog, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.all, [], obj=self.environment) + + assert result.exit_code == 1 + mock_elog.assert_called_with("Error: Failed to retrieve statuses: Project not found") + + # Tests for 'statuses case' subcommand + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_case_statuses_success(self, mock_project_client): + """Test successful case statuses listing""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_case_statuses.return_value = ( + [ + { + "case_status_id": 1, + "name": "Approved", + "abbreviation": None, + "is_default": False, + "is_approved": True, + }, + { + "case_status_id": 2, + "name": "Draft", + "abbreviation": "DFT", + "is_default": True, + "is_approved": False, + }, + ], + "", + ) + + with patch.object(self.environment, "log") as mock_log, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.case, [], obj=self.environment) + + assert result.exit_code == 0 + mock_client.api_request_handler.status_handler.get_case_statuses.assert_called_once() + assert mock_log.called + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_case_statuses_json_output(self, mock_project_client): + """Test case statuses listing with JSON output""" + mock_client = self._setup_project_client_mock(mock_project_client) + case_statuses_data = [ + { + "case_status_id": 1, + "name": "Approved", + "abbreviation": None, + "is_default": False, + "is_approved": True, + }, + ] + mock_client.api_request_handler.status_handler.get_case_statuses.return_value = (case_statuses_data, "") + + with patch.object(self.environment, "set_parameters"), patch.object( + self.environment, "check_for_required_parameters" + ): + result = self.runner.invoke(cmd_statuses.case, ["--json-output"], obj=self.environment) + + assert result.exit_code == 0 + assert '"case_status_id": 1' in result.output + assert '"name": "Approved"' in result.output + assert "\n" in result.output + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_case_statuses_show_all_fields(self, mock_project_client): + """Test case statuses listing with show all fields""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_case_statuses.return_value = ( + [ + { + "case_status_id": 2, + "name": "Draft", + "abbreviation": "DFT", + "is_default": True, + "is_approved": False, + } + ], + "", + ) + + with patch.object(self.environment, "log") as mock_log, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.case, ["--show-all-fields"], obj=self.environment) + + assert result.exit_code == 0 + assert mock_log.called + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_case_statuses_empty_result(self, mock_project_client): + """Test case statuses listing with no case statuses""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_case_statuses.return_value = ([], "") + + with patch.object(self.environment, "log") as mock_log, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.case, [], obj=self.environment) + + assert result.exit_code == 0 + assert mock_log.called + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_case_statuses_api_error(self, mock_project_client): + """Test case statuses listing with API error""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_case_statuses.return_value = ( + [], + "API endpoint not found", + ) + + with patch.object(self.environment, "elog") as mock_elog, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.case, [], obj=self.environment) + + assert result.exit_code == 1 + # Check that the error was logged (should be called at least once for the error message) + assert mock_elog.called + + @mock.patch("trcli.commands.cmd_statuses.ProjectBasedClient") + def test_list_case_statuses_with_abbreviation_none(self, mock_project_client): + """Test case statuses with None abbreviation""" + mock_client = self._setup_project_client_mock(mock_project_client) + mock_client.api_request_handler.status_handler.get_case_statuses.return_value = ( + [ + { + "case_status_id": 1, + "name": "Approved", + "abbreviation": None, + "is_default": False, + "is_approved": True, + } + ], + "", + ) + + with patch.object(self.environment, "log") as mock_log, patch.object( + self.environment, "set_parameters" + ), patch.object(self.environment, "check_for_required_parameters"): + result = self.runner.invoke(cmd_statuses.case, ["--show-all-fields"], obj=self.environment) + + assert result.exit_code == 0 + assert mock_log.called diff --git a/tests/test_data/cli_test_data.py b/tests/test_data/cli_test_data.py index 905a29d..a6e1a6f 100644 --- a/tests/test_data/cli_test_data.py +++ b/tests/test_data/cli_test_data.py @@ -79,6 +79,7 @@ " - plans: Query test plans\n" " - runs: Query test runs\n" " - milestones: Query milestones\n" + " - statuses: Query test statuses\n" " - results: Query and update test results\n" ) From 053be056d6ea9f1a2032f184ff018550413893e9 Mon Sep 17 00:00:00 2001 From: acuanico-tr-galt Date: Thu, 25 Jun 2026 18:03:34 +0800 Subject: [PATCH 3/3] TRCLI-282: Resolved some conflicts --- CHANGELOG.MD | 1 + README.md | 152 +++++++++++++++++++++++++++++++ tests/test_data/cli_test_data.py | 1 + trcli/api/api_request_handler.py | 2 + trcli/constants.py | 2 + 5 files changed, 158 insertions(+) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 929822c..3840e51 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -13,6 +13,7 @@ _released 06-24-2026 ### Added - **New `runs` command**: Query and retrieve test run information from TestRail with `get` and `list` subcommands. Provides execution metrics, status tracking, and configuration details. Note: Only returns standalone runs (not runs within test plans). - **New `milestones` command**: Query and retrieve milestone information from TestRail with `get` and `list` subcommands. Track project milestones, monitor completion status, due dates, and references. + - **New `configurations` command**: Query and retrieve configuration information from TestRail using `list` subcommand. Lists down configurations set in an entire project regardless of project type (single or multi-suite). - **New `statuses` command**: Query and retrieve status information from TestRail with `all`: List all test result statuses (Passed, Failed, Blocked, etc.) with system/custom flags, colors, and behavior properties and `case`: List all case statuses (Approved, Draft, etc.) for case workflow management (requires TestRail Enterprise 7.3+) ## [1.15.0] diff --git a/README.md b/README.md index eab6670..b443a6a 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ Commands: suites Manage test suites in TestRail runs Manage test runs in TestRail milestones Manage milestones in TestRail + configurations Manage testing configurations in TestRail statuses Manage test statuses in TestRail update Update TRCLI to the latest version from PyPI. ``` @@ -2335,6 +2336,157 @@ Case Status ID: 2 Is Approved: No +Case status listing completed successfully. +``` + +### Statuses Command + +The TestRail CLI provides the `statuses` command for retrieving status information from TestRail. This includes both test result statuses (Passed, Failed, Blocked, etc.) and case statuses (Approved, Draft, etc.). These statuses are crucial for mapping test results correctly and managing test case workflows. + +#### Statuses Command Overview + +The `statuses` command supports two subcommands: + +| Subcommand | Purpose | API Endpoint | +|------------|---------|--------------| +| `all` | List all test result statuses | Retrieve available test result statuses (Passed, Failed, Blocked, etc.) | +| `case` | List all case statuses (Enterprise 7.3+) | Retrieve test case statuses (Approved, Draft, etc.) for case workflows | + +#### Reference + +```shell +$ trcli statuses --help + +Usage: trcli statuses [OPTIONS] COMMAND [ARGS]... + Manage test statuses in TestRail + +Options: + --help Show this message and exit. + +Commands: + all List all test result statuses from TestRail + case List all case statuses from TestRail (Enterprise 7.3+) +``` + +##### Listing Test Result Statuses + +List all test result statuses available for a project: + +```shell +# List all test result statuses (using config file) +$ trcli -c config.yml statuses all + +# List all statuses with all parameters +$ trcli statuses all \ + --host https://yourinstance.testrail.io \ + --username \ + --password \ + --project "Your Project" + +# Show all fields including color values +$ trcli -c config.yml statuses all --show-all-fields + +# JSON output for integration with other tools +$ trcli -c config.yml statuses all --json-output | jq '.[].name' +``` + +**Example Output:** + +```shell +$ trcli -c config.yml statuses all + +Statuses List (Test Result Statuses) Execution Parameters +> TestRail instance: https://yourinstance.testrail.io (user: test@example.com) +> Project: Your Project + +Retrieving test result statuses for project ID 1... +Found 5 test result status(es): + +Status ID: 1 + Name: passed + Label: Passed + System Status: Yes + Is Untested: No + Is Final: Yes + +Status ID: 2 + Name: blocked + Label: Blocked + System Status: Yes + Is Untested: No + Is Final: No + +Status ID: 3 + Name: untested + Label: Untested + System Status: Yes + Is Untested: Yes + Is Final: No + +Status ID: 4 + Name: retest + Label: Retest + System Status: Yes + Is Untested: No + Is Final: No + +Status ID: 5 + Name: failed + Label: Failed + System Status: Yes + Is Untested: No + Is Final: Yes + + +Status listing completed successfully. +``` + +##### Listing Case Statuses + +List all case statuses (requires TestRail Enterprise 7.3+): + +```shell +# List all case statuses (using config file) +$ trcli -c config.yml statuses case + +# List case statuses with all parameters +$ trcli statuses case \ + --host https://yourinstance.testrail.io \ + --username \ + --password \ + --project "Your Project" + +# Show all fields including abbreviations +$ trcli -c config.yml statuses case --show-all-fields + +# JSON output for integration +$ trcli -c config.yml statuses case --json-output | jq '.[].name' +``` + +**Example Output:** + +```shell +$ trcli -c config.yml statuses case + +Statuses List (Case Statuses) Execution Parameters +> TestRail instance: https://yourinstance.testrail.io (user: test@example.com) +> Project: Your Project + +Retrieving case statuses... +Note: This command requires TestRail Enterprise 7.3 or later. +Found 2 case status(es): + +Case Status ID: 1 + Name: Approved + Is Default: No + Is Approved: Yes + +Case Status ID: 2 + Name: Draft + Is Default: Yes + Is Approved: No + + Case status listing completed successfully. ``` diff --git a/tests/test_data/cli_test_data.py b/tests/test_data/cli_test_data.py index a6e1a6f..09cfdc8 100644 --- a/tests/test_data/cli_test_data.py +++ b/tests/test_data/cli_test_data.py @@ -79,6 +79,7 @@ " - plans: Query test plans\n" " - runs: Query test runs\n" " - milestones: Query milestones\n" + " - configurations: Query configurations\n" " - statuses: Query test statuses\n" " - results: Query and update test results\n" ) diff --git a/trcli/api/api_request_handler.py b/trcli/api/api_request_handler.py index 562fdc2..5704b47 100644 --- a/trcli/api/api_request_handler.py +++ b/trcli/api/api_request_handler.py @@ -17,6 +17,7 @@ from trcli.api.case_handler import CaseHandler from trcli.api.plan_handler import PlanHandler from trcli.api.milestone_handler import MilestoneHandler +from trcli.api.configuration_handler import ConfigurationHandler from trcli.api.status_handler import StatusHandler from trcli.cli import Environment from trcli.constants import ( @@ -89,6 +90,7 @@ def __init__( ) self.plan_handler = PlanHandler(api_client, environment) self.milestone_handler = MilestoneHandler(api_client) + self.configuration_handler = ConfigurationHandler(api_client) self.status_handler = StatusHandler(api_client) # BDD case cache for feature name matching (shared by CucumberParser and JunitParser) diff --git a/trcli/constants.py b/trcli/constants.py index 8cc1872..f09fb91 100644 --- a/trcli/constants.py +++ b/trcli/constants.py @@ -96,6 +96,7 @@ sections=dict(**FAULT_MAPPING), runs=dict(**FAULT_MAPPING), milestones=dict(**FAULT_MAPPING), + configurations=dict(**FAULT_MAPPING), statuses=dict(**FAULT_MAPPING), ) @@ -129,6 +130,7 @@ - plans: Query test plans - runs: Query test runs - milestones: Query milestones + - configurations: Query configurations - statuses: Query test statuses - results: Query and update test results"""