# passerelle - uniform access to multiple data sources and services # Copyright (C) 2018 Entr'ouvert # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # 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 Affero 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 . # passerelle - uniform access to multiple data sources and services # Copyright (C) 2018 Entr'ouvert # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # 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 Affero 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 . import json import jsonschema import pytest from django.utils.translation import gettext_lazy from passerelle.utils.json import FLATTEN_SEPARATOR as SEP from passerelle.utils.json import flatten, flatten_json_schema, unflatten, validate_schema from passerelle.utils.jsonresponse import JSONEncoder def test_unflatten_base(): assert unflatten('') == '' assert unflatten('a') == 'a' assert unflatten([]) == [] assert unflatten([1]) == [1] assert unflatten({}) == {} assert unflatten(0) == 0 assert unflatten(1) == 1 assert unflatten(False) is False assert unflatten(True) is True def test_unflatten_dict(): assert unflatten( { 'a' + SEP + 'b' + SEP + '0': 1, 'a' + SEP + 'c' + SEP + '1': 'a', 'a' + SEP + 'b' + SEP + '1': True, 'a' + SEP + 'c' + SEP + '0': [1], } ) == { 'a': { 'b': [1, True], 'c': [[1], 'a'], } } assert unflatten( { 'a' + SEP + SEP + 'b': 1, } ) == {'a' + SEP + 'b': 1} assert unflatten( { 'a' + SEP + SEP + 'b': 1, 'a' + SEP + SEP + 'c' + SEP + 'd': 1, } ) == {'a' + SEP + 'b': 1, 'a' + SEP + 'c': {'d': 1}} def test_unflatten_array(): assert unflatten( { '0' + SEP + 'b' + SEP + '0': 1, '1' + SEP + 'c' + SEP + '1': 'a', '0' + SEP + 'b' + SEP + '1': True, '1' + SEP + 'c' + SEP + '0': [1], } ) == [{'b': [1, True]}, {'c': [[1], 'a']}] assert unflatten( { '0' + SEP + 'b' + SEP + '0': 1, '1' + SEP + 'c' + SEP + SEP + '0': 1, } ) == [{'b': [1]}, {'c' + SEP + '0': 1}] def test_unflatten_missing_final_index(): with pytest.raises(ValueError) as exc_info: unflatten({'1': 1}) assert 'incomplete' in exc_info.value.args[0] def test_unflatten_missing_intermediate_index(): with pytest.raises(ValueError) as exc_info: unflatten({'a' + SEP + '1' + SEP + 'b': 1}) assert 'incomplete' in exc_info.value.args[0] def test_flatten_array_schema(): schema = { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'a': { 'type': 'string', }, 'b': { 'type': 'integer', }, 'c': { 'type': 'array', 'items': { 'type': 'integer', }, }, }, 'additionalProperties': False, }, } flattened_schema = flatten_json_schema(schema) data = [ {'a': 'a', 'b': 1, 'c': [1, 2, 3]}, {'a': 'a', 'b': 1, 'c': [1, 2, 3]}, {'a': 'a', 'b': 1, 'c': [1, 2, 3]}, ] flattened_data = flatten(data) jsonschema.validate(schema=schema, instance=data) assert flattened_schema == { 'type': 'object', 'description': 'flattened schema *never* use for validation', 'properties': { '0' + SEP + 'a': {'type': 'string'}, '0' + SEP + 'b': {'type': 'integer'}, '0' + SEP + 'c' + SEP + '0': {'type': 'integer'}, '0' + SEP + 'c' + SEP + '1': {'type': 'integer'}, '0' + SEP + 'c' + SEP + '2': {'type': 'integer'}, '1' + SEP + 'a': {'type': 'string'}, '1' + SEP + 'b': {'type': 'integer'}, '1' + SEP + 'c' + SEP + '0': {'type': 'integer'}, '1' + SEP + 'c' + SEP + '1': {'type': 'integer'}, '1' + SEP + 'c' + SEP + '2': {'type': 'integer'}, '2' + SEP + 'a': {'type': 'string'}, '2' + SEP + 'b': {'type': 'integer'}, '2' + SEP + 'c' + SEP + '0': {'type': 'integer'}, '2' + SEP + 'c' + SEP + '1': {'type': 'integer'}, '2' + SEP + 'c' + SEP + '2': {'type': 'integer'}, }, 'additionalProperties': False, } # This should never be done as we cannot really validate all keys # containing array indexes, here it works because array have less than 3 # elements. jsonschema.validate(schema=flattened_schema, instance=flattened_data) assert data == unflatten(flattened_data) def test_flatten_dict_schema(): assert flatten_json_schema( { 'type': 'object', 'properties': { 'a': { 'type': 'string', }, 'b': { 'type': 'integer', }, 'c': { 'type': 'array', 'items': { 'type': 'integer', }, }, }, } ) == { 'type': 'object', 'description': 'flattened schema *never* use for validation', 'properties': { 'a': {'type': 'string'}, 'b': {'type': 'integer'}, 'c' + SEP + '0': {'type': 'integer'}, 'c' + SEP + '1': {'type': 'integer'}, 'c' + SEP + '2': {'type': 'integer'}, }, 'additionalProperties': False, } @pytest.mark.parametrize( 'instance,schema', [ ( { 'foo': 'bar', }, { 'type': 'object', 'title': gettext_lazy('test'), 'properties': { 'foo': {'type': 'string'}, }, }, ), ], ) def test_validate_schema(instance, schema): validate_schema(instance, schema) json.dumps(instance, cls=JSONEncoder)