diff --git a/Dockerfile b/Dockerfile index e0d6f58..cc4ab1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /home/gradle/project ADD . ./ -RUN gradle copyJarToOut +RUN gradle jar RUN ls @@ -13,7 +13,7 @@ FROM alpine:latest RUN apk add --update bash ca-certificates openjdk8-jre-base nss && \ rm -rf /var/cache/apk/* -COPY --from=builder /home/gradle/project/out/web-api-commander.jar ./ +COPY --from=builder /home/gradle/project/build/libs/web-api-commander.jar ./ ENTRYPOINT ["java","-jar","/web-api-commander.jar"] CMD ["--help"] diff --git a/README.md b/README.md index eaf103c..edc7642 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,6 @@ The XML DTD for this schema is as follows: - + - - - - - - ]> @@ -89,7 +82,7 @@ - @@ -100,14 +93,14 @@ - + - - - - + + + + - + @@ -126,11 +119,11 @@ - - - - - + + + + + @@ -184,220 +177,192 @@ - - - - - diff --git a/src/main/java/org/reso/certification/features/web-api-server-1.0.2.feature b/src/main/java/org/reso/certification/features/web-api-server-1.0.2.feature index 898bcbe..4a092dc 100644 --- a/src/main/java/org/reso/certification/features/web-api-server-1.0.2.feature +++ b/src/main/java/org/reso/certification/features/web-api-server-1.0.2.feature @@ -7,47 +7,145 @@ Feature: Web API Server 1.0.2 Certification And Client Settings and Parameters were read from the file And an OData client was successfully created from the given RESOScript - @REQ-WA103-END3 @core @x.y.z @core-support-endorsement - Scenario: REQ-WA103-END3 - CORE - Request and Validate Server Metadata - When a GET request is made to the resolved Url in "REQ-WA103-END3" + @REQ-WA103-END3 @core @x.y.z @core-endorsement + Scenario: Request and Validate Server Metadata + When a GET request is made to the resolved Url in "REQ-WA103-END3.metadata" Then the server responds with a status code of 200 And the response is valid XML And the metadata returned is valid - @REQ-WA103-END2 @core @x.y.z @core-support-endorsement - Scenario: REQ-WA103-END2 - CORE - Data System Endpoint test - When a GET request is made to the resolved Url in "REQ-WA103-END2" + @REQ-WA103-END2 @core @x.y.z @core-endorsement + Scenario: Data System Endpoint test + When a GET request is made to the resolved Url in "REQ-WA103-END2.datasystem" Then the server responds with a status code of 200 And the response is valid JSON + And the response has results And the results match the expected DataSystem JSON schema @REQ-WA103-QR1 @core @2.4.1 @query-functions-endorsement - Scenario: REQ-WA103-QR1 - CORE - Search Parameters: Search by UniqueID - When a GET request is made to the resolved Url in "REQ-WA103-QR1" + Scenario: Search Parameters: Select UniqueID + When a GET request is made to the resolved Url in "REQ-WA103-QR1.select.uniqueId" Then the server responds with a status code of 200 And the response is valid JSON + And the response has singleton results in the "Parameter_UniqueID" field And the provided "Parameter_UniqueIDValue" is returned in the "Parameter_UniqueID" field @REQ-WA103-QR3 @core @2.4.2 @query-functions-endorsement - Scenario: REQ-WA103-QR3 - CORE - Query Support: $select - When a GET request is made to the resolved Url in "REQ-WA103-QR3" + Scenario: Query Support: $select + When a GET request is made to the resolved Url in "REQ-WA103-QR3.select" Then the server responds with a status code of 200 And the response is valid JSON + And the response has results And data are present in fields contained within "Parameter_SelectList" @REQ-WA103-QR4 @core @2.4.2 @client-paging-endorsement - Scenario: REQ-WA103-QR4 - CORE - Query Support: $top - When a GET request is made to the resolved Url in "REQ-WA103-QR4" + Scenario: Query Support: $top + When a GET request is made to the resolved Url in "REQ-WA103-QR4.top" Then the server responds with a status code of 200 And the response is valid JSON - And the results contain at most "Parameter_TopCount" records + And the response has results + And the number of results is less than or equal to "Parameter_TopCount" - @REQ-WA103-QR5 @core @2.4.2 @query-support - Scenario: REQ-WA103-QR5 - CORE - Query Support: $skip - When a GET request is made to the resolved Url in "REQ-WA103-QR5" + @REQ-WA103-QR5 @core @2.4.2 @query-support-endorsement + Scenario: Query Support: $skip + When a GET request is made to the resolved Url in "REQ-WA103-QR5.skip" Then the server responds with a status code of 200 And the response is valid JSON - And a GET request is made to the resolved Url in "REQ-WA103-QR5" with $skip="Parameter_TopCount" + And the response has results + And a GET request is made to the resolved Url in "REQ-WA103-QR5.skip" with $skip="Parameter_TopCount" Then the server responds with a status code of 200 And the response is valid JSON + And the response has results And data in the "Parameter_UniqueId" fields are different in the second request than in the first + + @REQ-WA103-QO1 @REQ-WA103-QO1.select @core @2.4.4 @core-endorsement @OData-4.0 + Scenario: Query Support: $select case-sensitivity for OData 4.0 + When a GET request is made to the resolved Url in "REQ-WA103-QO1.select.case" + Then the server responds with a status code of 400 if the server headers report OData version "4.0" + + @REQ-WA103-QO1 @REQ-WA103-QO1.filter @core @2.4.4 @core-endorsement @OData-4.0 + Scenario: Query Support: $filter case-sensitivity for OData 4.0 + When a GET request is made to the resolved Url in "REQ-WA103-QO1.filter.case" + Then the server responds with a status code of 400 if the server headers report OData version "4.0" + + @REQ-WA103-QO1 @REQ-WA103-QO1.orderby.asc.case @core @2.4.4 @core-endorsement @OData-4.0 + Scenario: Query Support: $orderby asc case-sensitivity for OData 4.0 + When a GET request is made to the resolved Url in "REQ-WA103-QO1.orderby.asc.case" + Then the server responds with a status code of 400 if the server headers report OData version "4.0" + + @REQ-WA103-QO1 @REQ-WA103-QO1.orderby.desc.case @core @2.4.4 @core-endorsement @OData-4.0 + Scenario: Query Support: $orderby desc case-sensitivity for OData 4.0 + When a GET request is made to the resolved Url in "REQ-WA103-QO1.orderby.desc.case" + Then the server responds with a status code of 400 if the server headers report OData version "4.0" + + @REQ-WA103-QO2 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: eq + When a GET request is made to the resolved Url in "REQ-WA103-QO2.filter.int.compare.eq" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterIntegerField" "eq" "Parameter_FilterIntegerValueLow" + + @REQ-WA103-QO3 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: ne + When a GET request is made to the resolved Url in "REQ-WA103-QO3.filter.int.compare.ne" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterIntegerField" "ne" "Parameter_FilterIntegerValueLow" + + @REQ-WA103-QO4 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: gt + When a GET request is made to the resolved Url in "REQ-WA103-QO4.filter.int.compare.gt" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterIntegerField" "gt" "Parameter_FilterIntegerValueLow" + + @REQ-WA103-QO5 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: ge + When a GET request is made to the resolved Url in "REQ-WA103-QO5.filter.int.compare.ge" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterIntegerField" "ge" "Parameter_FilterIntegerValueLow" + + @REQ-WA103-QO6 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: lt + When a GET request is made to the resolved Url in "REQ-WA103-QO6.filter.int.compare.lt" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterIntegerField" "lt" "Parameter_FilterIntegerValueLow" + + @REQ-WA103-QO7 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: le + When a GET request is made to the resolved Url in "REQ-WA103-QO7.filter.int.compare.le" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterIntegerField" "le" "Parameter_FilterIntegerValueLow" + + @REQ-WA103-QO9 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: and + When a GET request is made to the resolved Url in "REQ-WA103-QO9.filter.int.compare.and" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterIntegerField" "gt" "Parameter_FilterIntegerValueLow" "and" "lt" "Parameter_FilterIntegerValueHigh" + + @REQ-WA103-QO10 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: or + When a GET request is made to the resolved Url in "REQ-WA103-QO10.filter.int.compare.or" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterIntegerField" "gt" "Parameter_FilterIntegerValueLow" "or" "lt" "Parameter_FilterIntegerValueHigh" + + @REQ-WA103-QO11 @core @2.4.4 @filterability-endorsement + Scenario: Query Support: $filter - Integer Comparison: not() (operator) + When a GET request is made to the resolved Url in "REQ-WA103-QO11.filter.int.compare.not.operator" + Then the server responds with a status code of 200 + And the response is valid JSON + And the response has results + And Integer data in "Parameter_FilterNotField" "ne" "Parameter_FilterNotValue" diff --git a/src/main/java/org/reso/certification/stepdefs/WebAPIServer_1_0_2.java b/src/main/java/org/reso/certification/stepdefs/WebAPIServer_1_0_2.java index 3ef1eed..be9e182 100644 --- a/src/main/java/org/reso/certification/stepdefs/WebAPIServer_1_0_2.java +++ b/src/main/java/org/reso/certification/stepdefs/WebAPIServer_1_0_2.java @@ -1,13 +1,14 @@ package org.reso.certification.stepdefs; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.cucumber.java8.En; import io.restassured.response.Response; import io.restassured.response.ValidatableResponse; import io.restassured.specification.RequestSpecification; +import org.apache.http.Header; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.olingo.client.api.communication.ODataClientErrorException; import org.apache.olingo.client.api.communication.request.retrieve.ODataRawRequest; import org.apache.olingo.client.api.communication.response.ODataRawResponse; import org.apache.olingo.client.api.edm.xml.XMLMetadata; @@ -23,8 +24,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import static io.restassured.path.json.JsonPath.from; import static org.junit.Assert.*; @@ -40,23 +43,83 @@ public class WebAPIServer_1_0_2 implements En { private String serviceRoot, bearerToken, clientId, clientSecret, authorizationUri, tokenUri, redirectUri, scope; private String pathToRESOScript; + private static final String JSON_VALUE_PATH = "value"; + + //container to hold retrieved metadata for later comparisons + private static AtomicReference xmlMetadata = new AtomicReference<>(); + public WebAPIServer_1_0_2() { //TODO: split into separate test files and parallelize to remove the need for Atomic "globals" AtomicReference commander = new AtomicReference<>(); AtomicReference oDataRawResponse = new AtomicReference<>(); AtomicReference request = new AtomicReference<>(); + AtomicReference responseCode = new AtomicReference<>(); AtomicReference responseData = new AtomicReference<>(); AtomicReference initialResponseData = new AtomicReference<>(); //used if two result sets need to be compared AtomicReference rawRequest = new AtomicReference<>(); - //container to hold retrieved metadata for later comparisons - AtomicReference xmlMetadata = new AtomicReference<>(); + AtomicReference oDataClientErrorException = new AtomicReference<>(); + AtomicReference oDataClientErrorExceptionHandled = new AtomicReference<>(); + + AtomicReference serverODataHeaderVersion = new AtomicReference<>(); + AtomicReference testAppliesToServerODataHeaderVersion = new AtomicReference<>(); + + final String HEADER_ODATA_VERSION = "OData-Version"; + + /* + * Instance Utility Methods - must precede usage + */ + + /* + * Resets the local instance state used during test time. TODO: refactor into collection of AtomicReference + */ + Runnable resetState = () -> { + commander.set(null); + oDataRawResponse.set(null); + request.set(null); + responseCode.set(null); + responseData.set(null); + initialResponseData.set(null); + rawRequest.set(null); + oDataClientErrorException.set(null); + serverODataHeaderVersion.set(null); + oDataClientErrorExceptionHandled.set(false); + testAppliesToServerODataHeaderVersion.set(false); + }; + + + /* + * Executes HTTP GET request and sets the expected local variables. + * Handles exceptions and sets response codes. + */ + Function executeGetRequest = (URI requestUri) -> { + LOG.info("Request URI: " + requestUri); + try { + rawRequest.set(commander.get().getClient().getRetrieveRequestFactory().getRawRequest(requestUri)); + oDataRawResponse.set(rawRequest.get().execute()); + responseData.set(convertInputStreamToString(oDataRawResponse.get().getRawResponse())); + serverODataHeaderVersion.set(oDataRawResponse.get().getHeader(HEADER_ODATA_VERSION).toString()); + LOG.info("Request succeeded..." + responseData.get().getBytes().length + " bytes received."); + } catch (ODataClientErrorException cex) { + LOG.debug("OData Client Error Exception caught. Check subsequent test output for asserted conditions..."); + oDataClientErrorException.set(cex); + serverODataHeaderVersion.set(Utils.getHeaderData(HEADER_ODATA_VERSION, cex.getHeaderInfo())); + responseCode.set(cex.getStatusLine().getStatusCode()); + oDataClientErrorExceptionHandled.set(true); + } + return null; + }; /* * Background */ Given("^a RESOScript file was provided$", () -> { + /* NOTE: this item is the first step in the Background */ + + //Reset ALL local state variables prior to each run. + resetState.run(); + if (pathToRESOScript == null) { pathToRESOScript = System.getProperty("pathToRESOScript"); } @@ -156,7 +219,7 @@ public class WebAPIServer_1_0_2 implements En { AtomicInteger numResults = new AtomicInteger(); //iterate over the items and count the number of fields with data to determine whether there are data present - from(responseData.get()).getList("value", HashMap.class).forEach(item -> { + from(responseData.get()).getList(JSON_VALUE_PATH, HashMap.class).forEach(item -> { if (item != null) { numResults.getAndIncrement(); fieldList.forEach(field -> { @@ -180,7 +243,7 @@ public class WebAPIServer_1_0_2 implements En { * $top=*Parameter_TopCount* */ And("^the results contain at most \"([^\"]*)\" records$", (String parameterTopCount) -> { - List items = from(responseData.get()).getList("value"); + List items = from(responseData.get()).getList(JSON_VALUE_PATH); AtomicInteger numResults = new AtomicInteger(items.size()); int topCount = Integer.parseInt(Utils.resolveValue(parameterTopCount, settings)); @@ -204,19 +267,18 @@ public class WebAPIServer_1_0_2 implements En { //TODO: convert to OData filter factory URI requestUri = Commander.prepareURI(Settings.resolveParameters(settings.getRequests().get(requirementId), settings).getUrl() + "&$skip=" + skipCount); LOG.info("Request URI: " + (requestUri != null ? requestUri.toString() : "")); - rawRequest.set(commander.get().getClient().getRetrieveRequestFactory().getRawRequest(requestUri)); - oDataRawResponse.set(rawRequest.get().execute()); - responseData.set(convertInputStreamToString(oDataRawResponse.get().getRawResponse())); + + executeGetRequest.apply(requestUri); + }); And("^data in the \"([^\"]*)\" fields are different in the second request than in the first$", (String parameterUniqueId) -> { ObjectMapper mapper = new ObjectMapper(); - JsonNode n1 = mapper.readTree(initialResponseData.get()); - JsonNode n2 = mapper.readTree(responseData.get()); + List l1 = from(initialResponseData.get()).getList(JSON_VALUE_PATH); + List l2 = from(responseData.get()).getList(JSON_VALUE_PATH); - assertFalse(n1.equals(n2)); + assertFalse(l1.containsAll(l2)); }); - //================================================================================================================== // Common Methods //================================================================================================================== @@ -226,51 +288,19 @@ public class WebAPIServer_1_0_2 implements En { */ When("^a GET request is made to the resolved Url in \"([^\"]*)\"$", (String requirementId) -> { URI requestUri = Commander.prepareURI(Settings.resolveParameters(settings.getRequests().get(requirementId), settings).getUrl()); - LOG.info("Request URI: " + requestUri); - - rawRequest.set(commander.get().getClient().getRetrieveRequestFactory().getRawRequest(requestUri)); - oDataRawResponse.set(rawRequest.get().execute()); - responseData.set(convertInputStreamToString(oDataRawResponse.get().getRawResponse())); - - LOG.info("Request succeeded..." + responseData.get().getBytes().length + " bytes received."); - }); - - /* - * GET request by requirementId with an additional Uri fragment for the base Uri - */ - When("^a GET request is made to the resolved Url in \"([^\"]*)\" with \"([^\"]*)\"$", (String requirementId, String parameterTopCount) -> { - request.set(Settings.resolveParameters(settings.getRequests().get(requirementId), settings)); - - LOG.info("Request URL: " + request.get().getUrl()); - - rawRequest.set(commander.get().getClient().getRetrieveRequestFactory().getRawRequest(Commander.prepareURI(request.get().getUrl()))); - oDataRawResponse.set(rawRequest.get().execute()); - responseData.set(convertInputStreamToString(oDataRawResponse.get().getRawResponse())); - - LOG.info("Request succeeded..." + responseData.get().getBytes().length + " bytes received."); + executeGetRequest.apply(requestUri); }); /* * Assert response code */ - Then("^the server responds with a status code of (\\d+)$", (Integer code) -> { - int responseCode = oDataRawResponse.get().getStatusCode(); - LOG.info("Response code is: " + responseCode); - assertEquals(code.intValue(), responseCode); - }); + Then("^the server responds with a status code of (\\d+)$", (Integer assertedResponseCode) -> { + responseCode.set(oDataClientErrorException.get() != null + ? oDataClientErrorException.get().getStatusLine().getStatusCode() + : oDataRawResponse.get().getStatusCode()); - /* - * Assert greater than: lValFromItem > rValFromSetting - * - * TODO: add general op expression parameter rather than creating individual comparators - */ - And("^data in \"([^\"]*)\" are greater than \"([^\"]*)\"$", (String lValFromItem, String rValFromSetting) -> { - from(responseData.get()).getList("value", HashMap.class).forEach(item -> { - Integer lVal = new Integer(item.get(Utils.resolveValue(lValFromItem, settings)).toString()), - rVal = new Integer(Utils.resolveValue(rValFromSetting, settings)); - - assertTrue( lVal > rVal ); - }); + LOG.info("Asserted Response Code: " + assertedResponseCode + ", " + "Server Response Code: " + responseCode); + assertEquals(responseCode.get().intValue(), assertedResponseCode.intValue()); }); /* @@ -291,12 +321,183 @@ public class WebAPIServer_1_0_2 implements En { LOG.info("Response is valid JSON!"); }); + + /* + * Assert OData version + */ + And("^the server reports OData version \"([^\"]*)\"$", (String assertODataVersion) -> { + LOG.info("Asserted version: " + assertODataVersion + ", Reported OData Version: " + serverODataHeaderVersion.get()); ; + assertEquals(serverODataHeaderVersion.get(), assertODataVersion); + }); + + /* + * Assert HTTP Response Code given asserted OData version + * + * TODO: make a general Header assertion function + */ + Then("^the server responds with a status code of (\\d+) if the server headers report OData version \"([^\"]*)\"$", + (Integer assertedHttpResponseCode, String assertedODataVersion) -> { + boolean versionsMatch = responseCode.get().intValue() == assertedHttpResponseCode.intValue(), + responseCodesMatch = serverODataHeaderVersion.get().equals(assertedODataVersion); + + if (versionsMatch) { + assertTrue(responseCodesMatch); + } + }); + + /* + * Compares field data (LHS) to a given parameter value (RHS). The operator is passed as a string, + * and is used to select among the supported comparisons. + */ + And("^Integer data in \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String parameterFieldName, String op, String parameterAssertedValue) -> { + LOG.info("Parameter_FieldName: " + parameterFieldName + ", op: " + op + ", Parameter_Value: " + parameterAssertedValue); + String fieldName = Utils.resolveValue(parameterFieldName, settings); + int assertedValue = Integer.parseInt(Utils.resolveValue(parameterAssertedValue, settings)); + + //subsequent value comparisons are and-ed together while iterating over the list of items, so init to true + AtomicBoolean result = new AtomicBoolean(true); + + AtomicReference fieldValue = new AtomicReference<>(); + + //iterate through response data and ensure that with data, the statement fieldName "op" assertValue is true + from(responseData.get()).getList(JSON_VALUE_PATH, HashMap.class).forEach(item -> { + fieldValue.set(Integer.parseInt(item.get(fieldName).toString())); + result.set(result.get() && Utils.compare(fieldValue.get(), op, assertedValue)); + LOG.info("Compare: " + fieldValue.get() + " " + op + " " + assertedValue + " is " + result.get()); + }); + + assertTrue(result.get()); + }); + + /* + * True if response has results, meaning value.length > 0 + */ + And("^the response has results$", () -> { + int count = from(responseData.get()).getList(JSON_VALUE_PATH, HashMap.class).size(); + LOG.info("Results count is: " + count); + assertTrue(count > 0); + }); + + /* + * True if data are present in the response + */ + And("^the response has singleton results in the \"([^\"]*)\" field$", (String parameterFieldName) -> { + String value = Utils.resolveValue(parameterFieldName, settings); + boolean isPresent = from(responseData.get()).get() != null && value != null; + LOG.info("Response value is: " + value); + LOG.info("IsPresent: " + isPresent); + assertTrue(isPresent); + }); + + /* + * True if results count less than or equal to limit + */ + And("^the number of results is less than or equal to \"([^\"]*)\"$", (String limitField) -> { + int count = from(responseData.get()).getList(JSON_VALUE_PATH, HashMap.class).size(), + limit = Integer.parseInt(Utils.resolveValue(limitField, settings)); + LOG.info("Results count is: " + count + ", Limit is: " + limit); + assertTrue(count <= limit); + }); + + /* + * True if data in the lhs expression and rhs expressions pass the AND or OR condition given in andOrOp + */ + And("^Integer data in \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String parameterFieldName, String opLhs, String parameterAssertedLhsValue, String andOrOp, String opRhs, String parameterAssertedRhsValue) -> { + String fieldName = Utils.resolveValue(parameterFieldName, settings); + Integer assertedLhsValue = Integer.parseInt(Utils.resolveValue(parameterAssertedLhsValue, settings)), + assertedRhsValue = Integer.parseInt(Utils.resolveValue(parameterAssertedRhsValue, settings)); + + String op = andOrOp.toLowerCase(); + boolean isAndOp = op.contains(Operators.AND); + + //these should default to true when And and false when Or for the purpose of boolean comparisons + AtomicBoolean lhsResult = new AtomicBoolean(isAndOp); + AtomicBoolean rhsResult = new AtomicBoolean(isAndOp); + AtomicBoolean itemResult = new AtomicBoolean(isAndOp); + + AtomicReference lhsValue = new AtomicReference<>(), + rhsValue = new AtomicReference<>(); + + //iterate through response data and ensure that with data, the statement fieldName "op" assertValue is true + from(responseData.get()).getList(JSON_VALUE_PATH, HashMap.class).forEach(item -> { + lhsValue.set(Integer.parseInt(item.get(fieldName).toString())); + rhsValue.set(Integer.parseInt(item.get(fieldName).toString())); + + LOG.info("Checking LHS"); + lhsResult.set(Utils.compare(lhsValue.get(), opLhs, assertedLhsValue)); + + LOG.info("Checking RHS"); + rhsResult.set(Utils.compare(rhsValue.get(), opRhs, assertedRhsValue)); + + if (op.contentEquals(Operators.AND)) { + itemResult.set(lhsResult.get() && rhsResult.get()); + LOG.info("assertTrue: " + lhsResult.get() + " AND " + rhsResult.get() + " ==> " + itemResult.get()); + assertTrue(itemResult.get()); + } else if (op.contentEquals(Operators.OR)) { + itemResult.set(lhsResult.get() || rhsResult.get()); + LOG.info("assertTrue: " + lhsResult.get() + " OR " + rhsResult.get() + " ==> " + itemResult.get()); + assertTrue(itemResult.get()); + } + }); + }); + + } + + /** + * Contains the list of supported operators for use in query expressions. + */ + private static class Operators { + private static final String + AND = "and", + OR = "or", + NE = "ne", + EQ = "eq", + GREATER_THAN = "gt", + GREATER_THAN_OR_EQUAL = "ge", + LESS_THAN = "lt", + LESS_THAN_OR_EQUAL = "le"; } private static class Utils { + + /** + * Returns true if each item in the list is true + * @param lhs left hand value + * @param op a binary operator for use in comparsions + * @param rhs right hand value + * @return true if lhs op rhs produces true, false otherwise + */ + private static boolean compare(Integer lhs, String op, Integer rhs) { + boolean result = false; + + switch (op) { + case Operators.EQ: + result = lhs.equals(rhs); + break; + case Operators.NE: + result = !lhs.equals(rhs); + break; + case Operators.GREATER_THAN: + result = lhs > rhs; + break; + case Operators.GREATER_THAN_OR_EQUAL: + result = lhs >= rhs; + break; + case Operators.LESS_THAN: + result = lhs < rhs; + break; + case Operators.LESS_THAN_OR_EQUAL: + result = lhs <= rhs; + break; + } + + LOG.info("Compare: " + lhs + " " + op + " " + rhs + " ==> " + result); + return result; + } + /** * Tests the given string to see if it's valid JSON - * @param jsonString + * @param jsonString the JSON string to test the validity of * @return true if valid, false otherwise. Throws {@link IOException} */ private static boolean isValidJson(String jsonString) { @@ -334,16 +535,16 @@ public class WebAPIServer_1_0_2 implements En { private static String getResponseData(ODataRawResponse oDataRawResponse) { return convertInputStreamToString(oDataRawResponse.getRawResponse()); } - } - /** - * These are now passed dynamically but we may want to verify the choices. Leave in for now. - */ - private static class REQUESTS { - private static class WEB_API_1_0_2 { - private static final String REQ_WA_103_END_3 = "REQ-WA103-END3"; - private static final String REQ_WA_103_QR_3 = "REQ-WA103-QR3"; - private static final String REQ_WA103_END2 = "REQ-WA103-END2"; + private static String getHeaderData(String key, Header[] headers) { + String data = null; + + for(Header header : headers) { + if (header.getName().toLowerCase().contains(key.toLowerCase())) { + data = header.getValue(); + } + } + return data; } } diff --git a/src/main/java/org/reso/commander/App.java b/src/main/java/org/reso/commander/App.java index 820ee4d..214836b 100644 --- a/src/main/java/org/reso/commander/App.java +++ b/src/main/java/org/reso/commander/App.java @@ -170,12 +170,12 @@ public class App { //TODO: create dynamic JUnit (or similar) test runner LOG.info(SMALL_DIVIDER); - LOG.info("Assertion: #" + (i + 1)); + LOG.info("Request: #" + (i + 1)); LOG.info(SMALL_DIVIDER); - LOG.info("Assertion Name: " + request.getName()); + LOG.info("Request Name: " + request.getName()); //TODO: function-ize the property test - LOG.info("Assertion Description: " + (request.getTestDescription().length() > 0 ? request.getTestDescription() : "Not Specified")); + LOG.info("Description: " + (request.getTestDescription().length() > 0 ? request.getTestDescription() : "Not Specified")); LOG.info("Requirement Id: " + (request.getRequirementId().length() > 0 ? request.getRequirementId() : "Not Specified")); LOG.info("Metallic Level: " + (request.getMetallicLevel().length() > 0 ? request.getMetallicLevel() : "Not Specified")); LOG.info("Capability: " + (request.getCapability().length() > 0 ? request.getCapability() : "Not Specified")); @@ -200,6 +200,8 @@ public class App { if (responseCode == HttpStatus.SC_OK) { STATS.updateRequest(request, Request.Status.SUCCEEDED); + } else { + STATS.updateRequest(request, Request.Status.FAILED); } if (request.getOutputFile().toLowerCase().contains(EDMX_EXTENSION.toLowerCase())) { @@ -210,16 +212,9 @@ public class App { LOG.error("Error: Invalid metadata retrieved. Cannot continue!!"); System.exit(NOT_OK); } - } else if (responseCode != null && request.getAssertResponseCode() != null) { - if (responseCode == Integer.parseInt(request.getAssertResponseCode())) { - LOG.info("Assert Response Code " + request.getAssertResponseCode() + " passed!"); - } else { - LOG.error("Request " + request.getName() + " Failed!"); - STATS.updateRequest(request, Request.Status.FAILED); - } } - } else { + LOG.info("Request " + request.getRequirementId() + " has an empty URL. Skipping..."); STATS.updateRequest(request, Request.Status.SKIPPED); } } catch (Exception ex) { @@ -228,10 +223,6 @@ public class App { LOG.error("Stack trace:"); Arrays.stream(ex.getStackTrace()).forEach(stackTraceElement -> LOG.error(stackTraceElement.toString())); } finally { - if (request != null && STATS.getRequests().size() > 0) { - LOG.info("Request " + STATS.getRequests().get(request).getStatus().toString().toLowerCase() + "!"); - } - if (resolvedUrl != null && resolvedUrl.length() > 0) { LOG.info("Elapsed Time: " + String.format("%.2f", (STATS.getRequests().get(request).getElapsedTimeMillis() / 1000.0)) + "s"); } @@ -443,7 +434,7 @@ public class App { numIncomplete = stats.getRequestCount(Request.Status.STARTED); reportBuilder.append("\n\n" + DIVIDER); - reportBuilder.append("\nAssertion Statistics"); + reportBuilder.append("\nRequest Statistics"); reportBuilder.append("\n" + DIVIDER); reportBuilder.append(generateTotalsReport(stats.totalRequestCount(), numSucceeded, numFailed, numSkipped, numIncomplete)); @@ -462,7 +453,7 @@ public class App { numSkipped = stats.filterByMetallicCertification(metallicKey, stats.filterByStatus(Request.Status.SKIPPED)).size(); numIncomplete = stats.filterByMetallicCertification(metallicKey, stats.filterByStatus(Request.Status.STARTED)).size(); - reportBuilder.append("\n\n").append(metallicKey).append(numSucceeded > 0 && numSucceeded == (requestCount - numSkipped) ? " - REQUESTS SUCCEEDED!" : ""); + reportBuilder.append("\n\n").append(metallicKey).append(numSucceeded > 0 && numSucceeded == (requestCount - numSkipped) ? " - ALL REQUESTS SUCCEEDED!" : ""); reportBuilder.append(generateTotalsReport(requestCount, numSucceeded, numFailed, numSkipped, numIncomplete)); } @@ -477,7 +468,7 @@ public class App { numSkipped = stats.filterByCapability(capabilityKey, stats.filterByStatus(Request.Status.SKIPPED)).size(); numIncomplete = stats.filterByCapability(capabilityKey, stats.filterByStatus(Request.Status.STARTED)).size(); - reportBuilder.append("\n\n").append(capabilityKey).append(numSucceeded > 0 && numSucceeded == (requestCount - numSkipped) ? " - REQUESTS SUCCEEDED!" : ""); + reportBuilder.append("\n\n").append(capabilityKey).append(numSucceeded > 0 && numSucceeded == (requestCount - numSkipped) ? " - ALL REQUESTS SUCCEEDED!" : ""); reportBuilder.append(generateTotalsReport(requestCount, numSucceeded, numFailed, numSkipped, numIncomplete)); } reportBuilder.append("\n" + DIVIDER + "\n"); diff --git a/src/main/java/org/reso/models/Request.java b/src/main/java/org/reso/models/Request.java index 3747b35..44fd6d2 100644 --- a/src/main/java/org/reso/models/Request.java +++ b/src/main/java/org/reso/models/Request.java @@ -24,7 +24,6 @@ public class Request { private String metallicLevel; private String capability; private String webApiReference; - private String assertResponseCode; private Request request; private Status status; @@ -41,7 +40,7 @@ public class Request { * @param url */ public Request(String requirementId, String outputFile, String url, String testDescription, String metallicLevel, - String capability, String webApiReference, String assertResponseCode) { + String capability, String webApiReference) { //TODO: add Builder setRequirementId(requirementId); @@ -51,7 +50,6 @@ public class Request { setMetallicLevel(metallicLevel); setCapability(capability); setWebApiReference(webApiReference); - setAssertResponseCode(assertResponseCode); } @@ -123,8 +121,7 @@ public class Request { String expression = "/OutputScript/" + REQUESTS_KEY + "/node()"; NodeList nodes = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET); Node node; - String name, outputFile, url, testDescription, requirementId, metallicLevel, capability, webApiReference, - assertResponseCode; + String name, outputFile, url, testDescription, requirementId, metallicLevel, capability, webApiReference; Request request; for (int i = 0; i < nodes.getLength(); i++) { @@ -138,9 +135,8 @@ public class Request { metallicLevel = safeGetNamedItem(FIELDS.METALLIC_LEVEL, node); capability = safeGetNamedItem(FIELDS.CAPABILITY, node); webApiReference = safeGetNamedItem(FIELDS.WEB_API_REFERENCE, node); - assertResponseCode = safeGetNamedItem(FIELDS.ASSERT_RESPONSE_CODE, node); - request = new Request(requirementId, outputFile, url, testDescription, metallicLevel, capability, webApiReference, assertResponseCode); + request = new Request(requirementId, outputFile, url, testDescription, metallicLevel, capability, webApiReference); name = safeGetNamedItem(FIELDS.NAME, node); request.setName(name == null ? outputFile : name); @@ -249,14 +245,6 @@ public class Request { this.webApiReference = webApiReference; } - public String getAssertResponseCode() { - return assertResponseCode; - } - - public void setAssertResponseCode(String assertResponseCode) { - this.assertResponseCode = assertResponseCode; - } - private static final class FIELDS { static final String NAME = "Name"; static final String OUTPUT_FILE = "OutputFile"; @@ -266,6 +254,5 @@ public class Request { static final String METALLIC_LEVEL = "MetallicLevel"; static final String CAPABILITY = "Capability"; static final String WEB_API_REFERENCE = "WebAPIReference"; - static final String ASSERT_RESPONSE_CODE = "AssertResponseCode"; } } diff --git a/src/main/java/org/reso/models/Settings.java b/src/main/java/org/reso/models/Settings.java index a0361be..bdc1fce 100644 --- a/src/main/java/org/reso/models/Settings.java +++ b/src/main/java/org/reso/models/Settings.java @@ -59,7 +59,7 @@ public class Settings { public static Request resolveParameters(Request request, Settings settings) { //calls to resolve nested parameters return new Request(request.getRequirementId(), request.getOutputFile(), resolveParametersString(request.getUrl(), settings), request.getTestDescription(), - request.getMetallicLevel(), request.getCapability(), request.getWebApiReference(), request.getAssertResponseCode()); + request.getMetallicLevel(), request.getCapability(), request.getWebApiReference()); } /** diff --git a/src/test/java/org/reso/commander/AppTest.java b/src/test/java/org/reso/commander/AppTest.java index 450de80..086d567 100644 --- a/src/test/java/org/reso/commander/AppTest.java +++ b/src/test/java/org/reso/commander/AppTest.java @@ -6,8 +6,7 @@ package org.reso.commander; import org.junit.Test; public class AppTest { - @Test public void testAppHasAGreeting() { - App classUnderTest = new App(); - //assertNotNull("app should have a greeting", classUnderTest.getGreeting()); + @Test public void testParameterDeserialization() { + //TODO } }