def test_conflict_with_custom_ops_update_and_head_with_nested_rules(): r = {'r': {'foo': 'baa', 'spam': 'eg'}, 'p': {'foo': 'baa', 'spam': 'eg'}} h = { 'r': { 'foo': 'bab', 'spam': 'egg' }, 'p': { 'foo': 'bab', 'spam': 'egg' } } u = { 'r': { 'foo': 'bac', 'spam': 'eggs' }, 'p': { 'foo': 'bac', 'spam': 'eggs' } } custom_ops = { 'r': DictMergerOps.FALLBACK_KEEP_UPDATE, 'p': DictMergerOps.FALLBACK_KEEP_HEAD, 'r.foo': DictMergerOps.FALLBACK_KEEP_HEAD, 'p.spam': DictMergerOps.FALLBACK_KEEP_UPDATE } expected = { 'r': { 'foo': 'bab', 'spam': 'eggs' }, 'p': { 'foo': 'bab', 'spam': 'eggs' } } m = SkipListsMerger(r, h, u, DictMergerOps.FALLBACK_KEEP_HEAD, custom_ops=custom_ops) with pytest.raises(MergeError) as excinfo: m.merge() assert m.merged_root == expected assert m.conflicts == excinfo.value.content assert len(m.conflicts) == 4 assert Conflict(ConflictType.SET_FIELD, ('r', 'spam'), 'egg') in m.conflicts assert Conflict(ConflictType.SET_FIELD, ('p', 'spam'), 'egg') in m.conflicts assert Conflict(ConflictType.SET_FIELD, ('r', 'foo'), 'bac') in m.conflicts assert Conflict(ConflictType.SET_FIELD, ('p', 'foo'), 'bac') in m.conflicts
def test_to_json_with_add_back_to_head(): conflict = Conflict('ADD_BACK_TO_HEAD', ['foo', 'bar'], {}) conflict_json = conflict.to_json() assert json.loads(conflict_json) == { '$type': 'ADD_BACK_TO_HEAD', 'op': 'add', 'path': '/foo/bar/-', 'value': {} }
def test_to_json_when_path_has_integers(): conflict = Conflict('REMOVE_FIELD', ['foo', 0, 'bar', 1], None) conflict_json = conflict.to_json() assert json.loads(conflict_json) == { '$type': 'REMOVE_FIELD', 'op': 'remove', 'path': '/foo/0/bar/1', 'value': None }
def test_to_json_with_set_field(): conflict = Conflict('SET_FIELD', ['foo', 'bar'], {}) conflict_json = conflict.to_json() assert json.loads(conflict_json) == { '$type': 'SET_FIELD', 'op': 'replace', 'path': '/foo/bar', 'value': {} }
def test_to_json_with_reorder(): conflict = Conflict('REORDER', ['foo', 'bar'], {}) conflict_json = conflict.to_json() assert json.loads(conflict_json) == { '$type': 'REORDER', 'op': 'replace', 'path': '/foo/bar', 'value': {} }
def test_to_json_with_remove_field(): conflict = Conflict('REMOVE_FIELD', ['foo', 'bar'], None) conflict_json = conflict.to_json() assert json.loads(conflict_json) == { '$type': 'REMOVE_FIELD', 'op': 'remove', 'path': '/foo/bar', 'value': None }
def test_to_json_with_manual_merge(): conflict = Conflict('MANUAL_MERGE', ['foo', 'bar'], {}) conflict_json = conflict.to_json() assert json.loads(conflict_json) == { '$type': 'MANUAL_MERGE', 'op': 'add', 'path': '/foo/bar/-', 'value': {} }
def test_conflict_with_custom_ops_update(): r = {'r': {'foo': 'baa', 'spam': 'eg'}, 'p': {'foo': 'baa', 'spam': 'eg'}} h = { 'r': { 'foo': 'bab', 'spam': 'egg' }, 'p': { 'foo': 'bab', 'spam': 'egg' } } u = { 'r': { 'foo': 'bac', 'spam': 'eggs' }, 'p': { 'foo': 'bac', 'spam': 'eggs' } } # set different strategies compare to the default one custom_ops = { 'r': DictMergerOps.FALLBACK_KEEP_UPDATE, 'p': DictMergerOps.FALLBACK_KEEP_UPDATE } expected = { 'r': { 'foo': 'bac', 'spam': 'eggs' }, 'p': { 'foo': 'bac', 'spam': 'eggs' } } m = SkipListsMerger(r, h, u, DictMergerOps.FALLBACK_KEEP_HEAD, custom_ops=custom_ops) with pytest.raises(MergeError) as excinfo: m.merge() assert m.merged_root == expected assert m.conflicts == excinfo.value.content assert len(m.conflicts) == 4 assert Conflict(ConflictType.SET_FIELD, ('p', 'foo'), 'bab') in m.conflicts assert Conflict(ConflictType.SET_FIELD, ('p', 'foo'), 'bab') in m.conflicts assert Conflict(ConflictType.SET_FIELD, ('p', 'foo'), 'bab') in m.conflicts assert Conflict(ConflictType.SET_FIELD, ('p', 'foo'), 'bab') in m.conflicts
def test_to_json_when_path_has_integers(): conflict = Conflict('REMOVE_FIELD', ('foo', 0, 'bar', 1), None) conflict_json = conflict.to_json() assert json.loads(conflict_json) == [ { '$type': 'REMOVE_FIELD', 'op': 'remove', 'path': '/foo/0/bar/1', 'value': None } ]
def test_to_json_with_remove_field(): conflict = Conflict('REMOVE_FIELD', ('foo', 'bar'), None) conflict_json = conflict.to_json() assert json.loads(conflict_json) == [ { '$type': 'REMOVE_FIELD', 'op': 'remove', 'path': '/foo/bar', 'value': None } ]
def test_to_json_with_add_back_to_head(): conflict = Conflict('ADD_BACK_TO_HEAD', ('foo', 'bar'), {}) conflict_json = conflict.to_json() assert json.loads(conflict_json) == [ { '$type': 'ADD_BACK_TO_HEAD', 'op': 'add', 'path': '/foo/bar/-', 'value': {} } ]
def test_to_json_with_set_field(): conflict = Conflict('SET_FIELD', ('foo', 'bar'), {}) conflict_json = conflict.to_json() assert json.loads(conflict_json) == [ { '$type': 'SET_FIELD', 'op': 'replace', 'path': '/foo/bar', 'value': {} } ]
def test_to_json_with_reorder(): conflict = Conflict('REORDER', ('foo', 'bar'), {}) conflict_json = conflict.to_json() assert json.loads(conflict_json) == [ { '$type': 'REORDER', 'op': 'replace', 'path': '/foo/bar', 'value': {} } ]
def test_base_values_exceptions(): m = SkipListsMerger(1, 3, 2, DictMergerOps.FALLBACK_KEEP_HEAD) with pytest.raises(MergeError): m.merge() assert m.conflicts[0] == Conflict(ConflictType.SET_FIELD, (), 2) assert m.merged_root == 3 m = SkipListsMerger(1, 3, 2, DictMergerOps.FALLBACK_KEEP_UPDATE) with pytest.raises(MergeError): m.merge() assert m.conflicts[0] == Conflict(ConflictType.SET_FIELD, (), 3) assert m.merged_root == 2
def test_simple_remove_conflict(): r = {'foo1': 'bar', 'foo2': 'bar'} h = {'foo1': 'baz', 'foo2': 'baz'} u = {} m = SkipListsMerger(r, h, u, DictMergerOps.FALLBACK_KEEP_HEAD) with pytest.raises(MergeError) as excinfo: m.merge() assert m.merged_root == {'foo1': 'baz', 'foo2': 'baz'} assert m.conflicts == excinfo.value.content assert len(m.conflicts) == 2 assert Conflict(ConflictType.REMOVE_FIELD, ('foo1', ), None) in m.conflicts assert Conflict(ConflictType.REMOVE_FIELD, ('foo2', ), None) in m.conflicts
def _process_add_back_to_head(conflict, merged): """Process ADD_BACK_TO_HEAD conflicts differently than other conflicts. Replace all ADD_BACK_TO_HEAD conflicts to became REMOVE_FIELD conflicts also adds value from conflict back to merged_root REMOVE_FIELD conflict now points to proper element on list. """ conflict_type, conflict_location, conflict_content = conflict if conflict_location[0] == "authors": position, merged["authors"] = _insert_to_list(conflict_content, merged["authors"]) insert_path = ("authors", position) new_conflict = Conflict("REMOVE_FIELD", insert_path, None) return new_conflict, merged else: insert_path, merged = _additem(conflict_content, merged, conflict_location) new_conflict = Conflict("REMOVE_FIELD", insert_path, None) return new_conflict, merged
def test_to_json_with_manual_merge(): body = [None, {'foo1': 'bar1'}, {'foo2': 'bar2'}] conflict = Conflict('MANUAL_MERGE', ('foo', 'bar'), body) conflict_json = conflict.to_json() assert json.loads(conflict_json) == [{ '$type': 'MANUAL_MERGE', 'op': 'add', 'path': '/foo/bar/-', 'value': { 'foo1': 'bar1' } }, { '$type': 'MANUAL_MERGE', 'op': 'add', 'path': '/foo/bar/-', 'value': { 'foo2': 'bar2' } }]
def assert_ordered_conflicts(conflicts, expected_conflicts): # expected conflict is a Conflict class instance expected_conflict = [ json.loads(Conflict(c[0], c[1], c[2]).to_json()) for c in expected_conflicts ] # order the lists to check if they match conflicts = sorted(conflicts, key=lambda c: c['path']) expected_conflict = sorted(expected_conflict, key=lambda c: c['path']) assert conflicts == expected_conflict
def test_custom_fallback(): r = {'r': {'foo': 'baa'}} h = {'r': {'foo': 'bab'}} u = {'r': {'foo': 'bac'}} m = SkipListsMerger(r, h, u, DictMergerOps.FALLBACK_KEEP_HEAD) with pytest.raises(MergeError) as excinfo: m.merge() assert m.merged_root == {'r': {'foo': 'bab'}} assert m.conflicts == excinfo.value.content assert len(m.conflicts) == 1 assert Conflict(ConflictType.SET_FIELD, ('r', 'foo'), 'bac') in m.conflicts
def test_to_json_with_manual_merge(): body = [ None, {'foo1': 'bar1'}, {'foo2': 'bar2'} ] conflict = Conflict('MANUAL_MERGE', ('foo', 'bar'), body) conflict_json = conflict.to_json() assert json.loads(conflict_json) == [ { '$type': 'MANUAL_MERGE', 'op': 'add', 'path': '/foo/bar/-', 'value': {'foo1': 'bar1'} }, { '$type': 'MANUAL_MERGE', 'op': 'add', 'path': '/foo/bar/-', 'value': {'foo2': 'bar2'} } ]
def _process_author_manual_merge_conflict(conflict, merged): """Process author `MANUAL_MERGE` conflict. Conflict object is an tuple containing: (conflict_type, conflict_location, conflict_data) where `conflict_data` is a tuple of: (ROOT, HEAD, UPDATE). """ _, _, (root, head, update) = conflict if head and head not in merged["authors"]: position, merged["authors"] = _insert_to_list(head, merged["authors"]) new_conflict = Conflict("SET_FIELD", ("authors", position), update) return new_conflict, merged, head return None, merged, head
def test_simple_conflicts_keep_head(): r = {} h = {'foo': 'bar'} u = {'foo': 'baz'} m = SkipListsMerger(r, h, u, DictMergerOps.FALLBACK_KEEP_HEAD) with pytest.raises(MergeError) as excinfo: m.merge() assert m.merged_root == {'foo': 'bar'} assert m.conflicts == excinfo.value.content assert len(m.conflicts) == 1 assert Conflict(ConflictType.SET_FIELD, ('foo', ), 'baz') in m.conflicts
def test_merge_list_with_string(): r = 'somerandomvalue' h = [1, 2, 3] u = 'a given string' m = Merger(r, h, u, DictMergerOps.FALLBACK_KEEP_HEAD, UnifierOps.KEEP_ONLY_UPDATE_ENTITIES) with pytest.raises(MergeError) as excinfo: m.merge() assert m.merged_root == [1, 2, 3] assert len(excinfo.value.content) == 1 assert excinfo.value.content[0] == Conflict(ConflictType.SET_FIELD, (), 'a given string')
def test_one_list_delete_touched_in_head_raises_conflict(): r = {'r': {'x': 1, 'l': [1, 2, 3]}} h = {'r': {'x': 1, 'l': [4, 3, 2, 1]}} u = {'r': {'x': 2}} m = SkipListsMerger(r, h, u, DictMergerOps.FALLBACK_KEEP_HEAD) with pytest.raises(MergeError) as excinfo: m.merge() assert m.merged_root == {'r': {'x': 2, 'l': [4, 3, 2, 1]}} assert len(m.skipped_lists) == 0 assert m.conflicts == excinfo.value.content assert len(m.conflicts) == 1 assert Conflict(ConflictType.REMOVE_FIELD, ('r', 'l'), None) in m.conflicts
def update_conflicts_list(conflict, conflict_list): """Updates positions in provided conflict_list for conflict which will be added Args: conflict(Conflict): New conflict conflict_list(list): List of conflicts where positions should be updated """ new_type, new_path, new_content = conflict for idx, processed_conflict in enumerate(conflict_list): path = processed_conflict[1] if path[0] == new_path[0] and len(path) > 1 and len( new_path) > 1 and path[1] >= new_path[1]: path = list(path) path[1] += 1 path = tuple(path) conflict_list[idx] = Conflict(processed_conflict[0], path, processed_conflict[2]) return conflict_list
def test_is_to_delete_true_longer_path(): c = Conflict('SET_FIELD', ('figures', 0, 'key'), 'figure1.png') to_delete = 'figures.key' assert is_to_delete(c, to_delete) is True
def _deserialize_conflict(conflict_type, path, body): if conflict_type == ConflictType.MANUAL_MERGE: body = tuple(body) return Conflict(conflict_type, tuple(path), body)
def test_conflict_to_list(): c = Conflict('SET_FIELD', ('figures', 0, 'key'), 'figure1.png') assert conflict_to_list(c) == ['figures', 'key']
def test_is_to_delete_false(): c = Conflict('SET_FIELD', ('figures', 0, 'key'), 'figure1.png') to_delete = 'authors' assert is_to_delete(c, to_delete) is False
def test_is_to_delete_wrong_path(): c = Conflict('SET_FIELD', ('figures', 0, 'key'), 'figure1.png') to_delete = 'figures.keys' assert is_to_delete(c, to_delete) is False
def test_is_to_delete_manual_merge(): c = Conflict('MANUAL_MERGE', ('figures', 0, 'key'), 'figure1.png') to_delete = 'figures' assert is_to_delete(c, to_delete) is False