From b2621d92028e9aa056083d90e3add02db22e1aae Mon Sep 17 00:00:00 2001 From: zixingdeng Date: Tue, 28 Jul 2026 19:43:18 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=A4=B4=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pytest/pyproject.toml | 2 +- .../src/testsolar_pytestx/header_injection.py | 17 ++++++++++++++--- pytest/testtool.yaml | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/pytest/pyproject.toml b/pytest/pyproject.toml index 9b8f9b3..c88b12f 100644 --- a/pytest/pyproject.toml +++ b/pytest/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "testsolar-pytestx" -version = "0.1.76" +version = "0.1.77" description = "Pytest tool for TestSolar" authors = [ {name = "asiazhang2002", email = "asiazhang2002@gmail.com"}, diff --git a/pytest/src/testsolar_pytestx/header_injection.py b/pytest/src/testsolar_pytestx/header_injection.py index 1417cf7..784fb49 100644 --- a/pytest/src/testsolar_pytestx/header_injection.py +++ b/pytest/src/testsolar_pytestx/header_injection.py @@ -6,6 +6,7 @@ """ import threading +import urllib.parse from typing import Optional, Callable, Any, Dict from loguru import logger @@ -33,6 +34,16 @@ def set_current_test_nodeid(nodeid: Optional[str]) -> None: # ============ Header 注入逻辑 ============ +def _encode_nodeid(nodeid: str) -> str: + """对nodeid进行URL编码,确保HTTP头部值只含ASCII字符。 + + HTTP头部值要求latin-1编码,含中文等非ASCII字符的用例名会导致 + UnicodeEncodeError,故注入前统一percent-encode。接收方可用 + urllib.parse.unquote还原原始用例名。 + """ + return urllib.parse.quote(nodeid, safe="") + + def _inject_header_to_dict(headers: Optional[Dict[str, Any]], nodeid: str) -> Dict[str, Any]: """ 向请求头字典注入X-Testsolar-Testcase @@ -53,7 +64,7 @@ def _inject_header_to_dict(headers: Optional[Dict[str, Any]], nodeid: str) -> Di # 创建副本避免修改原始dict headers = headers.copy() - headers["X-Testsolar-Testcase"] = nodeid + headers["X-Testsolar-Testcase"] = _encode_nodeid(nodeid) return headers @@ -119,11 +130,11 @@ def _patched_httplib_request( if nodeid: # httplib的headers参数默认是{}而不是None if final_headers is None: - final_headers = {"X-Testsolar-Testcase": nodeid} + final_headers = {"X-Testsolar-Testcase": _encode_nodeid(nodeid)} else: # 创建副本避免修改原始headers new_headers = dict(final_headers) - new_headers["X-Testsolar-Testcase"] = nodeid + new_headers["X-Testsolar-Testcase"] = _encode_nodeid(nodeid) final_headers = new_headers return _original_httplib_request(self, method, url, body, final_headers, **kwargs) diff --git a/pytest/testtool.yaml b/pytest/testtool.yaml index 8adab16..3f2777b 100644 --- a/pytest/testtool.yaml +++ b/pytest/testtool.yaml @@ -2,7 +2,7 @@ schemaVersion: 1.0 name: pytest nameZh: pytest自动化测试 lang: python -version: '0.1.76' +version: '0.1.77' langType: INTERPRETED description: |- pytest是一个成熟的全功能Python测试工具,可以帮助您编写更好的程序。此测试工具允许您在TestSolar上运行pytest。 From 224eea340e32ef1e36d55840423c89d177cfe1bd Mon Sep 17 00:00:00 2001 From: zixingdeng Date: Tue, 28 Jul 2026 19:48:10 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8D=95=E6=B5=8B?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pytest/tests/test_header_injection.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pytest/tests/test_header_injection.py b/pytest/tests/test_header_injection.py index a3ac7d6..ddbe958 100644 --- a/pytest/tests/test_header_injection.py +++ b/pytest/tests/test_header_injection.py @@ -1,6 +1,7 @@ """Tests for header_injection module.""" import threading +import urllib.parse from typing import Any, Dict, Optional from unittest import mock @@ -78,12 +79,12 @@ class TestInjectHeaderToDict: def test_inject_header_to_none_headers(self) -> None: """Test injecting header when headers is None.""" result = _inject_header_to_dict(None, "test::nodeid") - assert result == {"X-Testsolar-Testcase": "test::nodeid"} + assert result == {"X-Testsolar-Testcase": "test%3A%3Anodeid"} def test_inject_header_to_empty_dict(self) -> None: """Test injecting header to empty dict.""" result = _inject_header_to_dict({}, "test::nodeid") - assert result == {"X-Testsolar-Testcase": "test::nodeid"} + assert result == {"X-Testsolar-Testcase": "test%3A%3Anodeid"} def test_inject_header_to_existing_dict(self) -> None: """Test injecting header to dict with existing headers.""" @@ -92,11 +93,22 @@ def test_inject_header_to_existing_dict(self) -> None: assert result == { "Content-Type": "application/json", - "X-Testsolar-Testcase": "test::nodeid", + "X-Testsolar-Testcase": "test%3A%3Anodeid", } # Original should not be modified assert "X-Testsolar-Testcase" not in original + def test_inject_header_encodes_non_ascii_nodeid(self) -> None: + """含中文等非ASCII字符的nodeid需被percent-encode,避免latin-1编码错误。""" + nodeid = "test_features.py::test_重启运行中实例" + result = _inject_header_to_dict(None, nodeid) + + encoded = result["X-Testsolar-Testcase"] + # 注入值必须可被latin-1编码(HTTP头部要求),否则抛UnicodeEncodeError + assert isinstance(encoded.encode("latin-1"), bytes) + # 接收方unquote可还原原始用例名 + assert urllib.parse.unquote(encoded) == nodeid + def test_inject_header_does_not_modify_original(self) -> None: """Test that original headers dict is not modified.""" original: Dict[str, str] = {"Authorization": "Bearer token"} @@ -166,7 +178,7 @@ def test_requests_header_injection(self) -> None: if mock_send.called: request = mock_send.call_args[0][0] assert "X-Testsolar-Testcase" in request.headers - assert request.headers["X-Testsolar-Testcase"] == "test_file.py::test_case" + assert request.headers["X-Testsolar-Testcase"] == "test_file.py%3A%3Atest_case" def test_requests_no_injection_when_nodeid_not_set(self) -> None: """Test that requests does not inject header when nodeid is not set."""