# Copyright 2017 AT&T Intellectual Property. All other rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from deckhand.engine import layering from deckhand import errors from deckhand import factories from deckhand.tests.unit import base as test_base class TestDocumentLayering(test_base.DeckhandTestCase): def _test_layering(self, documents, site_expected=None, region_expected=None, global_expected=None, exception_expected=None): document_layering = layering.DocumentLayering(documents) if all([site_expected, region_expected, global_expected, exception_expected]): raise ValueError( '(site_expected|region_expected|global_expected) and ' '(exception_expected) are mutually exclusive.') if exception_expected: self.assertRaises(exception_expected, document_layering.render) return site_docs = [] region_docs = [] global_docs = [] # The layering policy is not returned as it is immutable. So all docs # should have a metadata.layeringDefinitionn.layer section. rendered_documents = document_layering.render() for doc in rendered_documents: layer = doc['metadata']['layeringDefinition']['layer'] if layer == 'site': site_docs.append(doc) if layer == 'region': region_docs.append(doc) if layer == 'global': global_docs.append(doc) if site_expected: if not isinstance(site_expected, list): site_expected = [site_expected] for idx, expected in enumerate(site_expected): self.assertEqual(expected, site_docs[idx].get('data')) if region_expected: if not isinstance(region_expected, list): region_expected = [region_expected] for idx, expected in enumerate(region_expected): self.assertEqual(expected, region_docs[idx].get('data')) if global_expected: if not isinstance(global_expected, list): global_expected = [global_expected] for idx, expected in enumerate(global_expected): self.assertEqual(expected, global_docs[idx].get('data')) class TestDocumentLayering2Layers(TestDocumentLayering): def test_layering_default_scenario(self): # Default scenario mentioned in design document for 2 layers (region # data is removed). mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(2, [1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'x': 1, 'y': 2}, 'b': 4} self._test_layering(documents, site_expected) def test_layering_method_delete(self): site_expected = [{}, {'c': 9}, {"a": {"x": 1, "y": 2}}] doc_factory = factories.DocumentFactory(2, [1, 1]) for idx, path in enumerate(['.', '.a', '.c']): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}, "c": 9}}, "_SITE_DATA_1_": {"data": {"a": {"x": 7, "z": 3}, "b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "delete", "path": path}]} } documents = doc_factory.gen_test(mapping, site_abstract=False) self._test_layering(documents, site_expected[idx]) def test_layering_method_merge(self): site_expected = [ {'a': {'x': 7, 'y': 2, 'z': 3}, 'b': 4, 'c': 9}, {'a': {'x': 7, 'y': 2, 'z': 3}, 'c': 9}, {'a': {'x': 1, 'y': 2}, 'b': 4, 'c': 9} ] doc_factory = factories.DocumentFactory(2, [1, 1]) for idx, path in enumerate(['.', '.a', '.b']): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}, "c": 9}}, "_SITE_DATA_1_": {"data": {"a": {"x": 7, "z": 3}, "b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": path}]} } documents = doc_factory.gen_test(mapping, site_abstract=False) self._test_layering(documents, site_expected[idx]) def test_layering_method_replace(self): site_expected = [ {'a': {'x': 7, 'z': 3}, 'b': 4}, {'a': {'x': 7, 'z': 3}, 'c': 9}, {'a': {'x': 1, 'y': 2}, 'b': 4, 'c': 9} ] doc_factory = factories.DocumentFactory(2, [1, 1]) for idx, path in enumerate(['.', '.a', '.b']): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}, "c": 9}}, "_SITE_DATA_1_": {"data": {"a": {"x": 7, "z": 3}, "b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "replace", "path": path}]} } documents = doc_factory.gen_test(mapping, site_abstract=False) self._test_layering(documents, site_expected[idx]) class TestDocumentLayering2LayersAbstractConcrete(TestDocumentLayering): """The the 2-layer payload with site/global layers concrete. Both the site and global data should be updated as they're both concrete docs. (2-layer has no region layer.) """ def test_layering_site_and_global_concrete(self): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}, "c": 9}}, "_SITE_DATA_1_": {"data": {"a": {"x": 7, "z": 3}, "b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "delete", "path": '.a'}]} } doc_factory = factories.DocumentFactory(2, [1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False, global_abstract=False) site_expected = {'c': 9} global_expected = {'a': {'x': 1, 'y': 2}, 'c': 9} self._test_layering(documents, site_expected, global_expected=global_expected) def test_layering_site_and_global_abstract(self): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}, "c": 9}}, "_SITE_DATA_1_": {"data": {"a": {"x": 7, "z": 3}, "b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "delete", "path": '.a'}]} } doc_factory = factories.DocumentFactory(2, [1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=True, global_abstract=True) site_expected = {"a": {"x": 7, "z": 3}, "b": 4} global_expected = {'a': {'x': 1, 'y': 2}, 'c': 9} self._test_layering(documents, site_expected, global_expected=global_expected) class TestDocumentLayering2Layers2Sites(TestDocumentLayering): def test_layering_default_scenario(self): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_2_": {"data": {"b": 3}}, "_SITE_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}, {"method": "delete", "path": ".a"}]} } doc_factory = factories.DocumentFactory(2, [1, 2]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = [{'a': {'x': 1, 'y': 2}, 'b': 4}, {'b': 3}] self._test_layering(documents, site_expected) def test_layering_alternate_scenario(self): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_2_": {"data": {"b": 3}}, "_SITE_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}, {"method": "delete", "path": ".a"}, {"method": "merge", "path": ".b"}]} } doc_factory = factories.DocumentFactory(2, [1, 2]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = [{'a': {'x': 1, 'y': 2}, 'b': 4}, {'b': 3}] self._test_layering(documents, site_expected) class TestDocumentLayering2Layers2Sites2Globals(TestDocumentLayering): def test_layering_two_parents_only_one_with_child(self): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_GLOBAL_DATA_2_": {"data": {"a": {"x": 1, "y": 2}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_2_": {"data": {"b": 3}}, "_SITE_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(2, [2, 2]) documents = doc_factory.gen_test( mapping, site_abstract=False, site_parent_selectors=[ {'global': 'global1'}, {'global': 'global2'}]) site_expected = [{'a': {'x': 1, 'y': 2}, 'b': 4}, {'a': {'x': 1, 'y': 2}, 'b': 3}] self._test_layering(documents, site_expected) def test_layering_two_parents_one_child_each_1(self): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_GLOBAL_DATA_2_": {"data": {"a": {"x": 1, "y": 2}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_2_": {"data": {"b": 3}}, "_SITE_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(2, [2, 2]) documents = doc_factory.gen_test( mapping, site_abstract=False, site_parent_selectors=[ {'global': 'global1'}, {'global': 'global2'}]) site_expected = [{'a': {'x': 1, 'y': 2}, 'b': 4}, {'a': {'x': 1, 'y': 2}, 'b': 3}] self._test_layering(documents, site_expected) def test_layering_two_parents_one_child_each_2(self): """Scenario: Initially: p1: {"a": {"x": 1, "y": 2}}, p2: {"b": {"f": -9, "g": 71}} Where: c1 references p1 and c2 references p2 Merge "." (p1 -> c1): {"a": {"x": 1, "y": 2, "b": 4}} Merge "." (p2 -> c2): {"b": {"f": -9, "g": 71}, "c": 3} Delete ".c" (p2 -> c2): {"b": {"f": -9, "g": 71}} """ mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_GLOBAL_DATA_2_": {"data": {"b": {"f": -9, "g": 71}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_2_": {"data": {"c": 3}}, "_SITE_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}, {"method": "delete", "path": ".c"}]} } doc_factory = factories.DocumentFactory(2, [2, 2]) documents = doc_factory.gen_test( mapping, site_abstract=False, site_parent_selectors=[ {'global': 'global1'}, {'global': 'global2'}]) site_expected = [{'a': {'x': 1, 'y': 2}, 'b': 4}, {"b": {"f": -9, "g": 71}}] self._test_layering(documents, site_expected) class TestDocumentLayering3Layers(TestDocumentLayering): def test_layering_default_scenario(self): # Default scenario mentioned in design document for 3 layers. mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_REGION_DATA_1_": {"data": {"a": {"z": 3}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{"method": "replace", "path": ".a"}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'z': 3}, 'b': 4} region_expected = {"a": {"z": 3}} # Region is abstract. self._test_layering(documents, site_expected, region_expected) def test_layering_delete_everything(self): mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 3, "y": 4}, "b": 99}}, "_REGION_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{"path": ".a", "method": "delete"}]}, "_SITE_ACTIONS_1_": {"actions": [ {"method": "delete", "path": ".b"}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {} self._test_layering(documents, site_expected) def test_layering_delete_everything_missing_path(self): """Scenario: Initially: {"a": {"x": 3, "y": 4}, "b": 99} Delete ".": {} Delete ".b": MissingDocumentKey """ mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 3, "y": 4}, "b": 99}}, "_REGION_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{"path": ".", "method": "delete"}]}, "_SITE_ACTIONS_1_": {"actions": [ {"method": "delete", "path": ".b"}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) self._test_layering( documents, exception_expected=errors.MissingDocumentKey) def test_layering_delete_path_a(self): mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}}}, "_REGION_DATA_1_": {"data": {"a": {"z": 3}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.a', 'method': 'delete'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'b': 4} self._test_layering(documents, site_expected) def test_layering_merge_and_replace(self): mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}}}, "_REGION_DATA_1_": {"data": {"a": {"z": 3}}}, "_SITE_DATA_1_": {"data": {'a': {'z': 5}}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.', 'method': 'replace'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'z': 5}} self._test_layering(documents, site_expected) def test_layering_double_merge(self): mapping = { "_GLOBAL_DATA_1_": {"data": {"c": {"e": 55}}}, "_REGION_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}}}, "_SITE_DATA_1_": {"data": {"a": {"z": 5}}}, "_REGION_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_ACTIONS_1_": {"actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'x': 1, 'y': 2, 'z': 5}, 'b': {'v': 3, 'w': 4}, 'c': {'e': 55}} self._test_layering(documents, site_expected) def test_layering_double_merge_2(self): mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}}}, "_REGION_DATA_1_": {"data": {'a': {'e': 55}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.a', 'method': 'merge'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'x': 1, 'y': 2, 'e': 55}, 'b': 4} self._test_layering(documents, site_expected) class TestDocumentLayering3LayersAbstractConcrete(TestDocumentLayering): """The the 3-layer payload with site/region layers concrete. Both the site and region data should be updated as they're both concrete docs. """ def test_layering_site_and_region_concrete(self): """Scenario: Initially: {"a": {"x": 1, "y": 2}} Merge ".": {"a": {"x": 1, "y": 2, "z": 3}, "b": 5, "c": 11} (Region updated.) Delete ".c": {"a": {"x": 1, "y": 2, "z": 3}, "b": 5} (Region updated.) Replace ".b": {"a": {"x": 1, "y": 2, "z": 3}, "b": 4} (Site updated.) """ mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_REGION_DATA_1_": {"data": {"a": {"z": 3}, "b": 5, "c": 11}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}, {"method": "delete", "path": ".c"}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "replace", "path": ".b"}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False, region_abstract=False) site_expected = {"a": {"x": 1, "y": 2, "z": 3}, "b": 4} region_expected = {"a": {"x": 1, "y": 2, "z": 3}, "b": 5} self._test_layering(documents, site_expected, region_expected) def test_layering_site_concrete_and_region_abstract(self): """Scenario: Initially: {"a": {"x": 1, "y": 2}} Merge ".": {"a": {"x": 1, "y": 2, "z": 3}, "b": 5, "c": 11} Delete ".c": {"a": {"x": 1, "y": 2, "z": 3}, "b": 5} Replace ".b": {"a": {"x": 1, "y": 2, "z": 3}, "b": 4} (Site updated.) """ mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_REGION_DATA_1_": {"data": {"a": {"z": 3}, "b": 5, "c": 11}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}, {"method": "delete", "path": ".c"}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "replace", "path": ".b"}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False, region_abstract=True) site_expected = {"a": {"x": 1, "y": 2, "z": 3}, "b": 4} region_expected = {"a": {"z": 3}, "b": 5, "c": 11} self._test_layering(documents, site_expected, region_expected) def test_layering_site_region_and_global_concrete(self): # Both the site and region data should be updated as they're both # concrete docs. mapping = { "_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}}, "_REGION_DATA_1_": {"data": {"a": {"z": 3}, "b": 5}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{"method": "replace", "path": ".a"}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test( mapping, site_abstract=False, region_abstract=False, global_abstract=False) site_expected = {'a': {'z': 3}, 'b': 4} region_expected = {'a': {'z': 3}} # Global data remains unchanged as there's no layer higher than it in # this example. global_expected = {'a': {'x': 1, 'y': 2}} self._test_layering(documents, site_expected, region_expected, global_expected) class TestDocumentLayering3LayersScenario(TestDocumentLayering): def test_layering_multiple_delete(self): """Scenario: Initially: {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}} Delete ".": {} Delete ".": {} Merge ".": {'b': 4} """ mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}}}, "_REGION_DATA_1_": {"data": {"a": {"z": 3}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.', 'method': 'delete'}, {'path': '.', 'method': 'delete'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'b': 4} self._test_layering(documents, site_expected) def test_layering_multiple_replace_1(self): """Scenario: Initially: {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}} Replace ".a": {'a': {'z': 5}, 'b': {'v': 3, 'w': 4}} Replace ".a": {'a': {'z': 5}, 'b': {'v': 3, 'w': 4}} Merge ".": {'a': {'z': 5}, 'b': 4} """ mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}}}, "_REGION_DATA_1_": {"data": {'a': {'z': 5}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.a', 'method': 'replace'}, {'path': '.a', 'method': 'replace'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'z': 5}, 'b': 4} self._test_layering(documents, site_expected) def test_layering_multiple_replace_2(self): """Scenario: Initially: {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}} Replace ".a": {'a': {'z': 5}, 'b': {'v': 3, 'w': 4}} Replace ".b": {'a': {'z': 5}, 'b': [109]} Merge ".": {'a': {'z': 5}, 'b': [32]} """ mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}}}, "_REGION_DATA_1_": {"data": {'a': {'z': 5}, 'b': [109]}}, "_SITE_DATA_1_": {"data": {"b": [32]}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.a', 'method': 'replace'}, {'path': '.b', 'method': 'replace'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'z': 5}, 'b': [32]} self._test_layering(documents, site_expected) def test_layering_multiple_replace_3(self): """Scenario: Initially: {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}, 'c': [123]} Replace ".a": {'a': {'z': 5}, 'b': {'v': 3, 'w': 4}, 'c': [123]} Replace ".b": {'a': {'z': 5}, 'b': -2, 'c': [123]} Merge ".": {'a': {'z': 5}, 'b': 4, 'c': [123]} """ mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}, 'c': [123]}}, "_REGION_DATA_1_": {"data": {'a': {'z': 5}, 'b': -2, 'c': '_'}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.a', 'method': 'replace'}, {'path': '.b', 'method': 'replace'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'z': 5}, 'b': 4, 'c': [123]} self._test_layering(documents, site_expected) def test_layering_multiple_replace_4(self): """Scenario: Initially: {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}, 'c': [123]} Replace ".a": {'a': {'z': 5}, 'b': {'v': 3, 'w': 4}, 'c': [123]} Replace ".b": {'a': {'z': 5}, 'b': -2, 'c': [123]} Replace ".c": {'a': {'z': 5}, 'b': -2, 'c': '_'} Merge ".": {'a': {'z': 5}, 'b': 4, 'c': '_'} """ mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}, 'c': [123]}}, "_REGION_DATA_1_": {"data": {'a': {'z': 5}, 'b': -2, 'c': '_'}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.a', 'method': 'replace'}, {'path': '.b', 'method': 'replace'}, {'path': '.c', 'method': 'replace'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'a': {'z': 5}, 'b': 4, 'c': '_'} self._test_layering(documents, site_expected) def test_layering_multiple_delete_replace(self): """Scenario: Initially: {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}} Delete ".a": {'b': {'v': 3, 'w': 4}} Replace ".b": {'b': {'z': 3}} Delete ".b": {} Merge ".": {'b': 4} """ mapping = { "_GLOBAL_DATA_1_": { "data": {'a': {'x': 1, 'y': 2}, 'b': {'v': 3, 'w': 4}}}, "_REGION_DATA_1_": {"data": {"b": {"z": 3}}}, "_SITE_DATA_1_": {"data": {"b": 4}}, "_REGION_ACTIONS_1_": { "actions": [{'path': '.a', 'method': 'delete'}, {'path': '.b', 'method': 'replace'}, {'path': '.b', 'method': 'delete'}]}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 1, 1]) documents = doc_factory.gen_test(mapping, site_abstract=False) site_expected = {'b': 4} self._test_layering(documents, site_expected) class TestDocumentLayering3Layers2Regions2Sites(TestDocumentLayering): def test_layering_two_abstract_regions_one_child_each(self): """Scenario: Initially: r1: {"c": 3, "d": 4}, r2: {"e": 5, "f": 6} Merge "." (g -> r1): {"a": 1, "b": 2, "c": 3, "d": 4} Merge "." (r1 -> s1): {"a": 1, "b": 2, "c": 3, "d": 4, "g": 7, "h": 8} Merge "." (g -> r2): {"a": 1, "b": 2, "e": 5, "f": 6} Merge "." (r2 -> s2): {"a": 1, "b": 2, "e": 5, "f": 6, "i": 9, "j": 10} """ mapping = { "_GLOBAL_DATA_1_": {"data": {"a": 1, "b": 2}}, "_REGION_DATA_1_": {"data": {"c": 3, "d": 4}}, "_REGION_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_REGION_DATA_2_": {"data": {"e": 5, "f": 6}}, "_REGION_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_1_": {"data": {"g": 7, "h": 8}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_2_": {"data": {"i": 9, "j": 10}}, "_SITE_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 2, 2]) documents = doc_factory.gen_test( mapping, region_abstract=True, site_abstract=False, site_parent_selectors=[ {'region': 'region1'}, {'region': 'region2'}]) site_expected = [{"a": 1, "b": 2, "c": 3, "d": 4, "g": 7, "h": 8}, {"a": 1, "b": 2, "e": 5, "f": 6, "i": 9, "j": 10}] region_expected = [{"c": 3, "d": 4}, {"e": 5, "f": 6}] global_expected = {"a": 1, "b": 2} self._test_layering(documents, site_expected, region_expected, global_expected) def test_layering_two_concrete_regions_one_child_each(self): """Scenario: Initially: r1: {"c": 3, "d": 4}, r2: {"e": 5, "f": 6} Merge "." (g -> r1): {"a": 1, "b": 2, "c": 3, "d": 4} Merge "." (r1 -> s1): {"a": 1, "b": 2, "c": 3, "d": 4, "g": 7, "h": 8} Merge "." (g -> r2): {"a": 1, "b": 2, "e": 5, "f": 6} Merge "." (r2 -> s2): {"a": 1, "b": 2, "e": 5, "f": 6, "i": 9, "j": 10} """ mapping = { "_GLOBAL_DATA_1_": {"data": {"a": 1, "b": 2}}, "_REGION_DATA_1_": {"data": {"c": 3, "d": 4}}, "_REGION_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_REGION_DATA_2_": {"data": {"e": 5, "f": 6}}, "_REGION_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_1_": {"data": {"g": 7, "h": 8}}, "_SITE_ACTIONS_1_": { "actions": [{"method": "merge", "path": "."}]}, "_SITE_DATA_2_": {"data": {"i": 9, "j": 10}}, "_SITE_ACTIONS_2_": { "actions": [{"method": "merge", "path": "."}]} } doc_factory = factories.DocumentFactory(3, [1, 2, 2]) documents = doc_factory.gen_test( mapping, region_abstract=False, site_abstract=False, site_parent_selectors=[ {'region': 'region1'}, {'region': 'region2'}]) site_expected = [{"a": 1, "b": 2, "c": 3, "d": 4, "g": 7, "h": 8}, {"a": 1, "b": 2, "e": 5, "f": 6, "i": 9, "j": 10}] region_expected = [{"a": 1, "b": 2, "c": 3, "d": 4}, {"a": 1, "b": 2, "e": 5, "f": 6}] global_expected = {"a": 1, "b": 2} self._test_layering(documents, site_expected, region_expected, global_expected)