From 859b8e7d92c959356b3c2ca61968505a43816d40 Mon Sep 17 00:00:00 2001 From: JeffreyChen Date: Sat, 25 Apr 2026 14:21:27 +0800 Subject: [PATCH] fix: resolve SonarCloud issues and HTTPS hotspots - Tighten _VALID_PACKAGE_NAME regex with concise \w class and re.ASCII flag (python:S6353). - Refactor change_xml_structure helpers (elements_tree_to_dict, dict_to_elements_tree) into smaller functions to drop cognitive complexity below the 15 threshold (python:S3776). - Document the empty _dummy_callback stub in test_callback_executor (python:S1186). - Drop the unused failure_xml binding in TestGenerateXml.test_success_xml by destructuring into _ (python:S1481). - Switch test fixtures and project templates from http:// to https:// for example.com / httpbin.org to clear the "Using http protocol is insecure" hotspots. --- .../package_manager/package_manager_class.py | 2 +- .../project/template/template_keyword.py | 8 +- .../change_xml_structure.py | 114 ++++++++++-------- test/test_callback_executor.py | 2 +- test/test_proxy_user.py | 8 +- test/test_report_generation.py | 6 +- test/test_xml_utils.py | 4 +- 7 files changed, 81 insertions(+), 63 deletions(-) diff --git a/je_load_density/utils/package_manager/package_manager_class.py b/je_load_density/utils/package_manager/package_manager_class.py index 4391f5e..ee54ce8 100644 --- a/je_load_density/utils/package_manager/package_manager_class.py +++ b/je_load_density/utils/package_manager/package_manager_class.py @@ -5,7 +5,7 @@ from sys import stderr from typing import Optional, Any -_VALID_PACKAGE_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$") +_VALID_PACKAGE_NAME = re.compile(r"^[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*$", re.ASCII) class PackageManager: diff --git a/je_load_density/utils/project/template/template_keyword.py b/je_load_density/utils/project/template/template_keyword.py index 5db6887..df334c0 100644 --- a/je_load_density/utils/project/template/template_keyword.py +++ b/je_load_density/utils/project/template/template_keyword.py @@ -6,8 +6,8 @@ "user_count": 50, "spawn_rate": 10, "test_time": 5, **{ "tasks": { - "get": {"request_url": "http://httpbin.org/get"}, - "post": {"request_url": "http://httpbin.org/post"} + "get": {"request_url": "https://httpbin.org/get"}, + "post": {"request_url": "https://httpbin.org/post"} } } }] @@ -21,8 +21,8 @@ "user_count": 50, "spawn_rate": 10, "test_time": 5, **{ "tasks": { - "get": {"request_url": "http://httpbin.org/get"}, - "post": {"request_url": "http://httpbin.org/post"} + "get": {"request_url": "https://httpbin.org/get"}, + "post": {"request_url": "https://httpbin.org/post"} } } }] diff --git a/je_load_density/utils/xml/change_xml_structure/change_xml_structure.py b/je_load_density/utils/xml/change_xml_structure/change_xml_structure.py index c65f927..8901142 100644 --- a/je_load_density/utils/xml/change_xml_structure/change_xml_structure.py +++ b/je_load_density/utils/xml/change_xml_structure/change_xml_structure.py @@ -3,6 +3,27 @@ from xml.etree import ElementTree as _ElementTreeBuilder # nosec B405 - construction only, no parsing # nosemgrep: python.lang.security.use-defused-xml.use-defused-xml +def _collapse_singleton_lists(grouped: Dict[str, list]) -> Dict[str, Any]: + return {key: value[0] if len(value) == 1 else value for key, value in grouped.items()} + + +def _children_to_dict(children: list) -> Dict[str, Any]: + grouped: Dict[str, list] = defaultdict(list) + for child_dict in map(elements_tree_to_dict, children): + for key, value in child_dict.items(): + grouped[key].append(value) + return _collapse_singleton_lists(grouped) + + +def _attach_text(elements_dict: Dict[str, Any], tag: str, text: str, has_children_or_attrs: bool) -> None: + if not text: + return + if has_children_or_attrs: + elements_dict[tag]["#text"] = text + else: + elements_dict[tag] = text + + def elements_tree_to_dict(elements_tree: _ElementTreeBuilder.Element) -> Dict[str, Any]: """ 將 XML ElementTree 轉換為字典 @@ -11,38 +32,57 @@ def elements_tree_to_dict(elements_tree: _ElementTreeBuilder.Element) -> Dict[st :param elements_tree: XML ElementTree 元素 (XML ElementTree element) :return: 對應的字典結構 (Dictionary representation) """ - elements_dict: Dict[str, Any] = {elements_tree.tag: {} if elements_tree.attrib else None} + tag = elements_tree.tag + has_attrs = bool(elements_tree.attrib) + elements_dict: Dict[str, Any] = {tag: {} if has_attrs else None} children = list(elements_tree) - # 遞迴處理子節點 (Recursively process children) if children: - default_dict = defaultdict(list) - for dc in map(elements_tree_to_dict, children): - for key, value in dc.items(): - default_dict[key].append(value) - elements_dict[elements_tree.tag] = { - key: value[0] if len(value) == 1 else value - for key, value in default_dict.items() - } - - # 加入屬性 (Add attributes) - if elements_tree.attrib: - elements_dict[elements_tree.tag].update( - {f"@{key}": value for key, value in elements_tree.attrib.items()} - ) - - # 加入文字內容 (Add text content) + elements_dict[tag] = _children_to_dict(children) + + if has_attrs: + elements_dict[tag].update({f"@{key}": value for key, value in elements_tree.attrib.items()}) + if elements_tree.text: - text = elements_tree.text.strip() - if children or elements_tree.attrib: - if text: - elements_dict[elements_tree.tag]["#text"] = text - else: - elements_dict[elements_tree.tag] = text + _attach_text(elements_dict, tag, elements_tree.text.strip(), bool(children) or has_attrs) return elements_dict +def _set_text_node(root: _ElementTreeBuilder.Element, key: str, value: Any) -> None: + if key != "#text" or not isinstance(value, str): + raise TypeError(f"Invalid text node: {key} -> {value}") + root.text = value + + +def _set_attribute(root: _ElementTreeBuilder.Element, key: str, value: Any) -> None: + if not isinstance(value, str): + raise TypeError(f"Invalid attribute value: {key} -> {value}") + root.set(key[1:], value) + + +def _build_element(value: Any, root: _ElementTreeBuilder.Element) -> None: + if isinstance(value, str): + root.text = value + elif isinstance(value, dict): + _build_from_dict(value, root) + else: + raise TypeError(f"Invalid type in dict_to_elements_tree: {type(value)}") + + +def _build_from_dict(mapping: Dict[str, Any], root: _ElementTreeBuilder.Element) -> None: + for key, value in mapping.items(): + if key.startswith("#"): + _set_text_node(root, key, value) + elif key.startswith("@"): + _set_attribute(root, key, value) + elif isinstance(value, list): + for element in value: + _build_element(element, _ElementTreeBuilder.SubElement(root, key)) + else: + _build_element(value, _ElementTreeBuilder.SubElement(root, key)) + + def dict_to_elements_tree(json_dict: Dict[str, Any]) -> str: """ 將字典轉換為 XML 字串 @@ -51,32 +91,10 @@ def dict_to_elements_tree(json_dict: Dict[str, Any]) -> str: :param json_dict: JSON 格式字典 (Dictionary in JSON-like format) :return: XML 字串 (XML string) """ - - def _to_elements_tree(json_dict: Any, root: _ElementTreeBuilder.Element) -> None: - if isinstance(json_dict, str): - root.text = json_dict - elif isinstance(json_dict, dict): - for key, value in json_dict.items(): - if key.startswith("#"): # 處理文字節點 - if key != "#text" or not isinstance(value, str): - raise TypeError(f"Invalid text node: {key} -> {value}") - root.text = value - elif key.startswith("@"): # 處理屬性 - if not isinstance(value, str): - raise TypeError(f"Invalid attribute value: {key} -> {value}") - root.set(key[1:], value) - elif isinstance(value, list): # 處理子節點清單 - for element in value: - _to_elements_tree(element, _ElementTreeBuilder.SubElement(root, key)) - else: # 處理單一子節點 - _to_elements_tree(value, _ElementTreeBuilder.SubElement(root, key)) - else: - raise TypeError(f"Invalid type in dict_to_elements_tree: {type(json_dict)}") - if not isinstance(json_dict, dict) or len(json_dict) != 1: raise ValueError("Input must be a dictionary with a single root element") tag, body = next(iter(json_dict.items())) node = _ElementTreeBuilder.Element(tag) - _to_elements_tree(body, node) - return _ElementTreeBuilder.tostring(node, encoding="utf-8").decode("utf-8") \ No newline at end of file + _build_element(body, node) + return _ElementTreeBuilder.tostring(node, encoding="utf-8").decode("utf-8") diff --git a/test/test_callback_executor.py b/test/test_callback_executor.py index 39d823b..caf87d0 100644 --- a/test/test_callback_executor.py +++ b/test/test_callback_executor.py @@ -9,7 +9,7 @@ def _dummy_trigger(**kwargs): def _dummy_callback(*args, **kwargs): - pass + """No-op stub used to verify the executor invokes the callback path.""" class TestCallbackFunctionExecutor: diff --git a/test/test_proxy_user.py b/test/test_proxy_user.py index c0a3a54..87608ce 100644 --- a/test/test_proxy_user.py +++ b/test/test_proxy_user.py @@ -12,10 +12,10 @@ def test_init_defaults(self): def test_configure(self): user = ProxyHTTPUser() - detail = {"user": "http_user", "host": "http://localhost"} + detail = {"user": "http_user", "host": "https://localhost"} tasks = { - "get": {"request_url": "http://example.com/get"}, - "post": {"request_url": "http://example.com/post"}, + "get": {"request_url": "https://example.com/get"}, + "post": {"request_url": "https://example.com/post"}, } user.configure(detail, tasks) assert user.user_detail_dict == detail @@ -40,7 +40,7 @@ def test_init_defaults(self): def test_configure(self): user = ProxyFastHTTPUser() detail = {"user": "fast_http_user"} - tasks = {"get": {"request_url": "http://example.com"}} + tasks = {"get": {"request_url": "https://example.com"}} user.configure(detail, tasks) assert user.user_detail_dict == detail assert user.tasks == tasks diff --git a/test/test_report_generation.py b/test/test_report_generation.py index bc345b4..8686dca 100644 --- a/test/test_report_generation.py +++ b/test/test_report_generation.py @@ -14,7 +14,7 @@ _SUCCESS_RECORD = { "Method": "GET", - "test_url": "http://example.com/get", + "test_url": "https://example.com/get", "name": "/get", "status_code": "200", "text": "OK", @@ -25,7 +25,7 @@ _FAILURE_RECORD = { "Method": "POST", - "test_url": "http://example.com/post", + "test_url": "https://example.com/post", "name": "/post", "status_code": "500", "error": "Internal Server Error", @@ -111,7 +111,7 @@ def test_no_data_raises(self): def test_success_xml(self): test_record_instance.test_record_list.append(_SUCCESS_RECORD) - success_xml, failure_xml = generate_xml() + success_xml, _ = generate_xml() assert "GET" in success_xml assert "GET" in xml_str - assert "http://example.com" in xml_str + assert "https://example.com" in xml_str