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.
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 6935003..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,
@@ -1144,37 +1142,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."""
- import json2xml.dicttoxml as module
-
- 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 = 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)
- return ids[-1]
-
- monkeypatch.setattr(module, "make_id", mock_make_id)
- monkeypatch.setattr(module, "get_unique_id", patched_get_unique_id)
+ calls.append(element)
+ return "test_123456"
+
+ monkeypatch.setattr(dicttoxml, "make_id", mock_make_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."""
@@ -1416,16 +1398,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,
@@ -1522,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
@@ -1538,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 == ""
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,
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
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)
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(