Skip to content

Conversation

vinitkumar
Copy link
Owner

@vinitkumar vinitkumar commented Aug 8, 2025

Summary

  • Add targeted tests for edge branches in dicttoxml (CDATA end sequence, namespaced/numeric keys, list header and @Flat handling, Decimal typing, ids attribute behavior)
  • Refactor get_unique_id to accept an optional ids parameter for controllable uniqueness and simplify type checks in get_xml_type

Test plan

  • Run: python -m pip install -r requirements-dev.txt
  • Run: pytest -q --cov=json2xml --cov-report=term-missing
  • Verify no regressions and improved coverage, especially around dict2xml_str, list2xml_str, and CDATA handling

Summary by Sourcery

Allow external ID tracking in get_unique_id, refine XML type detection, and add targeted tests to cover CDATA handling, XML naming, list flattening, Decimal typing, and auto-generated IDs.

Enhancements:

  • Extend get_unique_id to accept an optional list of existing IDs for duplicate prevention
  • Simplify get_xml_type to only distinguish Python str, int, float, and number types

Tests:

  • Add tests for CDATA end-sequence splitting and wrapping
  • Add tests for valid XML name conversion of numeric and namespaced keys with @Flat handling
  • Add tests for dict2xml_str and list2xml_str behaviors including list headers and item_flat
  • Add tests for Decimal values to be treated as numbers and proper type attributes
  • Add tests for automatic id attribute assignment in convert_dict
  • Update test for get_unique_id to verify duplicate resolution with a provided ids list

…me handling

Adds coverage for CDATA edge, numeric and namespaced keys, list header behavior,
@Flat handling, Decimal typing, cdata conversion, and id attributes when ids provided.
…get_unique_id API

- Accept optional ids list to avoid collisions deterministically in tests
- Replace legacy type name checks with direct 'str'/'int'
- Update tests to use monkeypatch for duplicate id simulation
Copy link
Contributor

sourcery-ai bot commented Aug 8, 2025

Reviewer's Guide

This PR refactors the unique ID generation and XML type detection in dicttoxml for stronger typing and controlled uniqueness, and adds a suite of targeted tests to cover edge cases like CDATA splitting, namespaced/numeric keys, list wrapping behavior, Decimal handling, and ID attribute injection.

Class diagram for test coverage improvements in test_additional_coverage.py

classDiagram
    class test_additional_coverage
    test_additional_coverage : +test_cdata_end_sequence()
    test_additional_coverage : +test_namespaced_numeric_keys()
    test_additional_coverage : +test_list_header_flat_handling()
    test_additional_coverage : +test_decimal_typing()
    test_additional_coverage : +test_ids_attribute_behavior()
    test_additional_coverage --> dicttoxml : tests
Loading

File-Level Changes

Change Details Files
Refactor get_unique_id to accept optional ids list and improve uniqueness logic
  • Added ids parameter to get_unique_id signature
  • Initialized ids list only when not provided
  • Retained generation loop to avoid duplicates against passed-in IDs
json2xml/dicttoxml.py
Simplify get_xml_type by removing legacy type-name checks
  • Restricted str detection to type name 'str'
  • Restricted int detection to type name 'int'
  • Left other types to fall through to 'float' or 'number'
json2xml/dicttoxml.py
Update duplicate-ID test to leverage new ids parameter and monkeypatching
  • Refactored test to inject existing IDs list
  • Used Mock and monkeypatch for make_id calls
  • Asserted correct call count and returned unique ID
tests/test_dict2xml.py
Add targeted edge-case tests to increase coverage
  • Test wrap_cdata splitting around ']]>'
  • Validate XML name conversion for numeric and namespaced keys
  • Cover list/dict wrapping flags and list_headers behavior
  • Verify Decimal typing as 'number' and convert_kv output
  • Assert ID attributes in convert_dict output
tests/test_additional_coverage.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

codecov bot commented Aug 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (43c4e18) to head (8f19e8a).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff             @@
##           master      #246      +/-   ##
===========================================
+ Coverage   99.30%   100.00%   +0.69%     
===========================================
  Files           3         3              
  Lines         288       289       +1     
===========================================
+ Hits          286       289       +3     
+ Misses          2         0       -2     
Flag Coverage Δ
unittests 100.00% <100.00%> (+0.69%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @vinitkumar - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `tests/test_additional_coverage.py:16` </location>
<code_context>
+        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:
</code_context>

<issue_to_address>
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.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    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 == {}
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `tests/test_additional_coverage.py:29` </location>
<code_context>
+        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,
+            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:
</code_context>

<issue_to_address>
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.
</issue_to_address>

### Comment 3
<location> `tests/test_additional_coverage.py:62` </location>
<code_context>
+        )
+        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:
</code_context>

<issue_to_address>
Consider testing list2xml_str with an empty list.

Testing with an empty list helps ensure list2xml_str handles this edge case without errors or unexpected output. Please add a corresponding test.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    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(value) == "number"
        # And convert_kv should mark it as type="number"
        out = dicttoxml.convert_kv("key", value, attr_type=True)
        assert out == '<key type="number">5</key>'
=======
    def test_list2xml_str_with_empty_list(self) -> None:
        # Should handle empty list without error and return empty string
        xml = dicttoxml.list2xml_str(
            attr_type=False,
            attr={},
            item=[],
            item_func=lambda _p: "item",
            cdata=False,
            item_name="test",
            item_wrap=True,
            list_headers=True,
        )
        assert xml == ""

    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(value) == "number"
        # And convert_kv should mark it as type="number"
        out = dicttoxml.convert_kv("key", value, attr_type=True)
        assert out == '<key type="number">5</key>'
>>>>>>> REPLACE

</suggested_fix>

### Comment 4
<location> `tests/test_additional_coverage.py:84` </location>
<code_context>
+        out = dicttoxml.convert_kv("key", 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:
</code_context>

<issue_to_address>
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.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    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:
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +22 to +27
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 == {}
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 == {}

Comment on lines +29 to +38
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,
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.

Comment on lines 76 to 82
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(value) == "number"
# And convert_kv should mark it as type="number"
out = dicttoxml.convert_kv("key", value, attr_type=True)
assert out == '<key type="number">5</key>'
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 testing list2xml_str with an empty list.

Testing with an empty list helps ensure list2xml_str handles this edge case without errors or unexpected output. Please add a corresponding test.

Suggested change
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(value) == "number"
# And convert_kv should mark it as type="number"
out = dicttoxml.convert_kv("key", value, attr_type=True)
assert out == '<key type="number">5</key>'
def test_list2xml_str_with_empty_list(self) -> None:
# Should handle empty list without error and return empty string
xml = dicttoxml.list2xml_str(
attr_type=False,
attr={},
item=[],
item_func=lambda _p: "item",
cdata=False,
item_name="test",
item_wrap=True,
list_headers=True,
)
assert xml == ""
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(value) == "number"
# And convert_kv should mark it as type="number"
out = dicttoxml.convert_kv("key", value, attr_type=True)
assert out == '<key type="number">5</key>'

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:

import decimal
from typing import Any

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.
@vinitkumar vinitkumar changed the title Increase test coverage and strengthen dicttoxml typing feat: Increase test coverage and strengthen dicttoxml typing Aug 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant