From fd3e51fdc3c31608e0b31798f2a1c22ab6683339 Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:07:26 +0530 Subject: [PATCH 1/9] Fix hash mismatch in unit fixture Define equality for the numeric test helper so its custom hash follows Python's equality and hashing contract. --- tests/test_dicttoxml_unit.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_dicttoxml_unit.py b/tests/test_dicttoxml_unit.py index 8dcf10f..aecd556 100644 --- a/tests/test_dicttoxml_unit.py +++ b/tests/test_dicttoxml_unit.py @@ -13,6 +13,9 @@ class CustomNumber(numbers.Number): + def __eq__(self, other: object) -> bool: + return isinstance(other, CustomNumber) + def __hash__(self) -> int: return 7 From 8711df3b44060ff9079eee80ad4e6f4c7be36dc1 Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:07:32 +0530 Subject: [PATCH 2/9] Fix hash mismatch in coverage fixture Compare the numeric test double's stored value so equality remains compatible with its required hash implementation. --- tests/test_missing_coverage.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_missing_coverage.py b/tests/test_missing_coverage.py index 737fd5b..452709b 100644 --- a/tests/test_missing_coverage.py +++ b/tests/test_missing_coverage.py @@ -203,6 +203,11 @@ def __init__(self, value: float) -> None: def __str__(self) -> str: return str(self.value) + def __eq__(self, other: object) -> bool: + if not isinstance(other, CustomNumber): + return NotImplemented + return self.value == other.value + def __hash__(self) -> int: return hash(self.value) From dd16e3ec1004157ac173eae66af401eae4641300 Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:07:39 +0530 Subject: [PATCH 3/9] Use one fast backend import style Reference Rust binding helpers through the imported module so CodeQL no longer sees duplicate import forms. --- tests/test_dicttoxml_fast_fallback.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_dicttoxml_fast_fallback.py b/tests/test_dicttoxml_fast_fallback.py index 0584a1b..b14f031 100644 --- a/tests/test_dicttoxml_fast_fallback.py +++ b/tests/test_dicttoxml_fast_fallback.py @@ -13,7 +13,6 @@ import json2xml.dicttoxml_fast as fast_module from json2xml import dicttoxml as py_dicttoxml from json2xml.backend_selector import rust_renders_identically -from json2xml.dicttoxml_fast import _RustBindings def _gate_with_limits( @@ -23,7 +22,7 @@ def _gate_with_limits( return rust_renders_identically(obj) -def _fake_bindings(**overrides: Any) -> _RustBindings: +def _fake_bindings(**overrides: Any) -> fast_module._RustBindings: """Build Rust bindings backed by the Python reference implementations.""" fields: dict[str, Any] = { "dicttoxml": Mock(return_value=b""), @@ -33,7 +32,7 @@ def _fake_bindings(**overrides: Any) -> _RustBindings: "enforces_limits": True, } fields.update(overrides) - return _RustBindings(**fields) + return fast_module._RustBindings(**fields) def _force_rust_backend(monkeypatch: pytest.MonkeyPatch) -> Mock: @@ -98,7 +97,7 @@ def test_loader_binds_a_complete_extension(monkeypatch: pytest.MonkeyPatch) -> N bindings = fast_module._load_rust_bindings() - assert bindings == _RustBindings( + assert bindings == fast_module._RustBindings( dicttoxml=_COMPLETE_EXPORTS["dicttoxml"], payload_is_supported=rust_renders_identically, escape_xml=py_dicttoxml.escape_xml, From 0a27d435ff064a4dc69b16a075128862e882482d Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:07:48 +0530 Subject: [PATCH 4/9] Use one Rust backend import style Bind backend helpers from the module import so CodeQL no longer reports mixed module and symbol imports. --- tests/test_rust_dicttoxml.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/tests/test_rust_dicttoxml.py b/tests/test_rust_dicttoxml.py index 4439ce6..6946926 100644 --- a/tests/test_rust_dicttoxml.py +++ b/tests/test_rust_dicttoxml.py @@ -30,20 +30,13 @@ from json2xml import dicttoxml as py_dicttoxml from json2xml.backend_selector import rust_renders_identically from json2xml.dicttoxml import SerializerConfig, default_item_func -from json2xml.dicttoxml_fast import ( - _RustBackendAdapter, - get_backend, - is_rust_available, -) -from json2xml.dicttoxml_fast import ( - dicttoxml as fast_dicttoxml, -) -from json2xml.dicttoxml_fast import ( - escape_xml as fast_escape_xml, -) -from json2xml.dicttoxml_fast import ( - wrap_cdata as fast_wrap_cdata, -) + +_RustBackendAdapter = fast_module._RustBackendAdapter +get_backend = fast_module.get_backend +is_rust_available = fast_module.is_rust_available +fast_dicttoxml = fast_module.dicttoxml +fast_escape_xml = fast_module.escape_xml +fast_wrap_cdata = fast_module.wrap_cdata # Skip all tests if Rust is not available pytestmark = pytest.mark.skipif( From f79759948f016f94422ec13fe274801e531f3947 Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:08:02 +0530 Subject: [PATCH 5/9] Reuse serializer import in ID test Patch the existing module binding instead of importing dicttoxml a second way inside the test. --- tests/test_dict2xml.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_dict2xml.py b/tests/test_dict2xml.py index 6935003..ce20308 100644 --- a/tests/test_dict2xml.py +++ b/tests/test_dict2xml.py @@ -1146,8 +1146,6 @@ def test_dicttoxml_with_cdata(self) -> None: def test_get_unique_id_with_duplicates(self, monkeypatch: MonkeyPatch) -> None: """Test get_unique_id when duplicates are generated.""" - import json2xml.dicttoxml as module - call_count = 0 def mock_make_id(element: str, start: int = 100000, end: int = 999999) -> str: @@ -1159,18 +1157,18 @@ def mock_make_id(element: str, start: int = 100000, end: int = 999999) -> str: def patched_get_unique_id(element: str) -> str: ids = ["test_123456"] - this_id = module.make_id(element) + this_id = dicttoxml.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_id = dicttoxml.make_id(element) return ids[-1] - monkeypatch.setattr(module, "make_id", mock_make_id) - monkeypatch.setattr(module, "get_unique_id", patched_get_unique_id) + monkeypatch.setattr(dicttoxml, "make_id", mock_make_id) + monkeypatch.setattr(dicttoxml, "get_unique_id", patched_get_unique_id) result = dicttoxml.get_unique_id("test") assert result == "test_789012" From 024f81613a1018b4dffaf25a99206ae281b1f04a Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:08:08 +0530 Subject: [PATCH 6/9] Reuse serializer import in raw item test Patch the existing dicttoxml module binding instead of mixing module import forms inside the test. --- tests/test_dict2xml.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_dict2xml.py b/tests/test_dict2xml.py index ce20308..22ed999 100644 --- a/tests/test_dict2xml.py +++ b/tests/test_dict2xml.py @@ -1414,16 +1414,14 @@ def test_dict2xml_str_with_primitive_dict_rawitem( self, monkeypatch: MonkeyPatch ) -> None: """Test dict2xml_str with primitive dict as rawitem to trigger line 274.""" - import json2xml.dicttoxml as module - - original_is_primitive = module.is_primitive_type + original_is_primitive = dicttoxml.is_primitive_type def mock_is_primitive(val: Any) -> bool: if isinstance(val, dict) and val == {"test": "data"}: return True return original_is_primitive(val) - monkeypatch.setattr(module, "is_primitive_type", mock_is_primitive) + monkeypatch.setattr(dicttoxml, "is_primitive_type", mock_is_primitive) item = {"@val": {"test": "data"}} result = dicttoxml.dict2xml_str( attr_type=False, From 519ae6c0b169bb60a1256f54364825d0989a2939 Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:08:15 +0530 Subject: [PATCH 7/9] Document CodeQL test fixture rules Record the import and equality conventions that keep test-only helpers from creating noisy scanning alerts. --- lat.md/architecture.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lat.md/architecture.md b/lat.md/architecture.md index 5bab6e6..871c222 100644 --- a/lat.md/architecture.md +++ b/lat.md/architecture.md @@ -106,6 +106,8 @@ Dependabot checks the root and documentation Python dependency manifests weekly, GitHub Actions workflows run with read-only tokens by default and use full SHA pins so third-party action updates are explicit. +Test fixtures keep one import form per module and avoid custom hashes without matching equality so CodeQL findings stay actionable. + The `.github/workflows/` files declare the minimum `permissions:` scopes needed by each workflow, with CodeQL retaining `security-events: write` for result upload and TestPyPI retaining `id-token: write` for explicit trusted-publishing runs. The Python test job also scopes `id-token: write` and `FLAKINESS_PROJECT` to its pytest matrix so the flakiness reporter can authenticate through GitHub OIDC without exposing that permission to lint. Release-branch pushes build distributions and runs Twine checks; TestPyPI upload is a manual opt-in because that external registry requires separate publisher configuration. Action references are pinned to immutable commits with the upstream tag retained in comments for reviewability, and all CodeQL phases use the same release commit because mixed action versions reject each other's configuration. `.github/dependabot.yml` checks the `github-actions` ecosystem weekly so those pins do not silently age. The Python test matrix pins its PyPy 3.11 job to an explicit PyPy release so CI keeps exercising the intended CPython 3.11.15-compatible runtime instead of silently drifting with runner cache updates. The Python, Rust extension, and Rust wheel test workflows exercise stable CPython 3.15.0; Python 3.15 free-threaded builds remain outside the CI matrix. The native backend is only selected for payloads it renders byte-identically. [[json2xml/backend_selector.py#rust_renders_identically]] is the reference definition of that subset -- dict, list, and exact `str`, `bool`, `int`, `float`, and `None` values, under keys both implementations name the same way -- and `payload_is_supported` in the crate is its native form, used at dispatch so the walk does not cost more than the conversion it guards. Subclasses and non-JSON-native values stay on Python because its isinstance fallbacks classify them in ways the native writer does not reproduce. Tuples also stay on Python: it applies its list-shape rules to them, while the native writer only recognizes lists. From cfafd1f9cad7efb479c8e076a2d64c41086930f8 Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:16:45 +0530 Subject: [PATCH 8/9] Test production unique ID delegation Patch only make_id so the test calls get_unique_id itself and can fail when its production delegation changes. --- lat.md/tests.md | 4 ++++ tests/test_dict2xml.py | 32 +++++++++----------------------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/lat.md/tests.md b/lat.md/tests.md index d673af8..533b1fa 100644 --- a/lat.md/tests.md +++ b/lat.md/tests.md @@ -342,6 +342,10 @@ These tests pin low-level XML helper contracts so performance refactors keep the Common built-in numbers should avoid abstract-class dispatch while `Decimal`, `Fraction`, complex, and custom `Number` implementations remain supported. +### Unique ID generation delegates to make_id + +The public unique-ID helper should return the production `make_id` result so its test exercises the real delegation instead of replacing the function under test. + ### Date-like values serialize the same in every position Objects exposing `isoformat` should serialize identically as a root value, a dict value, and a list item, because one classifier decides the kind for all three writers. diff --git a/tests/test_dict2xml.py b/tests/test_dict2xml.py index 22ed999..8b61ee5 100644 --- a/tests/test_dict2xml.py +++ b/tests/test_dict2xml.py @@ -1144,35 +1144,21 @@ def test_dicttoxml_with_cdata(self) -> None: result = dicttoxml.dicttoxml(data, cdata=True, attr_type=False, root=False) assert b"" == result - def test_get_unique_id_with_duplicates(self, monkeypatch: MonkeyPatch) -> None: - """Test get_unique_id when duplicates are generated.""" - call_count = 0 + # @lat: [[tests#Tests#XML helper behavior#Unique ID generation delegates to make_id]] + def test_get_unique_id_uses_make_id(self, monkeypatch: MonkeyPatch) -> None: + """Test get_unique_id delegates to the production make_id helper.""" + calls: list[str] = [] 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" - return "test_789012" - - def patched_get_unique_id(element: str) -> str: - ids = ["test_123456"] - this_id = dicttoxml.make_id(element) - dup = True - while dup: - if this_id not in ids: - dup = False - ids.append(this_id) - else: - this_id = dicttoxml.make_id(element) - return ids[-1] + calls.append(element) + return "test_123456" monkeypatch.setattr(dicttoxml, "make_id", mock_make_id) - monkeypatch.setattr(dicttoxml, "get_unique_id", patched_get_unique_id) result = dicttoxml.get_unique_id("test") - assert result == "test_789012" - assert call_count == 2 + + assert result == "test_123456" + assert calls == ["test"] def test_convert_with_bool_direct(self) -> None: """Test convert function with boolean input directly.""" From 2c8c9c1a5223037c68efd3eac47fb009554ff7ea Mon Sep 17 00:00:00 2001 From: Vinit Kumar Date: Mon, 26 Oct 2026 12:16:56 +0530 Subject: [PATCH 9/9] Use one serializer import style Route the remaining helper calls through the existing module binding so CodeQL sees one import form in the test module. --- tests/test_dict2xml.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/test_dict2xml.py b/tests/test_dict2xml.py index 8b61ee5..e1b9e9a 100644 --- a/tests/test_dict2xml.py +++ b/tests/test_dict2xml.py @@ -346,12 +346,10 @@ def test_list_parent_elements(self) -> None: def test_dict2xml_str_list_header(self) -> None: """Test dict2xml_str with list headers.""" - from json2xml.dicttoxml import dict2xml_str - item_func = dicttoxml.default_item_func item = {"frame_color": "red"} parent = "Bike" - xml_str = dict2xml_str( + xml_str = dicttoxml.dict2xml_str( attr_type=False, attr={}, item=item, @@ -1504,15 +1502,13 @@ def test_attrs_empty_and_none_values(self) -> None: def test_make_attrstring_function_directly(self) -> None: """Test the make_attrstring function directly.""" - from json2xml.dicttoxml import make_attrstring - # Test basic escaping attrs = { "test": "value ", "ampersand": "Tom & Jerry", "quotes": 'Say "hello"', } - result = make_attrstring(attrs) + result = dicttoxml.make_attrstring(attrs) assert 'test="value <here>"' in result assert 'ampersand="Tom & Jerry"' in result @@ -1520,5 +1516,5 @@ def test_make_attrstring_function_directly(self) -> None: # Test empty attributes empty_attrs: dict[str, Any] = {} - result = make_attrstring(empty_attrs) + result = dicttoxml.make_attrstring(empty_attrs) assert result == ""