import dataclasses # Copyright (C) 2010-2023 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License, version 3 # (only), as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # This program is dual-licensed. If you wish to learn more about the # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ import io import datetime import decimal import textwrap import pytest from rhodecode.lib import ext_json from rhodecode.translation import _, _pluralize class Timezone(datetime.tzinfo): def __init__(self, hours): self.hours = hours def utcoffset(self, unused_dt): return datetime.timedelta(hours=self.hours) class SerializableObject(object): def __json__(self): return 'foo' def test_dumps_set(): result = ext_json.json.dumps(set((1, 2, 3))) # We cannot infer what the order of result is going to be result = ext_json.json.loads(result) assert isinstance(result, list) assert [1, 2, 3] == sorted(result) def test_dumps_decimal(): assert b'"1.5"' == ext_json.json.dumps(decimal.Decimal('1.5')) def test_dumps_complex(): assert b"[0.0,1.0]" == ext_json.json.dumps(1j) assert b"[1.0,0.0]" == ext_json.json.dumps(1 + 0j) assert b"[1.1,1.2]" == ext_json.json.dumps(1.1 + 1.2j) def test_dumps_object_with_json_method(): assert '"foo"' == ext_json.str_json(SerializableObject()) def test_dumps_object_with_json_attribute(): assert '"foo"' == ext_json.str_json(SerializableObject()) def test_dumps_time(): assert '"03:14:15.926535"' == ext_json.str_json(datetime.time(3, 14, 15, 926535)) def test_dumps_time_no_microseconds(): assert '"03:14:15"' == ext_json.str_json(datetime.time(3, 14, 15)) def test_dumps_time_with_timezone(): with pytest.raises(TypeError) as excinfo: ext_json.json.dumps(datetime.time(3, 14, 15, 926535, Timezone(0))) error_msg = str(excinfo.value) assert 'timezone library is not supported' in error_msg # only for simplejson #assert 'Time-zone aware times are not JSON serializable' in error_msg def test_dumps_date(): assert b'"1969-07-20"' == ext_json.json.dumps(datetime.date(1969, 7, 20)) def test_dumps_datetime(): json_data = ext_json.json.dumps(datetime.datetime(1969, 7, 20, 3, 14, 15, 926535)) assert b'"1969-07-20T03:14:15.926535"' == json_data def test_dumps_datetime_no_microseconds(): json_data = ext_json.json.dumps(datetime.datetime(1969, 7, 20, 3, 14, 15)) assert b'"1969-07-20T03:14:15"' == json_data def test_dumps_datetime_with_utc_timezone(): json_data = ext_json.json.dumps( datetime.datetime(1969, 7, 20, 3, 14, 15, 926535, Timezone(0))) assert b'"1969-07-20T03:14:15.926535+00:00"' == json_data def test_dumps_datetime_with_plus1_timezone(): json_data = ext_json.json.dumps( datetime.datetime(1969, 7, 20, 3, 14, 15, 926535, Timezone(1))) assert b'"1969-07-20T03:14:15.926535+01:00"' == json_data def test_dumps_unserializable_class(): unserializable_obj = object() with pytest.raises(TypeError) as excinfo: ext_json.json.dumps(unserializable_obj) assert 'object' in str(excinfo.value) assert 'is not JSON serializable' in str(excinfo.value) def test_dump_is_like_dumps(): data = { 'decimal': decimal.Decimal('1.5'), 'set': set([1]), # Just one element to guarantee the order 'complex': 1 - 1j, 'datetime': datetime.datetime(1969, 7, 20, 3, 14, 15, 926535), 'time': datetime.time(3, 14, 15, 926535), 'date': datetime.date(1969, 7, 20), } json_buffer = io.StringIO() # StringIO because dump uses simplejson not orjson ext_json.json.dump(data, json_buffer) assert ext_json.sjson.dumps(data) == json_buffer.getvalue() def test_formatted_json(): data = { 'b': {'2': 2, '1': 1}, 'a': {'3': 3, '4': 4}, } expected_data = textwrap.dedent(''' { "a": { "3": 3, "4": 4 }, "b": { "1": 1, "2": 2 } }''').strip() assert expected_data == ext_json.formatted_str_json(data) def test_lazy_translation_string(baseapp): data = {'label': _('hello')} data2 = {'label2': _pluralize('singular', 'plural', 1)} assert b'{"label":"hello"}' == ext_json.json.dumps(data) assert b'{"label2":"singular"}' == ext_json.json.dumps(data2) def test_serialize_dataclass(): @dataclasses.dataclass class ExampleStruct: field_str: str field_int: int struct = ExampleStruct(field_int=1, field_str='hello') raw_struct = b'{"field_str":"hello","field_int":1}' assert raw_struct == ext_json.json.dumps(struct) assert struct == ExampleStruct(**ext_json.json.loads(raw_struct))