trcli
Advanced tools
+1
-1
| Metadata-Version: 2.4 | ||
| Name: trcli | ||
| Version: 1.13.1 | ||
| Version: 1.13.2 | ||
| License-File: LICENSE.md | ||
@@ -5,0 +5,0 @@ Requires-Dist: click<8.2.2,>=8.1.0 |
@@ -554,3 +554,233 @@ """ | ||
| class TestFroalaParagraphTagStripping: | ||
| """Test suite for Froala HTML paragraph tag stripping in automation_id matching""" | ||
| @pytest.mark.parametrize( | ||
| "input_value,expected_output", | ||
| [ | ||
| # Basic Froala wrapping | ||
| ("<p>com.example.Test.method</p>", "com.example.Test.method"), | ||
| ("<p>automation_id_value</p>", "automation_id_value"), | ||
| # Case insensitive tags | ||
| ("<P>value</P>", "value"), | ||
| ("<p>value</P>", "value"), | ||
| ("<P>value</p>", "value"), | ||
| # With whitespace | ||
| ("<p>value</p>\n", "value"), | ||
| ("<p> value </p>", "value"), | ||
| (" <p>value</p> ", "value"), | ||
| ("<p>\nvalue\n</p>", "value"), | ||
| # Without tags (should pass through) | ||
| ("plain_value", "plain_value"), | ||
| ("com.example.Test.method", "com.example.Test.method"), | ||
| # Partial tags (still stripped for safety - unlikely to have legitimate automation IDs with these) | ||
| ("<p>value", "value"), | ||
| ("value</p>", "value"), | ||
| # Empty/None values | ||
| ("", ""), | ||
| (None, None), | ||
| # Complex automation IDs | ||
| ("<p>com.example.MyTests.test_name_C120013()</p>", "com.example.MyTests.test_name_C120013()"), | ||
| ("<p>User Login.@positive.@smoke.Valid credentials</p>", "User Login.@positive.@smoke.Valid credentials"), | ||
| ("<p>Sub-Tests.Subtests 1.Subtest 1a</p>", "Sub-Tests.Subtests 1.Subtest 1a"), | ||
| # HTML entities (already unescaped before this function) | ||
| ("<p>test&value</p>", "test&value"), # Should be handled by html.unescape first | ||
| ], | ||
| ) | ||
| def test_strip_froala_paragraph_tags_unit(self, input_value, expected_output): | ||
| """ | ||
| Unit test for _strip_froala_paragraph_tags static method. | ||
| Tests various scenarios of Froala HTML wrapping. | ||
| """ | ||
| from trcli.api.case_matcher import AutomationIdMatcher | ||
| result = AutomationIdMatcher._strip_froala_paragraph_tags(input_value) | ||
| assert result == expected_output, f"Expected '{expected_output}', got '{result}'" | ||
| @pytest.mark.api_handler | ||
| def test_auto_matcher_matches_cases_with_froala_tags(self, environment, api_client, mocker): | ||
| """ | ||
| Integration test: AUTO matcher correctly matches cases even when TestRail returns | ||
| automation_id values wrapped in Froala <p> tags. | ||
| Simulates the real scenario: | ||
| 1. Test report has automation_id: "com.example.Test.method1" | ||
| 2. TestRail returns: "<p>com.example.Test.method1</p>" (after user edited the case) | ||
| 3. Should still match correctly | ||
| """ | ||
| # Setup: AUTO matcher | ||
| environment.case_matcher = MatchersParser.AUTO | ||
| # Create test suite with plain automation IDs (as they come from test reports) | ||
| test_case_1 = TestRailCase( | ||
| title="Test Method 1", | ||
| custom_automation_id="com.example.Test.method1", | ||
| result=TestRailResult(status_id=1), | ||
| ) | ||
| test_case_2 = TestRailCase( | ||
| title="Test Method 2", | ||
| custom_automation_id="com.example.Test.method2", | ||
| result=TestRailResult(status_id=1), | ||
| ) | ||
| test_case_3 = TestRailCase( | ||
| title="Test Method 3", | ||
| custom_automation_id="com.example.Test.method3", | ||
| result=TestRailResult(status_id=1), | ||
| ) | ||
| section = TestRailSection(name="Test Section", section_id=1, testcases=[test_case_1, test_case_2, test_case_3]) | ||
| test_suite = TestRailSuite(name="Test Suite", suite_id=1, testsections=[section]) | ||
| api_request_handler = ApiRequestHandler(environment, api_client, test_suite) | ||
| # Mock TestRail responses: return automation_id values wrapped in Froala <p> tags | ||
| # (simulates what happens when user edits cases in TestRail with Text field type) | ||
| mock_cases = [ | ||
| { | ||
| "id": 1, | ||
| "custom_automation_id": "<p>com.example.Test.method1</p>", # Froala wrapped | ||
| "title": "Test Method 1", | ||
| "section_id": 1, | ||
| }, | ||
| { | ||
| "id": 2, | ||
| "custom_automation_id": "<p>com.example.Test.method2</p>\n", # With newline | ||
| "title": "Test Method 2", | ||
| "section_id": 1, | ||
| }, | ||
| { | ||
| "id": 3, | ||
| "custom_automation_id": "com.example.Test.method3", # Plain (not edited yet) | ||
| "title": "Test Method 3", | ||
| "section_id": 1, | ||
| }, | ||
| ] | ||
| mocker.patch.object(api_request_handler, "_ApiRequestHandler__get_all_cases", return_value=(mock_cases, None)) | ||
| mock_update_data = mocker.patch.object(api_request_handler.data_provider, "update_data") | ||
| # Execute | ||
| project_id = 1 | ||
| has_missing, error = api_request_handler.check_missing_test_cases_ids(project_id) | ||
| # Assert: All 3 cases should match successfully (no missing cases) | ||
| assert not has_missing, "Should not have missing cases - Froala tags should be stripped" | ||
| assert error == "", "Should not have errors" | ||
| # Verify that update_data was called with all 3 matched cases | ||
| mock_update_data.assert_called_once() | ||
| call_args = mock_update_data.call_args[1] | ||
| matched_cases = call_args["case_data"] | ||
| assert len(matched_cases) == 3, "All 3 cases should be matched" | ||
| # Verify correct case IDs were matched | ||
| matched_ids = {case["case_id"] for case in matched_cases} | ||
| assert matched_ids == {1, 2, 3}, "Should match all case IDs correctly" | ||
| @pytest.mark.api_handler | ||
| def test_auto_matcher_handles_both_automation_id_field_names(self, environment, api_client, mocker): | ||
| """ | ||
| Test that Froala tag stripping works for both automation_id field names: | ||
| - custom_automation_id (legacy) | ||
| - custom_case_automation_id (current) | ||
| """ | ||
| # Setup: AUTO matcher | ||
| environment.case_matcher = MatchersParser.AUTO | ||
| test_case_1 = TestRailCase( | ||
| title="Test 1", | ||
| custom_automation_id="test.method1", | ||
| result=TestRailResult(status_id=1), | ||
| ) | ||
| test_case_2 = TestRailCase( | ||
| title="Test 2", | ||
| custom_automation_id="test.method2", | ||
| result=TestRailResult(status_id=1), | ||
| ) | ||
| section = TestRailSection(name="Test Section", section_id=1, testcases=[test_case_1, test_case_2]) | ||
| test_suite = TestRailSuite(name="Test Suite", suite_id=1, testsections=[section]) | ||
| api_request_handler = ApiRequestHandler(environment, api_client, test_suite) | ||
| # Mock cases: one with legacy name, one with current name (both Froala wrapped) | ||
| mock_cases = [ | ||
| { | ||
| "id": 1, | ||
| "custom_automation_id": "<p>test.method1</p>", # Legacy field name | ||
| "title": "Test 1", | ||
| "section_id": 1, | ||
| }, | ||
| { | ||
| "id": 2, | ||
| "custom_case_automation_id": "<p>test.method2</p>", # Current field name | ||
| "title": "Test 2", | ||
| "section_id": 1, | ||
| }, | ||
| ] | ||
| mocker.patch.object(api_request_handler, "_ApiRequestHandler__get_all_cases", return_value=(mock_cases, None)) | ||
| mock_update_data = mocker.patch.object(api_request_handler.data_provider, "update_data") | ||
| # Execute | ||
| project_id = 1 | ||
| has_missing, error = api_request_handler.check_missing_test_cases_ids(project_id) | ||
| # Assert: Both cases should match | ||
| assert not has_missing, "Should match cases with both field name variants" | ||
| assert error == "", "Should not have errors" | ||
| matched_cases = mock_update_data.call_args[1]["case_data"] | ||
| assert len(matched_cases) == 2, "Both cases should be matched" | ||
| @pytest.mark.api_handler | ||
| def test_froala_tags_cause_mismatch_without_fix(self, environment, api_client, mocker): | ||
| """ | ||
| Negative test: Demonstrate that WITHOUT the fix, Froala tags would cause mismatches. | ||
| This test documents the bug being fixed. | ||
| """ | ||
| # Setup | ||
| environment.case_matcher = MatchersParser.AUTO | ||
| test_case = TestRailCase( | ||
| title="Test Method", | ||
| custom_automation_id="com.example.Test.method", | ||
| result=TestRailResult(status_id=1), | ||
| ) | ||
| section = TestRailSection(name="Test Section", section_id=1, testcases=[test_case]) | ||
| test_suite = TestRailSuite(name="Test Suite", suite_id=1, testsections=[section]) | ||
| api_request_handler = ApiRequestHandler(environment, api_client, test_suite) | ||
| # Return automation_id with tags from TestRail | ||
| mock_cases = [ | ||
| { | ||
| "id": 1, | ||
| "custom_automation_id": "<p>com.example.Test.method</p>", | ||
| "title": "Test Method", | ||
| "section_id": 1, | ||
| } | ||
| ] | ||
| mocker.patch.object(api_request_handler, "_ApiRequestHandler__get_all_cases", return_value=(mock_cases, None)) | ||
| mock_update_data = mocker.patch.object(api_request_handler.data_provider, "update_data") | ||
| # Execute | ||
| has_missing, _ = api_request_handler.check_missing_test_cases_ids(1) | ||
| # Assert: With the fix, this should NOT have missing cases | ||
| assert not has_missing, "With fix applied, Froala tags should be stripped and case should match" | ||
| # Without the fix, this test would fail because: | ||
| # - Report has: "com.example.Test.method" | ||
| # - TestRail returns: "<p>com.example.Test.method</p>" | ||
| # - No match → missing case → new duplicate case created | ||
| if __name__ == "__main__": | ||
| pytest.main([__file__, "-v", "-s"]) |
| Metadata-Version: 2.4 | ||
| Name: trcli | ||
| Version: 1.13.1 | ||
| Version: 1.13.2 | ||
| License-File: LICENSE.md | ||
@@ -5,0 +5,0 @@ Requires-Dist: click<8.2.2,>=8.1.0 |
@@ -1,1 +0,1 @@ | ||
| __version__ = "1.13.1" | ||
| __version__ = "1.13.2" |
@@ -59,2 +59,27 @@ """ | ||
| @staticmethod | ||
| def _strip_froala_paragraph_tags(value: str) -> str: | ||
| """ | ||
| Strip Froala HTML paragraph tags from automation_id values. | ||
| :param value: Automation ID value from TestRail | ||
| :returns: Value with leading <p> and trailing </p> tags removed | ||
| """ | ||
| if not value: | ||
| return value | ||
| # Strip whitespace first | ||
| value = value.strip() | ||
| # Remove leading <p> tag (case-insensitive) | ||
| if value.lower().startswith("<p>"): | ||
| value = value[3:] | ||
| # Remove trailing </p> tag (case-insensitive) | ||
| if value.lower().endswith("</p>"): | ||
| value = value[:-4] | ||
| # Strip any remaining whitespace after tag removal | ||
| return value.strip() | ||
| def check_missing_cases( | ||
@@ -91,2 +116,3 @@ self, | ||
| aut_case_id = html.unescape(aut_case_id) | ||
| aut_case_id = self._strip_froala_paragraph_tags(aut_case_id) | ||
| test_cases_by_aut_id[aut_case_id] = case | ||
@@ -93,0 +119,0 @@ |
@@ -24,2 +24,3 @@ """ | ||
| logging: | ||
| enabled: true # Must be true to enable logging (default: false) | ||
| level: INFO | ||
@@ -34,2 +35,3 @@ format: json # json or text | ||
| DEFAULT_CONFIG = { | ||
| "enabled": False, | ||
| "level": "INFO", | ||
@@ -147,2 +149,3 @@ "format": "json", # json or text | ||
| Environment variables: | ||
| TRCLI_LOG_ENABLED: Enable/disable logging (true, false, yes, no, 1, 0) | ||
| TRCLI_LOG_LEVEL: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) | ||
@@ -161,2 +164,7 @@ TRCLI_LOG_FORMAT: Output format (json, text) | ||
| """ | ||
| # Boolean override for enabled flag | ||
| if "TRCLI_LOG_ENABLED" in os.environ: | ||
| enabled_value = os.environ["TRCLI_LOG_ENABLED"].lower() | ||
| config["enabled"] = enabled_value in ("true", "yes", "1", "on") | ||
| # Simple overrides | ||
@@ -247,2 +255,10 @@ env_mappings = { | ||
| if not config.get("enabled", True): | ||
| from trcli.logging.structured_logger import LoggerFactory, LogLevel | ||
| import os | ||
| # Set log level to maximum to effectively disable all logging | ||
| LoggerFactory.configure(level="CRITICAL", format_style="json", stream=open(os.devnull, "w")) | ||
| return | ||
| # Determine output stream | ||
@@ -249,0 +265,0 @@ output_type = config.get("output", "stderr") |
Sorry, the diff of this file is too big to display
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
1016581
1.18%18804
1.22%