Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions json2xml/dicttoxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,19 @@ def make_id(element: str, start: int = 100000, end: int = 999999) -> str:
return f"{element}_{safe_random.randint(start, end)}"


def get_unique_id(element: str) -> str:
def get_unique_id(element: str, ids: list[str] | None = None) -> str:
"""
Generate a unique ID for a given element.

Args:
element (str): The element to generate an ID for.
ids (list[str] | None, optional): A list of existing IDs to avoid duplicates. Defaults to None.

Returns:
str: The unique ID.
"""
ids: list[str] = [] # initialize list of unique ids
if ids is None:
ids = []
this_id = make_id(element)
dup = True
while dup:
Expand Down Expand Up @@ -78,9 +80,9 @@ def get_xml_type(val: ELEMENT) -> str:
str: The XML type.
"""
if val is not None:
if type(val).__name__ in ("str", "unicode"):
if type(val).__name__ == "str":
return "str"
if type(val).__name__ in ("int", "long"):
if type(val).__name__ == "int":
return "int"
if type(val).__name__ == "float":
return "float"
Expand Down
101 changes: 101 additions & 0 deletions tests/test_additional_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import decimal
from typing import Any, cast

import pytest

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'pytest' is not used.

Copilot Autofix

AI about 1 month ago

To fix the problem, simply remove the unused import statement import pytest from line 4 of tests/test_additional_coverage.py. This will clean up the code by eliminating an unnecessary dependency and improving readability. No other changes are required, as the rest of the code does not reference pytest directly.

Suggested changeset 1
tests/test_additional_coverage.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tests/test_additional_coverage.py b/tests/test_additional_coverage.py
--- a/tests/test_additional_coverage.py
+++ b/tests/test_additional_coverage.py
@@ -3,3 +3,3 @@
 
-import pytest
+
 
EOF
@@ -3,3 +3,3 @@

import pytest


Copilot is powered by AI and may make mistakes. Always verify output.

from json2xml import dicttoxml


class TestAdditionalCoverage:
def test_wrap_cdata_handles_cdata_end(self) -> None:
# Ensure CDATA splitting works for "]]>" sequence
text = "a]]>b"
wrapped = dicttoxml.wrap_cdata(text)
assert wrapped == "<![CDATA[a]]]]><![CDATA[>b]]>"

def test_make_valid_xml_name_with_int_key(self) -> None:
# Int keys should be converted to n<digits>
key, attr = dicttoxml.make_valid_xml_name(123, {}) # type: ignore[arg-type]
assert key == "n123"
assert attr == {}

def test_make_valid_xml_name_namespace_flat(self) -> None:
# Namespaced key with @flat suffix should be considered valid as-is
key_in = "ns:key@flat"
key_out, attr = dicttoxml.make_valid_xml_name(key_in, {})
assert key_out == key_in
assert attr == {}
Comment on lines +22 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider adding a test for negative integer keys in make_valid_xml_name.

Negative integer keys may be handled differently and could cause issues with XML naming. Please add a test for a negative integer key to verify correct behavior.

Suggested change
def test_make_valid_xml_name_namespace_flat(self) -> None:
# Namespaced key with @flat suffix should be considered valid as-is
key_in = "ns:key@flat"
key_out, attr = dicttoxml.make_valid_xml_name(key_in, {})
assert key_out == key_in
assert attr == {}
def test_make_valid_xml_name_with_negative_int_key(self) -> None:
# Negative int keys should be converted to a valid XML name, e.g., n-123
key, attr = dicttoxml.make_valid_xml_name(-123, {}) # type: ignore[arg-type]
assert key.startswith("n")
assert "-" in key
assert "123" in key
assert attr == {}
def test_make_valid_xml_name_namespace_flat(self) -> None:
# Namespaced key with @flat suffix should be considered valid as-is
key_in = "ns:key@flat"
key_out, attr = dicttoxml.make_valid_xml_name(key_in, {})
assert key_out == key_in
assert attr == {}


def test_dict2xml_str_parent_list_with_attrs_and_no_wrap(self) -> None:
# When inside list context with list_headers=True and item_wrap=False,
# attributes belong to the parent element header
item = {"@attrs": {"a": "b"}, "@val": "X"}
xml = dicttoxml.dict2xml_str(
attr_type=False,
attr={},
item=item,
item_func=lambda _p: "item",
cdata=False,
Comment on lines +29 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider adding a test for multiple attributes in parent list context.

Please add a test case with multiple attributes to verify correct serialization in the parent element.

item_name="ignored",
item_wrap=False,
parentIsList=True,
parent="Parent",
list_headers=True,
)
assert xml == '<Parent a="b">X</Parent>'

def test_dict2xml_str_with_flat_flag_in_item(self) -> None:
# If @flat=True, the subtree should not be wrapped
item = {"@val": "text", "@flat": True}
xml = dicttoxml.dict2xml_str(
attr_type=False,
attr={},
item=item,
item_func=lambda _p: "item",
cdata=False,
item_name="ignored",
item_wrap=True,
parentIsList=False,
)
assert xml == "text"

def test_list2xml_str_returns_subtree_when_list_headers_true(self) -> None:
# list_headers=True should return subtree directly from convert_list
xml = dicttoxml.list2xml_str(
attr_type=False,
attr={},
item=["a"],
item_func=lambda _p: "item",
cdata=False,
item_name="test",
item_wrap=True,
list_headers=True,
)
assert xml == "<item>a</item>"

def test_get_xml_type_with_decimal_number(self) -> None:
# Decimal is a numbers.Number but not int/float
value = decimal.Decimal("5")
assert dicttoxml.get_xml_type(cast(Any, value)) == "number"
# And convert_kv should mark it as type="number"
out = dicttoxml.convert_kv("key", cast(Any, value), attr_type=True)
assert out == '<key type="number">5</key>'

def test_dicttoxml_cdata_with_cdata_end_sequence(self) -> None:
data = {"key": "a]]>b"}
out = dicttoxml.dicttoxml(data, root=False, attr_type=False, cdata=True).decode()
assert out == "<key><![CDATA[a]]]]><![CDATA[>b]]></key>"

def test_convert_dict_with_ids_adds_id_attributes(self) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider adding a test for multiple CDATA end sequences in a single value.

Please include a test case where the value contains more than one ']]>' sequence to verify correct handling in all instances.

Suggested change
def test_convert_dict_with_ids_adds_id_attributes(self) -> None:
def test_dicttoxml_cdata_with_multiple_cdata_end_sequences(self) -> None:
data = {"key": "a]]>b]]>c"}
out = dicttoxml.dicttoxml(data, root=False, attr_type=False, cdata=True).decode()
assert out == "<key><![CDATA[a]]]]><![CDATA[>b]]]]><![CDATA[>c]]></key>"
def test_convert_dict_with_ids_adds_id_attributes(self) -> None:

obj: dict[str, Any] = {"a": 1, "b": 2}
xml = dicttoxml.convert_dict(
obj=obj,
ids=["seed"],
parent="root",
attr_type=False,
item_func=lambda _p: "item",
cdata=False,
item_wrap=True,
)
# Both elements should carry some id attribute
assert xml.count(' id="') == 2
49 changes: 8 additions & 41 deletions tests/test_dict2xml.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import numbers
from typing import TYPE_CHECKING, Any
from unittest.mock import Mock

import pytest

Expand Down Expand Up @@ -774,50 +775,16 @@ def test_dicttoxml_with_cdata(self) -> None:
result = dicttoxml.dicttoxml(data, cdata=True, attr_type=False, root=False)
assert b"<key><![CDATA[value]]></key>" == result

def test_get_unique_id_with_duplicates(self) -> None:
def test_get_unique_id_with_duplicates(self, monkeypatch: "MonkeyPatch") -> None:
"""Test get_unique_id when duplicates are generated."""
# We need to modify the original get_unique_id to simulate a pre-existing ID list
import json2xml.dicttoxml as module
ids = ["existing_id"]
make_id_mock = Mock(side_effect=["existing_id", "new_id"])
monkeypatch.setattr(dicttoxml, "make_id", make_id_mock)

# Save original function
original_get_unique_id = module.get_unique_id

# Track make_id calls
call_count = 0
original_make_id = module.make_id

def mock_make_id(element: str, start: int = 100000, end: int = 999999) -> str:
nonlocal call_count
call_count += 1
if call_count == 1:
return "test_123456" # First call - will collide
else:
return "test_789012" # Second call - unique

# Patch get_unique_id to use a pre-populated ids list
def patched_get_unique_id(element: str) -> str:
# Start with a pre-existing ID to force collision
ids = ["test_123456"]
this_id = module.make_id(element)
dup = True
while dup:
if this_id not in ids:
dup = False
ids.append(this_id)
else:
this_id = module.make_id(element) # This exercises line 52
return ids[-1]

module.make_id = mock_make_id
module.get_unique_id = patched_get_unique_id
unique_id = dicttoxml.get_unique_id("some_element", ids=ids)

try:
result = dicttoxml.get_unique_id("test")
assert result == "test_789012"
assert call_count == 2
finally:
module.make_id = original_make_id
module.get_unique_id = original_get_unique_id
assert unique_id == "new_id"
assert make_id_mock.call_count == 2

def test_convert_with_bool_direct(self) -> None:
"""Test convert function with boolean input directly."""
Expand Down
Loading