/* Copyright 2013 Red Hat, Inc. and/or its affiliates. This file is part of lightblue. This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.redhat.lightblue.util; import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.ArrayList; import org.junit.Assert; import org.junit.Test; import com.fasterxml.jackson.databind.JsonNode; public class JsonCompareTest { public static JsonNode json(String s) throws Exception { return JsonUtils.json(s.replaceAll("\'", "\"")); } /** * Compare identical docs, no deltas */ @Test public void testEq() throws Exception { JsonCompare cmp = new JsonCompare(); JsonNode doc1 = json("{'a':1,'b':'x','c':true,'d':[1,2,3],'e':[{'q':1,'w':'x'}]}"); JsonNode doc2 = json("{'b':'x','a':1,'c':true,'d':[1,2,3],'e':[{'q':1,'w':'x'}]}"); JsonCompare.Difference diff = cmp.compareNodes(doc1, doc2); Assert.assertTrue(diff.same()); } /** * Compare docs with one change to a field */ @Test public void testSimpleDiff() throws Exception { JsonCompare cmp = new JsonCompare(); JsonNode doc1 = json("{'a':1,'b':'y','c':true,'d':[1,2,3],'e':[{'q':1,'w':'x'}]}"); JsonNode doc2 = json("{'b':'x','a':1,'c':true,'d':[1,2,3],'e':[{'q':1,'w':'x'}]}"); JsonCompare.Difference diff = cmp.compareNodes(doc1, doc2); Assert.assertFalse(diff.same()); Assert.assertEquals(1, diff.getNumChangedFields()); Assert.assertEquals(1, diff.getDelta().size()); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Modification.class, "b", null)); } /** * Compare docs with one primitive array element modification. Should show * up as add/remove */ @Test public void testSimpleArrayDiff() throws Exception { JsonCompare cmp = new JsonCompare(); JsonNode doc1 = json("{'a':1,'b':'y','c':true,'d':[1,2,3],'e':[{'q':1,'w':'x'}]}"); JsonNode doc2 = json("{'b':'y','a':1,'c':true,'d':[1,2,4],'e':[{'q':1,'w':'x'}]}"); JsonCompare.Difference diff = cmp.compareNodes(doc1, doc2); Assert.assertFalse(diff.same()); Assert.assertEquals(2, diff.getNumChangedFields()); Assert.assertEquals(2, diff.getDelta().size()); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Removal.class, "d.2", null)); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Addition.class, null, "d.2")); } /** * Compare docs with one primitive array element addition/removal. */ @Test public void testSimpleArrayDiff_add() throws Exception { JsonCompare cmp = new JsonCompare(); JsonNode doc1 = json("{'a':1,'b':'y','c':true,'d':[1,2,3],'e':[{'q':1,'w':'x'}]}"); JsonNode doc2 = json("{'b':'y','a':1,'c':true,'d':[1,2,4,3],'e':[{'q':1,'w':'x'}]}"); JsonCompare.Difference diff = cmp.compareNodes(doc1, doc2); Assert.assertFalse(diff.same()); Assert.assertEquals(1, diff.getNumChangedFields()); Assert.assertEquals(2, diff.getDelta().size()); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Addition.class, null, "d.2")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Move.class, "d.2", "d.3")); } /** * Compare docs with one primitive array element addition/removal. */ @Test public void testSimpleArrayDiff_addRemove() throws Exception { JsonCompare cmp = new JsonCompare(); JsonNode doc1 = json("{'a':1,'b':'y','c':true,'d':[1,2,3,4,5,6,7,8,9],'e':[{'q':1,'w':'x'}]}"); JsonNode doc2 = json("{'b':'y','a':1,'c':true,'d':[1,2,4,5,11,12,8,9,14],'e':[{'q':1,'w':'x'}]}"); JsonCompare.Difference diff = cmp.compareNodes(doc1, doc2); Assert.assertFalse(diff.same()); Assert.assertEquals(10, diff.getDelta().size()); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Removal.class, "d.2", null)); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Removal.class, "d.5", null)); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Removal.class, "d.6", null)); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Addition.class, null, "d.4")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Addition.class, null, "d.5")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Addition.class, null, "d.8")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Move.class, "d.3", "d.2")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Move.class, "d.4", "d.3")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Move.class, "d.7", "d.6")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Move.class, "d.8", "d.7")); } /** * Modifications to array of objects */ @Test public void testObjectArrayDiff_addRemove() throws Exception { JsonCompare cmp = new JsonCompare(); JsonNode doc1 = json("{'a':1,'b':'y','c':true,'d':[ {'q':'1','w':'2'}, {'q':'3','w':'4'},{'q':'5','w':'6'}]}"); JsonNode doc2 = json("{'b':'y','a':1,'c':true,'d':[ {'q':'1','w':'22'}, {'q':'3','w':'4'},{'q':'7','w':'8'}]}"); JsonCompare.Difference diff = cmp.compareNodes(doc1, doc2); Assert.assertFalse(diff.same()); Assert.assertEquals(3, diff.getDelta().size()); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Removal.class, "d.2", null)); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Addition.class, null, "d.2")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Modification.class, null, "d.0.w")); } /** * Modifications to array of objects */ @Test public void testObjectArrayDiff_addObj() throws Exception { JsonCompare cmp = new JsonCompare(); JsonNode doc1 = json("{'d':[ {'q':'1','w':'2'}, {'q':'3','w':'4'},{'q':'5','w':'6'}]}"); JsonNode doc2 = json("{'d':[ {'q':'a'}, {'q':'1','w':'22'}, {'q':'3','w':'4'},{'q':'5','w':'6'}]}"); JsonCompare.Difference diff = cmp.compareNodes(doc1, doc2); Assert.assertFalse(diff.same()); Assert.assertEquals(5, diff.getDelta().size()); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Addition.class, null, "d.0")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Modification.class, null, "d.1.w")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Move.class, "d.0", "d.1")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Move.class, "d.1", "d.2")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Move.class, "d.2", "d.3")); } /** * Modifications to array of objects w/id */ @Test public void testObjectArrayDiff_addRemove_wid() throws Exception { JsonCompare cmp = new JsonCompare(); cmp.addArrayIdentity(new Path("d"), new Path("id")); JsonNode doc1 = json("{'a':1,'b':'y','c':true,'d':[ {'id':1,'q':'1','w':'2'}, {'id':2,'q':'3','w':'4'},{'id':3,'q':'5','w':'6'}]}"); JsonNode doc2 = json("{'b':'y','a':1,'c':true,'d':[ {'id':1,'q':'1','w':'22'}, {'id':2,'q':'3','w':'4'},{'id':3,'q':'7','w':'8'}]}"); JsonCompare.Difference diff = cmp.compareNodes(doc1, doc2); System.out.println(diff); Assert.assertFalse(diff.same()); Assert.assertEquals(3, diff.getDelta().size()); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Modification.class, null, "d.0.w")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Modification.class, null, "d.2.q")); Assert.assertTrue(hasDelta(diff.getDelta(), JsonCompare.Modification.class, null, "d.2.w")); } @Test public void testArrayIdentityComparisonSanity() throws Exception { JsonCompare cmp = new JsonCompare(); JsonNode doc1 = json("{'lastUpdatedBy':'test','lastUpdateDate':'20160304T09:44:13.813-0700','personalInformation':{'firstName':'cxKIFfirst','localeCode':'en_US','prefix':'Dr.','timeZone':'America/Halifax','lastNames':'ptZUYlast'},'creationDate':'20160304T09:44:13.813-0700','authentications':[{'principal':'IAECY','uid':100,'principalDisplay':'iaECy','providerName':'Red Hat'}],'isUserSimple':false,'createdBy':'test','legalEntities':[{'emails':[{'uid':'bd594540-d35c-48b5-93ff-d7760ac206f7','address':'iaECy@redhat.com','isPrimary':true,'validation':{'creationDate':'20160304T09:44:13.587-0700','key':'sr4Jt9BDJP','attempts':0},'status':'enabled'}],'uid':'776b2b6f-0dee-479d-ab13-1bd5a542d6f9','legalEntityId':123,'permissions':[{'uid':'d2e5a5c6-7eda-4c09-9598-792069f03a9c','accessLevel':'admin','permissionCode':'portal_manage_subscriptions','startDate':'20160304T09:44:13.587-0700'}],'telephones':[{'uid':'dee2dcda-3647-4721-9386-02bce9e2b8d4','phoneType':'other','isPrimary':true,'rawNumber':'555-555-5555','status':'enabled'},{'uid':'5b24315e-5645-42c2-9f98-ac2665500d18','phoneType':'fax','isPrimary':true,'rawNumber':'666-666-6666','status':'enabled'}],'physicalAddresses':[{'defaultFlag':true,'city':'Raleigh','countryIso2Code':'US','postalCode':'27601','county':'Wake','description':'Default','addressTypes':[{'uid':25000000,'code':'market','status':'enabled'}],'uid':25000000,'street':['100 E Davie St'],'state':'NC','status':'enabled'}],'title':'Tester','startDate':'20160304T09:44:13.587-0700'}],'_id':50000000,'status':'enabled','objectType':'user','authentications#':1,'legalEntities#':1}"); JsonNode doc2 = json("{'_id':50000000,'isUserSimple':false,'creationDate':'20160304T09:44:13.813-0700','createdBy':'test','lastUpdateDate':'20160304T09:44:13.813-0700','lastUpdatedBy':'test','status':'enabled','personalInformation':{'firstName':'cxKIFfirst','lastNames':'ptZUYlast','prefix':'Dr.','timeZone':'America/Halifax','localeCode':'en_US'},'authentications':[{'uid':100,'principal':'IAECY','principalDisplay':'iaECy','providerName':'Red Hat'}],'legalEntities':[{'uid':'776b2b6f-0dee-479d-ab13-1bd5a542d6f9','legalEntityId':123,'title':'Tester','startDate':'20160304T09:44:13.587-0700','physicalAddresses':[{'uid':25000000,'street':['100 E Davie St'],'city':'Raleigh','state':'NC','postalCode':'27601','county':'Wake','addressTypes':[{'uid':25000000,'code':'market','status':'enabled'}],'countryIso2Code':'US','status':'enabled','defaultFlag':true,'description':'Default'}],'telephones':[{'uid':'dee2dcda-3647-4721-9386-02bce9e2b8d4','phoneType':'other','rawNumber':'555-555-5555','status':'enabled','isPrimary':true},{'uid':'5b24315e-5645-42c2-9f98-ac2665500d18','phoneType':'fax','rawNumber':'666-666-6666','status':'enabled','isPrimary':true}],'emails':[{'uid':'bd594540-d35c-48b5-93ff-d7760ac206f7','address':'FQFIB@redhat.com','validation':{'key':'sr4Jt9BDJP','creationDate':'20160304T09:44:13.587-0700','attempts':0},'status':'enabled','isPrimary':true}],'permissions':[{'uid':'d2e5a5c6-7eda-4c09-9598-792069f03a9c','startDate':'20160304T09:44:13.587-0700','permissionCode':'portal_manage_subscriptions','accessLevel':'admin'}]}],'authentications#':1,'legalEntities#':1,'objectType':'user'}"); JsonCompare.Difference<JsonNode> diff = cmp.compareNodes(doc1, doc2); System.out.println(diff); // There should be only one diff, email is changed Assert.assertEquals(1, diff.getDelta().size()); Assert.assertEquals("legalEntities.0.emails.0.address", diff.getDelta().get(0).getField().toString()); Map<Path, List<Path>> idMap = new HashMap(); List<Path> l = new ArrayList<>(); l.add(new Path("uid")); idMap.put(new Path("authentications"), l); l = new ArrayList<>(); l.add(new Path("uid")); idMap.put(new Path("legalEntities"), l); l = new ArrayList<>(); l.add(new Path("uid")); idMap.put(new Path("legalEntities.*.physicalAddresses.*.addressTypes"), l); l = new ArrayList<>(); l.add(new Path("uid")); idMap.put(new Path("legalEntities.*.physicalAddresses"), l); l = new ArrayList<>(); l.add(new Path("uid")); idMap.put(new Path("legalEntities.*.emails"), l); l = new ArrayList<>(); l.add(new Path("uid")); idMap.put(new Path("legalEntities.*.permissions"), l); for (Map.Entry<Path, List<Path>> entry : idMap.entrySet()) { cmp.addArrayIdentity(entry.getKey(), entry.getValue().toArray(new Path[entry.getValue().size()])); } diff = cmp.compareNodes(doc1, doc2); System.out.println(diff); // There should be only one diff, email is changed Assert.assertEquals(1, diff.getDelta().size()); Assert.assertEquals("legalEntities.0.emails.0.address", diff.getDelta().get(0).getField().toString()); } /** * Returns if the given type delta exists, with given fields. Any field can * be null */ private static boolean hasDelta(List<JsonCompare.Delta> delta, Class deltaType, String field1, String field2) { return hasDelta(delta, deltaType, field1 == null ? null : new Path(field1), field2 == null ? null : new Path(field2)); } private static boolean hasDelta(List<JsonCompare.Delta> delta, Class deltaType, Path field1, Path field2) { boolean exists = false; for (JsonCompare.Delta d : delta) { if (d.getClass().isAssignableFrom(deltaType)) { boolean f1, f2; if (field1 != null) { f1 = field1.equals(d.getField1()); } else { f1 = true; } if (field2 != null) { f2 = field2.equals(d.getField2()); } else { f2 = true; } if (f1 && f2) { exists = true; break; } } } return exists; } }