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