|
| 1 | +""" |
| 2 | +Regression tests added when the ``ujson`` dependency was removed: pin the |
| 3 | +exact byte output of ``JsonSerializer`` (sorted keys, compact separators, |
| 4 | +raw UTF-8), since it feeds signing/hashing and must stay byte-stable. |
| 5 | +""" |
| 6 | +import pytest |
| 7 | + |
| 8 | +from common.serializers.json_serializer import JsonSerializer |
| 9 | + |
| 10 | + |
| 11 | +@pytest.fixture |
| 12 | +def sz(): |
| 13 | + return JsonSerializer() |
| 14 | + |
| 15 | + |
| 16 | +def test_keys_sorted_and_compact(sz): |
| 17 | + # Insertion order must not affect output; keys are sorted, no whitespace. |
| 18 | + assert sz.serialize({'b': 2, 'a': 1, 'c': 3}, toBytes=False) == '{"a":1,"b":2,"c":3}' |
| 19 | + |
| 20 | + |
| 21 | +def test_non_ascii_kept_raw_utf8(sz): |
| 22 | + # ensure_ascii=False: characters are emitted as raw UTF-8, not \\uXXXX. |
| 23 | + assert sz.serialize({'name': 'héllo', 'kr': '한글', 'emoji': '🚀'}, |
| 24 | + toBytes=False) == '{"emoji":"🚀","kr":"한글","name":"héllo"}' |
| 25 | + # And the bytes form is the UTF-8 encoding of that string. |
| 26 | + assert sz.serialize({'name': 'héllo'}) == '{"name":"héllo"}'.encode('utf-8') |
| 27 | + |
| 28 | + |
| 29 | +def test_float_repr_pinned(sz): |
| 30 | + assert sz.serialize({'a': 14.8639, 'b': -97.466179, 'c': 1.0, 'd': 1e20}, |
| 31 | + toBytes=False) == '{"a":14.8639,"b":-97.466179,"c":1.0,"d":1e+20}' |
| 32 | + |
| 33 | + |
| 34 | +def test_int_keys_become_strings(sz): |
| 35 | + # json coerces non-string keys to strings and then sorts lexicographically. |
| 36 | + assert sz.serialize({3: 'c', 1: 'a', 2: 'b'}, toBytes=False) == '{"1":"a","2":"b","3":"c"}' |
| 37 | + |
| 38 | + |
| 39 | +def test_bool_and_none(sz): |
| 40 | + assert sz.serialize({'t': True, 'f': False, 'n': None}, |
| 41 | + toBytes=False) == '{"f":false,"n":null,"t":true}' |
| 42 | + |
| 43 | + |
| 44 | +def test_empty_dict(sz): |
| 45 | + assert sz.serialize({}, toBytes=False) == '{}' |
| 46 | + |
| 47 | + |
| 48 | +def test_top_level_bytes_base64(sz): |
| 49 | + # The OrderedJsonEncoder.encode override base64-encodes a top-level |
| 50 | + # bytes/bytearray value (b'raw' -> base64 'cmF3'). |
| 51 | + assert sz.serialize(b'raw', toBytes=False) == '"cmF3"' |
| 52 | + assert sz.serialize(bytearray(b'raw'), toBytes=False) == '"cmF3"' |
| 53 | + |
| 54 | + |
| 55 | +def test_round_trip(sz): |
| 56 | + data = {'name': 'Alice', 'n': 1, 'f': 1.5, 'b': True, 'list': [1, 'two', None]} |
| 57 | + assert sz.deserialize(sz.serialize(data)) == data |
| 58 | + assert sz.deserialize(sz.serialize(data, toBytes=False)) == data |
| 59 | + |
| 60 | + |
| 61 | +@pytest.mark.parametrize('value', [ |
| 62 | + {'z': b'raw'}, # bytes nested in a dict value |
| 63 | + [b'raw'], # bytes nested in a list |
| 64 | + {'z': bytearray(b'raw')}, |
| 65 | +]) |
| 66 | +def test_nested_bytes_raise_typeerror(sz, value): |
| 67 | + """ |
| 68 | + The bytes special-case in ``OrderedJsonEncoder.encode`` only fires for a |
| 69 | + top-level value; bytes nested in a container raise TypeError. A fix, if |
| 70 | + ever needed, belongs in ``OrderedJsonEncoder.default``. |
| 71 | + """ |
| 72 | + with pytest.raises(TypeError): |
| 73 | + sz.serialize(value) |
0 commit comments