|
20 | 20 | from json2xml.dicttoxml_fast import ( |
21 | 21 | dicttoxml as fast_dicttoxml, |
22 | 22 | ) |
| 23 | +from json2xml.dicttoxml_fast import ( |
| 24 | + escape_xml as fast_escape_xml, |
| 25 | +) |
23 | 26 | from json2xml.dicttoxml_fast import ( |
24 | 27 | get_backend, |
25 | 28 | is_rust_available, |
26 | 29 | ) |
| 30 | +from json2xml.dicttoxml_fast import ( |
| 31 | + wrap_cdata as fast_wrap_cdata, |
| 32 | +) |
27 | 33 |
|
28 | 34 | # Skip all tests if Rust is not available |
29 | 35 | pytestmark = pytest.mark.skipif(not RUST_AVAILABLE, reason="Rust extension not installed") |
@@ -453,3 +459,114 @@ def test_deeply_nested_structure(self): |
453 | 459 | current = current["level"] |
454 | 460 | result = rust_dicttoxml(data) |
455 | 461 | assert b"<level" in result |
| 462 | + |
| 463 | + |
| 464 | +class TestFastDicttoxmlHelpers: |
| 465 | + """Test helper functions in dicttoxml_fast module.""" |
| 466 | + |
| 467 | + def test_escape_xml_via_rust(self): |
| 468 | + """Test escape_xml uses Rust backend when available.""" |
| 469 | + result = fast_escape_xml("Hello <World> & 'Friends'") |
| 470 | + assert "<" in result |
| 471 | + assert ">" in result |
| 472 | + assert "&" in result |
| 473 | + assert "'" in result |
| 474 | + |
| 475 | + def test_escape_xml_empty_string(self): |
| 476 | + """Test escape_xml with empty string.""" |
| 477 | + result = fast_escape_xml("") |
| 478 | + assert result == "" |
| 479 | + |
| 480 | + def test_escape_xml_no_special_chars(self): |
| 481 | + """Test escape_xml with no special characters.""" |
| 482 | + result = fast_escape_xml("Hello World") |
| 483 | + assert result == "Hello World" |
| 484 | + |
| 485 | + def test_wrap_cdata_via_rust(self): |
| 486 | + """Test wrap_cdata uses Rust backend when available.""" |
| 487 | + result = fast_wrap_cdata("Hello <World>") |
| 488 | + assert result == "<![CDATA[Hello <World>]]>" |
| 489 | + |
| 490 | + def test_wrap_cdata_empty_string(self): |
| 491 | + """Test wrap_cdata with empty string.""" |
| 492 | + result = fast_wrap_cdata("") |
| 493 | + assert result == "<![CDATA[]]>" |
| 494 | + |
| 495 | + def test_wrap_cdata_with_cdata_end_sequence(self): |
| 496 | + """Test wrap_cdata handles ]]> in content.""" |
| 497 | + result = fast_wrap_cdata("Content with ]]> inside") |
| 498 | + assert "]]>" in result |
| 499 | + assert result.startswith("<![CDATA[") |
| 500 | + |
| 501 | + def test_special_keys_in_nested_list(self): |
| 502 | + """Test that special keys in lists trigger Python fallback.""" |
| 503 | + # List containing dicts with special keys |
| 504 | + data = {"items": [{"@attrs": {"id": "1"}, "@val": "value"}]} |
| 505 | + result = fast_dicttoxml(data) |
| 506 | + # Should use Python fallback and handle @attrs correctly |
| 507 | + assert b'id="1"' in result |
| 508 | + |
| 509 | + def test_special_keys_deep_in_list(self): |
| 510 | + """Test special keys detection in deeply nested list structures.""" |
| 511 | + data = { |
| 512 | + "outer": [ |
| 513 | + {"normal": "value"}, |
| 514 | + {"nested": {"@attrs": {"class": "test"}, "@val": "content"}} |
| 515 | + ] |
| 516 | + } |
| 517 | + result = fast_dicttoxml(data) |
| 518 | + assert b'class="test"' in result |
| 519 | + |
| 520 | + def test_list_with_flat_key(self): |
| 521 | + """Test @flat key handling in lists.""" |
| 522 | + data = {"items": [{"name@flat": "John"}]} |
| 523 | + result = fast_dicttoxml(data) |
| 524 | + # Should use Python fallback for @flat handling |
| 525 | + assert b"John" in result |
| 526 | + |
| 527 | + |
| 528 | +class TestFastDicttoxmlPythonFallback: |
| 529 | + """Test Python fallback paths in dicttoxml_fast module.""" |
| 530 | + |
| 531 | + def test_escape_xml_python_fallback(self): |
| 532 | + """Test escape_xml falls back to Python when Rust unavailable.""" |
| 533 | + from unittest.mock import patch |
| 534 | + |
| 535 | + import json2xml.dicttoxml_fast as fast_module |
| 536 | + |
| 537 | + # Temporarily mock _USE_RUST to False |
| 538 | + with patch.object(fast_module, '_USE_RUST', False): |
| 539 | + result = fast_module.escape_xml("Hello <World>") |
| 540 | + assert "<" in result |
| 541 | + assert ">" in result |
| 542 | + |
| 543 | + def test_wrap_cdata_python_fallback(self): |
| 544 | + """Test wrap_cdata falls back to Python when Rust unavailable.""" |
| 545 | + from unittest.mock import patch |
| 546 | + |
| 547 | + import json2xml.dicttoxml_fast as fast_module |
| 548 | + |
| 549 | + # Temporarily mock _USE_RUST to False |
| 550 | + with patch.object(fast_module, '_USE_RUST', False): |
| 551 | + result = fast_module.wrap_cdata("Hello World") |
| 552 | + assert result == "<![CDATA[Hello World]]>" |
| 553 | + |
| 554 | + def test_escape_xml_fallback_when_rust_func_none(self): |
| 555 | + """Test escape_xml falls back when rust_escape_xml is None.""" |
| 556 | + from unittest.mock import patch |
| 557 | + |
| 558 | + import json2xml.dicttoxml_fast as fast_module |
| 559 | + |
| 560 | + with patch.object(fast_module, 'rust_escape_xml', None): |
| 561 | + result = fast_module.escape_xml("Test & Value") |
| 562 | + assert "&" in result |
| 563 | + |
| 564 | + def test_wrap_cdata_fallback_when_rust_func_none(self): |
| 565 | + """Test wrap_cdata falls back when rust_wrap_cdata is None.""" |
| 566 | + from unittest.mock import patch |
| 567 | + |
| 568 | + import json2xml.dicttoxml_fast as fast_module |
| 569 | + |
| 570 | + with patch.object(fast_module, 'rust_wrap_cdata', None): |
| 571 | + result = fast_module.wrap_cdata("Test Content") |
| 572 | + assert result == "<![CDATA[Test Content]]>" |
0 commit comments