Commit c2fc6cf8 authored by Carlos Aristu's avatar Carlos Aristu
Browse files

fixes ISSUE-48373: Test matchers for asserting JSON objects

parent 29cd5683
......@@ -88,6 +88,7 @@ import org.openbravo.test.expression.EvaluationTest;
import org.openbravo.test.expression.OBBindingsTest;
import org.openbravo.test.generalsetup.enterprise.organization.ADOrgPersistInfoTestSuite;
import org.openbravo.test.inventoryStatus.InventoryStatusTest;
import org.openbravo.test.matchers.json.JSONMatchersTest;
import org.openbravo.test.materialMgmt.invoiceFromShipment.InvoiceFromShipmentTest;
import org.openbravo.test.materialMgmt.iscompletelyinvoicedshipment.IsCompletelyInvoicedShipment;
import org.openbravo.test.model.ClassLoaderTest;
......@@ -205,6 +206,9 @@ import org.openbravo.userinterface.selectors.test.ExpressionsTest;
OBBindingsTest.class, //
ExpressionsTest.class,
// matchers
JSONMatchersTest.class,
// model
RuntimeModelTest.class, //
OneToManyTest.class, //
......
/*
*************************************************************************
* The contents of this file are subject to the Openbravo Public License
* Version 1.1 (the "License"), being the Mozilla Public License
* Version 1.1 with a permitted attribution clause; you may not use this
* file except in compliance with the License. You may obtain a copy of
* the License at http://www.openbravo.com/legal/license.html
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* The Original Code is Openbravo ERP.
* The Initial Developer of the Original Code is Openbravo SLU
* All portions are Copyright (C) 2022 Openbravo SLU
* All Rights Reserved.
* Contributor(s): ______________________________________.
************************************************************************
*/
package org.openbravo.test.matchers.json;
import java.util.List;
import java.util.stream.Collectors;
import org.codehaus.jettison.json.JSONArray;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
/**
* A matcher to check whether a JSONArray contains all the objects of a list
*/
class HasItems extends TypeSafeMatcher<JSONArray> {
private List<Object> items;
HasItems(List<Object> items) {
this.items = items;
}
@Override
protected boolean matchesSafely(JSONArray item) {
return JSONComparator.arrayContains(item, items);
}
@Override
public void describeTo(Description description) {
String itemsDesc = items.stream().map(Object::toString).collect(Collectors.joining(","));
description.appendText("has items <[").appendText(itemsDesc).appendText("]>");
}
@Override
public void describeMismatchSafely(JSONArray item, Description description) {
description.appendText("missing items in ").appendValue(item);
}
}
/*
*************************************************************************
* The contents of this file are subject to the Openbravo Public License
* Version 1.1 (the "License"), being the Mozilla Public License
* Version 1.1 with a permitted attribution clause; you may not use this
* file except in compliance with the License. You may obtain a copy of
* the License at http://www.openbravo.com/legal/license.html
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* The Original Code is Openbravo ERP.
* The Initial Developer of the Original Code is Openbravo SLU
* All portions are Copyright (C) 2022 Openbravo SLU
* All Rights Reserved.
* Contributor(s): ______________________________________.
************************************************************************
*/
package org.openbravo.test.matchers.json;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.codehaus.jettison.json.JSONArray;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
/**
* A matcher to check whether a JSONArray matches with a given list of matchers
*/
class HasMatchingItems extends TypeSafeMatcher<JSONArray> {
private List<Matcher<?>> matchers;
private List<Matcher<?>> nonMatching;
HasMatchingItems(List<Matcher<?>> matchers) {
this.matchers = matchers;
}
@Override
protected boolean matchesSafely(JSONArray item) {
nonMatching = matchers.stream()
.filter(matcher -> !anyMatch(matcher, item))
.collect(Collectors.toList());
return nonMatching.isEmpty();
}
private boolean anyMatch(Matcher<?> matcher, JSONArray item) {
for (int i = 0; i < item.length(); i++) {
if (matcher.matches(item.opt(i))) {
return true;
}
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("expected items: ");
addToDescription(matchers, description);
}
@Override
public void describeMismatchSafely(JSONArray item, Description description) {
description.appendText("missing items matching: ");
addToDescription(nonMatching, description);
}
private void addToDescription(List<Matcher<?>> m, Description description) {
IntStream.range(0, m.size()).forEach(idx -> {
m.get(idx).describeTo(description);
if (idx < m.size() - 1) {
description.appendText(", ");
}
});
}
}
/*
*************************************************************************
* The contents of this file are subject to the Openbravo Public License
* Version 1.1 (the "License"), being the Mozilla Public License
* Version 1.1 with a permitted attribution clause; you may not use this
* file except in compliance with the License. You may obtain a copy of
* the License at http://www.openbravo.com/legal/license.html
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* The Original Code is Openbravo ERP.
* The Initial Developer of the Original Code is Openbravo SLU
* All portions are Copyright (C) 2022 Openbravo SLU
* All Rights Reserved.
* Contributor(s): ______________________________________.
************************************************************************
*/
package org.openbravo.test.matchers.json;
import org.codehaus.jettison.json.JSONObject;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
/**
* A matcher to check whether two JSONObjects are equal. Two JSONObjects will be considered equal if
* both have exactly the same number of properties with the same value for each of them.
*/
class IsEqualJSONObject extends TypeSafeMatcher<JSONObject> {
private JSONObject expected;
IsEqualJSONObject(JSONObject expected) {
this.expected = expected;
}
@Override
protected boolean matchesSafely(JSONObject item) {
return JSONComparator.objectsAreEquivalent(item, expected);
}
@Override
public void describeTo(Description description) {
description.appendText("equal to ").appendValue(expected);
}
@Override
public void describeMismatchSafely(JSONObject item, Description description) {
description.appendText("was ").appendValue(item);
}
}
/*
*************************************************************************
* The contents of this file are subject to the Openbravo Public License
* Version 1.1 (the "License"), being the Mozilla Public License
* Version 1.1 with a permitted attribution clause; you may not use this
* file except in compliance with the License. You may obtain a copy of
* the License at http://www.openbravo.com/legal/license.html
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* The Original Code is Openbravo ERP.
* The Initial Developer of the Original Code is Openbravo SLU
* All portions are Copyright (C) 2022 Openbravo SLU
* All Rights Reserved.
* Contributor(s): ______________________________________.
************************************************************************
*/
package org.openbravo.test.matchers.json;
import org.codehaus.jettison.json.JSONObject;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
/**
* A matcher to check whether a JSONObjects includes all the properties and with the same value of
* another JSONObject.
*/
class IsMatchingJSONObject extends TypeSafeMatcher<JSONObject> {
private JSONObject subset;
IsMatchingJSONObject(JSONObject subset) {
this.subset = subset;
}
@Override
protected boolean matchesSafely(JSONObject item) {
return JSONComparator.objectsMatch(item, subset);
}
@Override
public void describeTo(Description description) {
description.appendText("matches with ").appendValue(subset);
}
@Override
public void describeMismatchSafely(JSONObject item, Description description) {
description.appendText("was ").appendValue(item);
}
}
/*
*************************************************************************
* The contents of this file are subject to the Openbravo Public License
* Version 1.1 (the "License"), being the Mozilla Public License
* Version 1.1 with a permitted attribution clause; you may not use this
* file except in compliance with the License. You may obtain a copy of
* the License at http://www.openbravo.com/legal/license.html
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* The Original Code is Openbravo ERP.
* The Initial Developer of the Original Code is Openbravo SLU
* All portions are Copyright (C) 2022 Openbravo SLU
* All Rights Reserved.
* Contributor(s): ______________________________________.
************************************************************************
*/
package org.openbravo.test.matchers.json;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.hamcrest.Matcher;
/**
* Contains the base methods for comparing JSONObjects and JSONArrays
*/
class JSONComparator {
private JSONComparator() {
}
static boolean objectsAreEquivalent(Object obj1, Object obj2) {
if (!(obj1 instanceof JSONObject) || !(obj2 instanceof JSONObject)) {
return obj1.equals(obj2);
}
JSONObject json1 = (JSONObject) obj1;
JSONObject json2 = (JSONObject) obj2;
if (json1.length() != json2.length()) {
return false;
}
return allMatch(json1, key -> propertiesAreEqual(json1.opt(key), json2.opt(key)));
}
static boolean objectsMatch(JSONObject json, JSONObject subset) {
if (subset.length() > json.length()) {
return false;
}
JSONObject common = getCommonProperties(json, subset);
if (common.length() == 0) {
// not any common property
return false;
}
return allMatch(common, key -> propertiesAreEqual(common.opt(key), subset.opt(key)));
}
static boolean arrayContains(JSONArray array, List<Object> objects) {
return objects.stream().allMatch(json -> hasEquivalentObject(array, json));
}
private static JSONObject getCommonProperties(JSONObject json1, JSONObject json2) {
@SuppressWarnings("unchecked")
Stream<String> stream = asStream(json1.keys());
return stream.filter(json2::has).collect(Collector.of(JSONObject::new, (result, key) -> {
try {
result.put(key, json1.get(key));
} catch (JSONException ignore) {
// should not fail
}
}, (object1, object2) -> {
throw new UnsupportedOperationException(
"This JSONObject collector does not support combine operation");
}));
}
private static boolean allMatch(JSONObject json, Predicate<String> keyPredicate) {
@SuppressWarnings("unchecked")
Stream<String> stream = asStream(json.keys());
return stream.allMatch(keyPredicate::test);
}
private static boolean propertiesAreEqual(Object prop1, Object prop2) {
if (prop1 == null || prop2 == null) {
return prop1 == null && prop2 == null;
}
if (prop1.getClass() != prop2.getClass()) {
if (prop1 instanceof Number && prop2 instanceof Number) {
return areEqualNumericValues((Number) prop1, (Number) prop2);
} else if (canCompareTimestampValues(prop1, prop2)) {
return areEqualStringValues(prop1, prop2);
} else if (prop2 instanceof Matcher<?>) {
return ((Matcher<?>) prop2).matches(prop1);
}
return false;
}
if (prop1 instanceof JSONObject) {
return objectsAreEquivalent(prop1, prop2);
}
if (prop1 instanceof JSONArray) {
return arraysAreEquivalent((JSONArray) prop1, (JSONArray) prop2);
}
return prop1.equals(prop2);
}
private static boolean areEqualNumericValues(Number number1, Number number2) {
return new BigDecimal(number1.toString()).compareTo(new BigDecimal(number2.toString())) == 0;
}
private static boolean canCompareTimestampValues(Object object1, Object object2) {
if (object1 instanceof Timestamp) {
return object2 instanceof Timestamp || object2 instanceof String;
}
return object1 instanceof String && object2 instanceof Timestamp;
}
private static boolean areEqualStringValues(Object object1, Object object2) {
return object1.toString().equals(object2.toString());
}
private static boolean hasEquivalentObject(JSONArray array, Object object) {
return asStream(array).anyMatch(json -> objectsAreEquivalent(json, object));
}
private static boolean arraysAreEquivalent(JSONArray array1, JSONArray array2) {
if (array1.length() != array2.length()) {
return false;
}
return arrayContains(array1, asStream(array2).collect(Collectors.toList()));
}
private static Stream<String> asStream(Iterator<String> sourceIterator) {
Iterable<String> iterable = () -> sourceIterator;
return StreamSupport.stream(iterable.spliterator(), false);
}
private static Stream<Object> asStream(JSONArray array) {
return IntStream.range(0, array.length()).mapToObj(array::opt);
}
}
/*
*************************************************************************
* The contents of this file are subject to the Openbravo Public License
* Version 1.1 (the "License"), being the Mozilla Public License
* Version 1.1 with a permitted attribution clause; you may not use this
* file except in compliance with the License. You may obtain a copy of
* the License at http://www.openbravo.com/legal/license.html
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* The Original Code is Openbravo ERP.
* The Initial Developer of the Original Code is Openbravo SLU
* All portions are Copyright (C) 2022 Openbravo SLU
* All Rights Reserved.
* Contributor(s): ______________________________________.
************************************************************************
*/
package org.openbravo.test.matchers.json;
import java.util.Arrays;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import org.hamcrest.Matcher;
/**
* Provides different matchers for asserting JSONObjects and JSONArrays
*/
public class JSONMatchers {
private JSONMatchers() {
}
/**
* Creates a matcher for a JSONObject matching when the examined JSONObject has exactly the same
* number of properties with the same values as the expected one. The order of the keys is not
* taken into account. It accepts matcher properties.
*
* For example assertThat(actual, equal(expected));:
*
* - Passes if actual = {p1: "hello", p2: 2} and expected = {p1: "hello", p2: 2} <br>
* - Passes if actual = {p1: "hello", p2: 2} and expected = {p1: "hello", p2: greaterThan(1)} <br>
* - Does not pass if actual = {p1: "hello", p2: 2} and expected = {p1: "hello", p2: 1}
*
* @param expected
* the JSONObject that is expected to be equal to the actual one
*
* @return the equal matcher
*
*/
public static Matcher<JSONObject> equal(JSONObject expected) {
return new IsEqualJSONObject(expected);
}
/**
* Creates a matcher for a JSONObject matching when the examined JSONObject contains the
* properties with the same values of the expected one. The order of the keys is not taken into
* account. It accepts matcher properties.
*
* For example assertThat(actual, matchesObject(expected));:
*
* - Passes if actual = {p1: "hello", p2: 2} and expected = {p1: "hello", p2: 2} <br>
* - Passes if actual = {p1: "hello", p2: 2} and expected = {p1: "hello", p2: greaterThan(1)} <br>
* - Passes if actual = {p1: "hello", p2: 2} and expected = {p1: "hello"} <br>
* - Passes if actual = {p1: "hello", p2: 2} and expected = {p2: greaterThan(1)} <br>
* - Does not pass if actual = {p1: "hello", p2: 2} and expected = {p1: "hello", p2: 1} <br>
* - Does not pass if actual = {p1: "hello", p2: 2} and expected = {p1: "hello", p2: 2, p3: "bye"}
*
* @param expected
* the JSON object that is expected to match all its properties with the actual one
*
* @return the matchesObject matcher
*/
public static Matcher<JSONObject> matchesObject(JSONObject expected) {
return new IsMatchingJSONObject(expected);
}
/**
* Creates a matcher for a JSONArray matching when the examined JSONArray contains all the
* expected objects. The order of the objects is not taken into account.
*
* For example assertThat(actual, hasItems(item1, item2));:
*
* - Passes if actual = [{p1: "hello", p2: 2}, 1], item = {p1: "hello", p2: 2} and item2 = 1 <br>
* - Passes if actual = [{p1: "hello", p2: 2}, 1], item = {p1: "hello", p2: greaterThan(1)} and
* item2 = 1 <br>
* - Does not pass if actual = [{p1: "hello", p2: 2}, 1], item = {p1: "hello", p2: 2} and item2 =
* 3
*
* @param expected
* the objects that are expected to be included in the actual JSONArray
*
* @return the hasItems for objects matcher
*/
public static Matcher<JSONArray> hasItems(Object... expected) {
return new HasItems(Arrays.asList(expected));
}
/**
* Creates a matcher for a JSONArray matching when the examined JSONArray matches with all the
* expected matchers.
*
* For example assertThat(actual, hasItems(matcher1, matcher2));:
*
* - Passes if actual = [{p1: "hello", p2: 2}, 1], matcher1 = matchesObject{p1: "hello"} and
* matcher2 = greaterThan(1) <br>
* - Passes if actual = [{p1: "hello", p2: 2}, 1], matcher1 = matchesObject{p1: startsWith("he")}
* and matcher2 = greaterThan(1) <br>
* - Does not pass if actual = [{p1: "hello", p2: 2}, 1], matcher1 = matchesObject{p1: "hello"}
* and matcher2 = greaterThan(10)
*
* @param expected
* a list of matchers that are expected to match with the actual JSONArray
*
* @return the hasItems for matchers matcher
*/
@SafeVarargs
public static Matcher<JSONArray> hasItems(Matcher<?>... expected) {
return new HasMatchingItems(Arrays.asList(expected));
}
}
/*
*************************************************************************
* The contents of this file are subject to the Openbravo Public License
* Version 1.1 (the "License"), being the Mozilla Public License
* Version 1.1 with a permitted attribution clause; you may not use this
* file except in compliance with the License. You may obtain a copy of
* the License at http://www.openbravo.com/legal/license.html
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* The Original Code is Openbravo ERP.
* The Initial Developer of the Original Code is Openbravo SLU
* All portions are Copyright (C) 2022 Openbravo SLU
* All Rights Reserved.
* Contributor(s): ______________________________________.
************************************************************************
*/
package org.openbravo.test.matchers.json;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.junit.Assert.assertThat;
import static org.openbravo.test.matchers.json.JSONMatchers.equal;
import static org.openbravo.test.matchers.json.JSONMatchers.hasItems;
import static org.openbravo.test.matchers.json.JSONMatchers.matchesObject;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
/**
* Tests the correct behavior of the matchers exposed by {@link JSONMatchers}
*/
public class JSONMatchersTest {
@Test
public void areEqual() {
JSONObject json1 = BaseJSON.base();
JSONObject json2 = BaseJSON.base();