diff --git a/build/libs/web-api-commander.jar b/build/libs/web-api-commander.jar index 4be545a..1859976 100644 Binary files a/build/libs/web-api-commander.jar and b/build/libs/web-api-commander.jar differ diff --git a/src/main/java/org/reso/certification/stepdefs/WebAPIServer.java b/src/main/java/org/reso/certification/stepdefs/WebAPIServer.java index c0390ef..cf5c0c8 100644 --- a/src/main/java/org/reso/certification/stepdefs/WebAPIServer.java +++ b/src/main/java/org/reso/certification/stepdefs/WebAPIServer.java @@ -537,7 +537,7 @@ class WebAPIServer implements En { result.set(!fieldValue.get().contentEquals(assertedValue.get())); } - LOG.info("Assert True: " + fieldValue.get() + op + assertedValue.get() + " ==> " + result.get()); + LOG.info("Assert True: " + fieldValue.get() + " " + op + " " + assertedValue.get() + " ==> " + result.get()); assertTrue(result.get()); }); } catch (Exception ex) { @@ -546,25 +546,33 @@ class WebAPIServer implements En { }); /* - * Multi-valued enumerations testing. - * TODO: turn array into JSON array and parse values from there + * Multi-valued enumerations */ And("^Multiple Valued Enumeration Data in \"([^\"]*)\" has \"([^\"]*)\"$", (String parameterFieldName, String parameterAssertedValue) -> { try { String fieldName = Settings.resolveParametersString(parameterFieldName, getTestContainer().getSettings()); AtomicReference fieldValue = new AtomicReference<>(); AtomicReference assertedValue = new AtomicReference<>(); - - AtomicBoolean result = new AtomicBoolean(false); + AtomicBoolean result = new AtomicBoolean(true); assertedValue.set(Settings.resolveParametersString(parameterAssertedValue, getTestContainer().getSettings())); LOG.info("Asserted value is: " + assertedValue.get()); from(getTestContainer().getResponseData()).getList(JSON_VALUE_PATH, ObjectNode.class).forEach(item -> { fieldValue.set(item.get(fieldName).toString()); - result.set(fieldValue.get().contains(assertedValue.get())); - LOG.info("Assert True: " + fieldValue.get() + " has " + assertedValue.get() + " ==> " + result.get()); - assertTrue(result.get()); + if (useCollections) { + if (item.get(fieldName).isArray()) { + result.set(result.get() && TestUtils.testAnyOperator(item, fieldName, assertedValue.get())); + LOG.info("Assert True: " + fieldValue.get() + " contains " + assertedValue.get() + " ==> " + result.get()); + assertTrue(result.get()); + } else { + fail(getDefaultErrorMessage(fieldName, "MUST contain an array of values but found:", item.get(fieldName).toString())); + } + } else { + result.set(fieldValue.get().contains(assertedValue.get())); + LOG.info("Assert True: " + fieldValue.get() + " has " + assertedValue.get() + " ==> " + result.get()); + assertTrue(result.get()); + } }); } catch (Exception ex) { fail(getDefaultErrorMessage(ex)); @@ -585,9 +593,7 @@ class WebAPIServer implements En { from(getTestContainer().getResponseData()).getList(JSON_VALUE_PATH, ObjectNode.class).forEach(item -> { fieldValue.set(item.get(fieldName).toString()); if (item.get(fieldName).isArray()) { - Set values = new LinkedHashSet<>(); - item.get(fieldName).elements().forEachRemaining(element -> values.add(element.asText())); - result.set(result.get() && (values.size() == 0 || values.stream().allMatch(value -> value.contentEquals(assertedValue.get())))); + result.set(result.get() && testAllOperator(item, fieldName, assertedValue.get())); LOG.info("Assert True: " + fieldValue.get() + " equals [] or contains only " + assertedValue.get() + " ==> " + result.get()); assertTrue(result.get()); } else { diff --git a/src/main/java/org/reso/commander/common/TestUtils.java b/src/main/java/org/reso/commander/common/TestUtils.java index 30e64bc..7b61f2e 100644 --- a/src/main/java/org/reso/commander/common/TestUtils.java +++ b/src/main/java/org/reso/commander/common/TestUtils.java @@ -1,6 +1,7 @@ package org.reso.commander.common; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.http.Header; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -257,6 +258,43 @@ public final class TestUtils { return result.get(); } + /** + * Compares each item in the string (JSON) payload with the given field name to the asserted value using op. + * + * @param payload JSON payload to compare + * @param fieldName collection-based fieldName to compare against + * @param op collection-based operator (any or all) + * @param assertedValue asserted value + * @return true if values in the payload match the assertion, false otherwise. + */ + public static boolean compareCollectionPayloadToAssertedValue(String payload, String fieldName, String op, String assertedValue) { + AtomicBoolean result = new AtomicBoolean(true); + final String ANY = "any", ALL = "all"; + + //iterate over the items apply the given collection op + from(payload).getList(JSON_VALUE_PATH, ObjectNode.class).forEach(item -> { + if (op.contentEquals(ANY)) { + result.set(result.get() && testAnyOperator(item, fieldName, assertedValue)); + } else if (op.contentEquals(ALL)) { + result.set(result.get() && testAllOperator(item, fieldName, assertedValue)); + } + }); + return result.get(); + } + + public static boolean testAnyOperator(ObjectNode item, String fieldName, String assertedValue) { + List values = new ArrayList<>(); + item.get(fieldName).elements().forEachRemaining(element -> values.add(element.asText())); + return values.stream().anyMatch(value -> value.contentEquals(assertedValue)); + } + + public static boolean testAllOperator(ObjectNode item, String fieldName, String assertedValue) { + List values = new ArrayList<>(); + item.get(fieldName).elements().forEachRemaining(element -> values.add(element.asText())); + return values.stream().allMatch(value -> value.contentEquals(assertedValue)); + } + + /** * Returns true if each item in the list is true * diff --git a/src/test/java/org/reso/commander/test/features/test-web-api-test-container.feature b/src/test/java/org/reso/commander/test/features/test-web-api-test-container.feature index 90a2cda..e06d6f9 100644 --- a/src/test/java/org/reso/commander/test/features/test-web-api-test-container.feature +++ b/src/test/java/org/reso/commander/test/features/test-web-api-test-container.feature @@ -1728,3 +1728,22 @@ Feature: Web API Container Tests Scenario: Date day test 'le' is true when null data are compared to null value When sample JSON data from "good-property-payload-null.json" are loaded into the test container Then "day" comparisons of Date field "ListingContractDate" "le" null return "true" + + ####################################### + # Lookup Tests + ####################################### + Scenario: Multi-valued collections filtered by all() pass when they contain exactly the given values or the empty list + When sample JSON data from "good-property-payload-all-collection.json" are loaded into the test container + Then collection values in the "AccessibilityFeatures" field contain only "Visitable" or the empty list is "true" + + Scenario: Multi-valued collections filtered by all() fail when they don't contain exactly the given value or the empty list + When sample JSON data from "bad-property-payload-all-collection.json" are loaded into the test container + Then collection values in the "AccessibilityFeatures" field contain only "Visitable" or the empty list is "false" + + Scenario: Multi-valued collections filtered by any() pass when they contain the given value + When sample JSON data from "good-property-payload-any-collection.json" are loaded into the test container + Then collection results in the "AccessibilityFeatures" field contain "Visitable" is "true" + + Scenario: Multi-valued collections filtered by any() fail when they don't contain the given value + When sample JSON data from "bad-property-payload-any-collection.json" are loaded into the test container + Then collection results in the "AccessibilityFeatures" field contain "Visitable" is "false" \ No newline at end of file diff --git a/src/test/java/org/reso/commander/test/stepdefs/TestWebAPITestContainer.java b/src/test/java/org/reso/commander/test/stepdefs/TestWebAPITestContainer.java index b8803c7..0ebf160 100644 --- a/src/test/java/org/reso/commander/test/stepdefs/TestWebAPITestContainer.java +++ b/src/test/java/org/reso/commander/test/stepdefs/TestWebAPITestContainer.java @@ -273,6 +273,37 @@ public class TestWebAPITestContainer implements En { } }); + Then("^collection values only contain \"([^\"]*)\" or the empty list is \"([^\"]*)\"$", (String value, String expectedResult) -> { +// final boolean expected = Boolean.parseBoolean(expectedResult), +// result = TestUtils.compareCollectionPayloadToAssertedValue(getTestContainer().getResponseData(), fieldName, op, expected); +// expected ? assertTrue(result) : false ; + }); + + Then("^collection values in the \"([^\"]*)\" field contain only \"([^\"]*)\" or the empty list is \"([^\"]*)\"$", + (String fieldName, String assertedValue, String expectedResult) -> { + + final boolean expected = Boolean.parseBoolean(expectedResult), + result = TestUtils.compareCollectionPayloadToAssertedValue(getTestContainer().getResponseData(), fieldName, "all", assertedValue); + + if (expected) { + assertTrue(result); + } else { + assertFalse(result); + } + }); + + Then("^collection results in the \"([^\"]*)\" field contain \"([^\"]*)\" is \"([^\"]*)\"$", + (String fieldName, String assertedValue, String expectedResult) -> { + + final boolean expected = Boolean.parseBoolean(expectedResult), + result = TestUtils.compareCollectionPayloadToAssertedValue(getTestContainer().getResponseData(), fieldName, "any", assertedValue); + + if (expected) { + assertTrue(result); + } else { + assertFalse(result); + } + }); } /** diff --git a/src/test/resources/bad-property-payload-all-collection.json b/src/test/resources/bad-property-payload-all-collection.json new file mode 100644 index 0000000..6c7805a --- /dev/null +++ b/src/test/resources/bad-property-payload-all-collection.json @@ -0,0 +1,25 @@ +{ + "@odata.context": "https://test.reso.org/Property?$filter=ListingKey eq 'abc123'", + "value": [ + { + "ListingKey": "abc123", + "BedroomsTotal": 5, + "ListPrice": 100000.00, + "StreetName": "Main", + "ModificationTimestamp": "2020-04-02T02:02:02.02Z", + "ListingContractDate": "2020-04-02", + "StandardStatus": "ActiveUnderContract", + "AccessibilityFeatures": ["Visitable", "AccessibleWithRamp"] + }, + { + "ListingKey": "abc123", + "BedroomsTotal": 5, + "ListPrice": 100000.00, + "StreetName": "Main", + "ModificationTimestamp": "2020-04-02T02:02:02.02Z", + "ListingContractDate": "2020-04-02", + "StandardStatus": "ActiveUnderContract", + "AccessibilityFeatures": ["AccessibleWithRamp"] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/bad-property-payload-any-collection.json b/src/test/resources/bad-property-payload-any-collection.json new file mode 100644 index 0000000..06a17ed --- /dev/null +++ b/src/test/resources/bad-property-payload-any-collection.json @@ -0,0 +1,25 @@ +{ + "@odata.context": "https://test.reso.org/Property?$filter=ListingKey eq 'abc123'", + "value": [ + { + "ListingKey": "abc123", + "BedroomsTotal": 5, + "ListPrice": 100000.00, + "StreetName": "Main", + "ModificationTimestamp": "2020-04-02T02:02:02.02Z", + "ListingContractDate": "2020-04-02", + "StandardStatus": "ActiveUnderContract", + "AccessibilityFeatures": ["AccessibleWithRamp"] + }, + { + "ListingKey": "abc123", + "BedroomsTotal": 5, + "ListPrice": 100000.00, + "StreetName": "Main", + "ModificationTimestamp": "2020-04-02T02:02:02.02Z", + "ListingContractDate": "2020-04-02", + "StandardStatus": "ActiveUnderContract", + "AccessibilityFeatures": [] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/good-property-payload-all-collection.json b/src/test/resources/good-property-payload-all-collection.json new file mode 100644 index 0000000..018ef1c --- /dev/null +++ b/src/test/resources/good-property-payload-all-collection.json @@ -0,0 +1,25 @@ +{ + "@odata.context": "https://test.reso.org/Property?$filter=ListingKey eq 'abc123'", + "value": [ + { + "ListingKey": "abc123", + "BedroomsTotal": 5, + "ListPrice": 100000.00, + "StreetName": "Main", + "ModificationTimestamp": "2020-04-02T02:02:02.02Z", + "ListingContractDate": "2020-04-02", + "StandardStatus": "ActiveUnderContract", + "AccessibilityFeatures": ["Visitable"] + }, + { + "ListingKey": "abc123", + "BedroomsTotal": 5, + "ListPrice": 100000.00, + "StreetName": "Main", + "ModificationTimestamp": "2020-04-02T02:02:02.02Z", + "ListingContractDate": "2020-04-02", + "StandardStatus": "ActiveUnderContract", + "AccessibilityFeatures": [] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/good-property-payload-any-collection.json b/src/test/resources/good-property-payload-any-collection.json new file mode 100644 index 0000000..fc6f68b --- /dev/null +++ b/src/test/resources/good-property-payload-any-collection.json @@ -0,0 +1,25 @@ +{ + "@odata.context": "https://test.reso.org/Property?$filter=ListingKey eq 'abc123'", + "value": [ + { + "ListingKey": "abc123", + "BedroomsTotal": 5, + "ListPrice": 100000.00, + "StreetName": "Main", + "ModificationTimestamp": "2020-04-02T02:02:02.02Z", + "ListingContractDate": "2020-04-02", + "StandardStatus": "ActiveUnderContract", + "AccessibilityFeatures": ["Visitable"] + }, + { + "ListingKey": "abc123", + "BedroomsTotal": 5, + "ListPrice": 100000.00, + "StreetName": "Main", + "ModificationTimestamp": "2020-04-02T02:02:02.02Z", + "ListingContractDate": "2020-04-02", + "StandardStatus": "ActiveUnderContract", + "AccessibilityFeatures": ["Visitable", "AccessibleWithRamp"] + } + ] +} \ No newline at end of file