Tightened up metadata checking, including checking for given resource and select list. Added better debugging info. Updated README.

This commit is contained in:
Joshua Darnell 2020-03-08 03:29:00 -07:00
parent 317436996b
commit 0ae0fee43e
8 changed files with 622 additions and 395 deletions

120
README.md
View File

@ -397,7 +397,7 @@ $ ./gradlew testWebAPIServer_1_0_2_Gold -DpathToRESOScript=/path/to/your.resoscr
##### Windows
```
C:\path\to\web-api-commander> gradlew testWebAPIServer_1_0_2 -DpathToRESOScript=C:\path\to\your.resoscript -DshowResponses=true
C:\path\to\web-api-commander> gradlew testWebAPIServer_1_0_2_Gold -DpathToRESOScript=C:\path\to\your.resoscript -DshowResponses=true
```
*Note: the first time you run these tasks, they will take some time as the environment is being configured behind the
@ -416,37 +416,18 @@ $ ./gradlew testWebAPIServer_1_0_2_Platinum -DpathToRESOScript=/path/to/your.res
C:\path\to\web-api-commander> gradlew testWebAPIServer_1_0_2_Platinum -DpathToRESOScript=C:\path\to\your.resoscript -DshowResponses=true
```
#### General Task Wrapper (Advanced)
You may also run the gradle task wrapper using your own tags.
This runs all tests without any additional parameters given. With no tags, the wrapper is called like this:
##### MacOS or Linux
```
$ ./gradlew testWebAPIServer_1_0_2 -DpathToRESOScript=/path/to/your.resoscript -DshowResponses=true
```
##### Windows
```
C:\path\to\web-api-commander> gradlew.bat testWebAPIServer_1_0_2 -DpathToRESOScript=C:\path\to\your.resoscript -DshowResponses=true
```
This will run the entirety of the tests against the Web API 1.0.2 Server provided as `WebAPIURI` in `your.resoscript` file.
You can pass tags to filter on in order to run one or more tests matching the given tag.
#### Advanced feature: Tag Filtering
To filter by tags, a command similar to the following would be used:
You may also filter by tags. These are the items in the Cucumber .feature files prefixed by an `@` symbol. Expressions
may also be used with tags. See the [Cucumber Documentation](https://cucumber.io/docs/cucumber/api/#tags) for more information.
##### MacOS or Linux
```
$ gradle testWebAPIServer_1_0_2 -DpathToRESOScript=/path/to/your.resoscript -Dcucumber.filter.tags="@core"
$ gradle testWebAPIServer_1_0_2_Platinum -DpathToRESOScript=/path/to/your.resoscript -Dcucumber.filter.tags="@core"
```
##### Windows
```
C:\path\to\web-api-commander> gradlew.bat testWebAPIServer_1_0_2 -DpathToRESOScript=C:\path\to\your.resoscript -Dcucumber.filter.tags="@core"
C:\path\to\web-api-commander> gradlew.bat testWebAPIServer_1_0_2_Platinum -DpathToRESOScript=C:\path\to\your.resoscript -Dcucumber.filter.tags="@core"
```
This would run only the tests marked as `@core` in the
@ -470,9 +451,11 @@ Please feel free to suggest additional tags that might be useful.
A sample of the runtime terminal output follows:
```
@REQ-WA103-END3 @core @x.y.z @core-support-endorsement
Scenario: REQ-WA103-END3 - CORE - Request and Validate Server Metadata
```gherkin
> Task :testWebApiServer_1_0_2_Platinum
@REQ-WA103-END3 @core @x.y.z @core-endorsement @metadata
Scenario: Request and Validate Server Metadata
Using RESOScript: /path/to/your.resoscript
Given a RESOScript file was provided
@ -481,21 +464,35 @@ A sample of the runtime terminal output follows:
And Client Settings and Parameters were read from the file
Bearer token loaded... first 4 characters: abcd
Service root is: https://api.server.com/serviceRoot
Service root is: https://api.server.com/
And an OData client was successfully created from the given RESOScript
Request URI: https://api.server.com/serviceRoot/$metadata?$format=application/xml
Request succeeded...185032 bytes received.
When a GET request is made to the resolved Url in "REQ-WA103-END3"
Response code is: 200
Then the server responds with a status code of 200
Response is valid XML!
And the response is valid XML
Fetching XMLMetadata with OData Client from: https://api.server.com/$metadata
When a successful metadata request is made to the service root in "ClientSettings_WebAPIURI"
Metadata is valid!
And the metadata returned is valid
Fetching Edm with OData Client from: https://api.server.com/$metadata
Found EntityContainer for the given resource: 'Property'
And the metadata contains the "Parameter_EndpointResource" resource
Searching metadata for fields in given select list: ListingKey,BedroomsTotal,StreetName,PropertyType,ListingContractDate,ModificationTimestamp,Latitude,Longitude
Found: 'ListingKey'
Found: 'BedroomsTotal'
Found: 'StreetName'
Found: 'PropertyType'
Found: 'ListingContractDate'
Found: 'ModificationTimestamp'
Found: 'Latitude'
Found: 'Longitude'
And resource metadata for "Parameter_EndpointResource" contains the fields in "Parameter_SelectList"
1 Scenarios (1 passed)
7 Steps (7 passed)
0m3.244s
```
This shows configuration parameters, requests, and responses in a lightweight-manner.
@ -538,31 +535,58 @@ $ docker run -it web-api-commander --help
If you have input files you may need to mount your filesystem into the docker container
```
$ docker run -it -v $PWD:/app darnjo/web-api-commander --validateMetadata --inputFile <pathInContainer>
$ docker run -it -v $PWD:/app web-api-commander --validateMetadata --inputFile <pathInContainer>
```
### Automated Web API Testing
You may also run the tests in a Docker container locally by issuing the following command.
Docker must be running on your local machine. This must be done from the root of the Web API Commander
project directory.
You may also run the tests in a Docker container locally by issuing one of the following commands.
Docker must be running on your local machine.
#### MacOS or Linux
#### MacOS or Linux All-In-One Commands
##### Gold
```
$ docker run --rm -u gradle -v "$PWD":/home/gradle/project -v /path/to/your/resoscripts:/home/gradle/project/resoscripts -w /home/gradle/project gradle gradle testWebAPIServer_1_0_2 -DpathToRESOScript=/home/gradle/project/resoscripts/your.resoscript -DshowResponses=true
cd ~; \
rm -rf commander-tmp/; \
mkdir commander-tmp; \
cd commander-tmp; \
git clone https://github.com/RESOStandards/web-api-commander.git; \
cd web-api-commander; \
docker run --rm -u gradle -v "$PWD":/home/gradle/project -v /path/to/your/resoscripts:/home/gradle/project/resoscripts -w /home/gradle/project gradle gradle testWebAPIServer_1_0_2_Gold -DpathToRESOScript=/home/gradle/project/resoscripts/your.resoscript -DshowResponses=true
```
#### Windows
##### Platinum
```
$ docker run --rm -u gradle -v C:\path\to\web-api-commander\:/home/gradle/project -v C:\path\to\your\resoscripts:/home/gradle/project/resoscripts -w /home/gradle/project gradle gradle testWebAPIServer_1_0_2 -DpathToRESOScript=/home/gradle/project/resoscripts/your.resoscript -DshowResponses=true
cd ~; \
rm -rf commander-tmp/; \
mkdir commander-tmp; \
cd commander-tmp; \
git clone https://github.com/RESOStandards/web-api-commander.git; \
cd web-api-commander; \
docker run --rm -u gradle -v "$PWD":/home/gradle/project -v /path/to/your/resoscripts:/home/gradle/project/resoscripts -w /home/gradle/project gradle gradle testWebAPIServer_1_0_2_Platinum -DpathToRESOScript=/home/gradle/project/resoscripts/your.resoscript -DshowResponses=true
```
You may also run the specific task wrappers for Gold or Platinum Web API 1.0.2 Server testing
by replacing `testWebAPIServer_1_0_2` above with `testWebAPIServer_1_0_2_Gold` or `testWebAPIServer_1_0_2_Platinum`, respectively.
Note that this will create a directory in your home directory for the project, and build artifacts and the log will be placed in that directory,
which is also where you will end up after runtime.
#### Windows All-In-One WIP
##### Gold
```
cd C:\;mkdir commander-tmp;cd commander-tmp;git clone https://github.com/RESOStandards/web-api-commander.git;cd web-api-commander; docker run --rm -u gradle -v C:\current\path\web-api-commander:/home/gradle/project -v C:\path\to\your\resoscripts:/home/gradle/project/resoscripts -w /home/gradle/project gradle gradle testWebAPIServer_1_0_2_Gold -DpathToRESOScript=/home/gradle/project/resoscripts/your.resoscript -DshowResponses=true
```
##### Platinum
```
cd C:\;mkdir commander-tmp;cd commander-tmp;git clone https://github.com/RESOStandards/web-api-commander.git;cd web-api-commander;docker run --rm -u gradle -v C:\current\path\web-api-commander:/home/gradle/project -v C:\path\to\your\resoscripts:/home/gradle/project/resoscripts -w /home/gradle/project gradle gradle testWebAPIServer_1_0_2_Platinum -DpathToRESOScript=/home/gradle/project/resoscripts/your.resoscript -DshowResponses=true
```
---
## Logging
In the current version of the Commander, two logs are produced. One is outputted in the terminal at `INFO` level during runtime through `stdout`. A detailed log called `commander.log` will be outputted at runtime and will contain details down to the wire requests.

View File

@ -67,36 +67,15 @@ tasks.withType(JavaCompile) {
options.deprecation = true
}
// task for general Web API Server Testing - this is what should be run in the command line to run everything
task testWebApiServer_1_0_2() {
dependsOn jar
doLast {
javaexec {
main = "io.cucumber.core.cli.Main"
classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
args = [
'--strict',
'--plugin',
'pretty',
'--plugin',
'json:build/web-api-server-1.0.2.json',
'--plugin',
'html:build/web-api-server-1.0.2.html',
'--glue',
'org.reso.certification.stepdefs#WebAPIServer_1_0_2',
'src/main/java/org/reso/certification/features'
]
systemProperties = System.getProperties()
}
}
// task for Web API Server 1.0.2 Gold Testing
task testWebApiServer_1_0_2_Gold() {
// task for Web API Server 1.0.2 Gold Testing
task testWebApiServer_1_0_2_Gold() {
dependsOn jar
doLast {
javaexec {
main = "io.cucumber.core.cli.Main"
classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
systemProperties = System.getProperties()
args = [
'--strict',
'--plugin',
@ -110,19 +89,20 @@ task testWebApiServer_1_0_2() {
'src/main/java/org/reso/certification/features',
'--tags',
'not @platinum'
+ (systemProperties.get('cucumber.filter.tags') != null ? ' and ' + systemProperties.get('cucumber.filter.tags') : '')
]
systemProperties = System.getProperties()
}
}
}
}
// task for Web API Server 1.0.2 Platinum Testing - currently equivalent to all, but generates a platinum-named report.
task testWebApiServer_1_0_2_Platinum() {
// task for Web API Server 1.0.2 Platinum Testing - currently equivalent to all, but generates a platinum-named report.
task testWebApiServer_1_0_2_Platinum() {
dependsOn jar
doLast {
javaexec {
main = "io.cucumber.core.cli.Main"
classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
systemProperties = System.getProperties()
args = [
'--strict',
'--plugin',
@ -135,8 +115,6 @@ task testWebApiServer_1_0_2() {
'org.reso.certification.stepdefs#WebAPIServer_1_0_2',
'src/main/java/org/reso/certification/features'
]
systemProperties = System.getProperties()
}
}
}
}

Binary file not shown.

View File

@ -7,5 +7,4 @@
* in the user manual at https://docs.gradle.org/5.2.1/userguide/multi_project_builds.html
*/
rootProject.name = 'web-api-commander'
rootProject.setName('web-api-commander')

View File

@ -6,12 +6,12 @@ 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-endorsement
@REQ-WA103-END3 @core @x.y.z @core-endorsement @metadata
Scenario: Request and Validate Server Metadata
When a GET request is made to the resolved Url in "REQ-WA103-END3"
Then the server responds with a status code of 200
And the response is valid XML
When a successful metadata request is made to the service root in "ClientSettings_WebAPIURI"
And the metadata returned is valid
And the metadata contains the "Parameter_EndpointResource" resource
And resource metadata for "Parameter_EndpointResource" contains the fields in "Parameter_SelectList"
@REQ-WA103-END2 @core @x.y.z @core-endorsement
Scenario: Data System Endpoint test

View File

@ -14,8 +14,11 @@ 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;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainer;
import org.apache.olingo.commons.api.edm.provider.CsdlProperty;
import org.apache.olingo.commons.api.edm.provider.CsdlSchema;
import org.apache.olingo.commons.core.edm.primitivetype.EdmDate;
import org.apache.olingo.commons.core.edm.primitivetype.EdmDateTimeOffset;
import org.apache.olingo.commons.core.edm.primitivetype.EdmTimeOfDay;
@ -28,7 +31,10 @@ import java.io.*;
import java.net.URI;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.*;
import java.time.Instant;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.Year;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@ -116,6 +122,10 @@ public class WebAPIServer_1_0_2 implements En {
serverODataHeaderVersion.set(Utils.getHeaderData(HEADER_ODATA_VERSION, cex.getHeaderInfo()));
responseCode.set(cex.getStatusLine().getStatusCode());
oDataClientErrorExceptionHandled.set(true);
throw cex;
} catch (Exception ex) {
fail(ex.toString());
throw ex;
}
return null;
};
@ -127,12 +137,15 @@ public class WebAPIServer_1_0_2 implements En {
if (pathToRESOScript == null) {
pathToRESOScript = System.getProperty("pathToRESOScript");
}
assertNotNull("ERROR: pathToRESOScript must be present in command arguments, see README", pathToRESOScript);
LOG.info("Using RESOScript: " + pathToRESOScript);
});
And("^Client Settings and Parameters were read from the file$", () -> {
if (settings == null) {
settings = Settings.loadFromRESOScript(new File(System.getProperty("pathToRESOScript")));
}
assertNotNull("ERROR: Settings could not be loaded.", settings);
LOG.info("RESOScript loaded successfully!");
});
Given("^an OData client was successfully created from the given RESOScript$", () -> {
@ -165,6 +178,10 @@ public class WebAPIServer_1_0_2 implements En {
.bearerToken(bearerToken)
.useEdmEnabledClient(true)
.build());
assertNotNull(commander.get());
assertTrue("ERROR: Commander must either have a valid bearer token or Client Credentials configuration.",
commander.get().isTokenClient() || (commander.get().isOAuthClient() && commander.get().getTokenUri() != null));
});
@ -180,13 +197,17 @@ public class WebAPIServer_1_0_2 implements En {
* REQ-WA103-END3
*/
And("^the metadata returned is valid$", () -> {
//store the metadata for later comparisons
xmlMetadata.set(commander.get().getClient().getDeserializer(ContentType.APPLICATION_XML)
.toMetadata(new ByteArrayInputStream(responseData.get().getBytes())));
if (xmlMetadata.get() == null) {
fail("ERROR: No XML Metadata Exists!");
}
try {
boolean isValid = commander.get().validateMetadata(xmlMetadata.get());
LOG.info("Metadata is " + (isValid ? "valid" : "invalid") + "!");
assertTrue(isValid);
} catch (Exception ex) {
fail(ex.getMessage());
}
});
@ -194,6 +215,7 @@ public class WebAPIServer_1_0_2 implements En {
* REQ-WA103-QR1
*/
And("^the provided \"([^\"]*)\" is returned in \"([^\"]*)\"$", (String parameterUniqueIdValue, String parameterUniqueId) -> {
try {
String expectedValueAsString = Settings.resolveParametersString(parameterUniqueIdValue, settings), resolvedValueAsString = null;
Object resolvedValue = from(responseData.get()).get(Settings.resolveParametersString(parameterUniqueId, settings));
@ -213,6 +235,9 @@ public class WebAPIServer_1_0_2 implements En {
} else {
assertEquals(expectedValueAsString, resolvedValue.toString());
}
} catch (Exception ex) {
fail(ex.getMessage());
}
});
@ -220,11 +245,13 @@ public class WebAPIServer_1_0_2 implements En {
* REQ-WA103-QR3 - $select
*/
And("^data are present in fields contained within \"([^\"]*)\"$", (String parameterSelectList) -> {
try {
AtomicInteger numFieldsWithData = new AtomicInteger();
List<String> fieldList = new ArrayList<>(Arrays.asList(Settings.resolveParametersString(parameterSelectList, settings).split(",")));
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(JSON_VALUE_PATH, HashMap.class).forEach(item -> {
if (item != null) {
@ -246,6 +273,9 @@ public class WebAPIServer_1_0_2 implements En {
LOG.info("Percent Fill: 0% - no fields with data found!");
}
assertTrue(numFieldsWithData.get() > 0);
} catch (Exception ex) {
fail(ex.getMessage());
}
});
@ -254,6 +284,7 @@ public class WebAPIServer_1_0_2 implements En {
* $top=*Parameter_TopCount*
*/
And("^the results contain at most \"([^\"]*)\" records$", (String parameterTopCount) -> {
try {
List<String> items = from(responseData.get()).getList(JSON_VALUE_PATH);
AtomicInteger numResults = new AtomicInteger(items.size());
@ -261,6 +292,9 @@ public class WebAPIServer_1_0_2 implements En {
LOG.info("Number of values returned: " + numResults.get() + ", top count is: " + topCount);
assertTrue(numResults.get() > 0 && numResults.get() <= topCount);
} catch (Exception ex) {
fail(ex.getMessage());
}
});
@ -269,6 +303,7 @@ public class WebAPIServer_1_0_2 implements En {
* $skip=*Parameter_TopCount*
*/
And("^a GET request is made to the resolved Url in \"([^\"]*)\" with \\$skip=\"([^\"]*)\"$", (String requirementId, String parameterTopCount) -> {
try {
int skipCount = Integer.parseInt(Settings.resolveParametersString(parameterTopCount, settings));
LOG.info("Skip count is: " + skipCount);
@ -279,8 +314,12 @@ public class WebAPIServer_1_0_2 implements En {
URI requestUri = Commander.prepareURI(Settings.resolveParameters(settings.getRequests().get(requirementId), settings).getUrl() + "&$skip=" + skipCount);
executeGetRequest.apply(requestUri);
} catch (Exception ex) {
fail(ex.getMessage());
}
});
And("^data in the \"([^\"]*)\" fields are different in the second request than in the first$", (String parameterUniqueId) -> {
try {
List<POJONode> l1 = from(initialResponseData.get()).getJsonObject(JSON_VALUE_PATH);
List<POJONode> l2 = from(responseData.get()).getJsonObject(JSON_VALUE_PATH);
@ -292,6 +331,9 @@ public class WebAPIServer_1_0_2 implements En {
LOG.info("Response Page 2: " + new POJONode(l2));
assertEquals(combinedCount, combined.size());
} catch (Exception ex) {
fail(ex.getMessage());
}
});
//==================================================================================================================
@ -302,34 +344,47 @@ public class WebAPIServer_1_0_2 implements En {
* GET request by requirementId (see generic.resoscript)
*/
When("^a GET request is made to the resolved Url in \"([^\"]*)\"$", (String requirementId) -> {
try {
//reset local state each time a get request is run
resetRequestState.run();
URI requestUri = Commander.prepareURI(Settings.resolveParameters(settings.getRequests().get(requirementId), settings).getUrl());
executeGetRequest.apply(requestUri);
} catch (Exception ex) {
LOG.debug("Exception was thrown in " + this.getClass() + ": " + ex.toString());
}
});
/*
* Assert response code
*/
Then("^the server responds with a status code of (\\d+)$", (Integer assertedResponseCode) -> {
try {
LOG.info("Asserted Response Code: " + assertedResponseCode + ", Server Response Code: " + responseCode);
assertTrue(responseCode.get() > 0 && assertedResponseCode > 0);
assertEquals(assertedResponseCode.intValue(), responseCode.get().intValue());
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* validate XML wrapper
*/
And("^the response is valid XML$", () -> {
try {
assertTrue(Commander.validateXML(responseData.get()));
LOG.info("Response is valid XML!");
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* validate JSON wrapper
*/
And("^the response is valid JSON$", () -> {
try {
assertTrue(Utils.isValidJson(responseData.get()));
LOG.info("Response is valid JSON!");
@ -337,14 +392,9 @@ public class WebAPIServer_1_0_2 implements En {
if (Boolean.parseBoolean(showResponses)) {
LOG.info("Response: " + new ObjectMapper().readTree(responseData.get()).toPrettyString());
}
});
/*
* 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);
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
@ -353,6 +403,7 @@ public class WebAPIServer_1_0_2 implements En {
* 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) -> {
try {
boolean versionsMatch = serverODataHeaderVersion.get().equals(assertedODataVersion),
responseCodesMatch = responseCode.get().intValue() == assertedHttpResponseCode.intValue();
@ -362,6 +413,10 @@ public class WebAPIServer_1_0_2 implements En {
LOG.info("Asserted Response Code: " + assertedHttpResponseCode + ", Response code: " + responseCode.get());
assertTrue(responseCodesMatch);
}
} catch (Exception ex) {
//DEBUG only in this case as we are expecting an error and don't want to throw or print it
LOG.debug(ex.toString());
}
});
/*
@ -369,6 +424,7 @@ public class WebAPIServer_1_0_2 implements En {
* and is used to select among the supported comparisons.
*/
And("^Integer data in \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String parameterFieldName, String op, String parameterAssertedValue) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
int assertedValue = Integer.parseInt(Settings.resolveParametersString(parameterAssertedValue, settings));
@ -386,42 +442,58 @@ public class WebAPIServer_1_0_2 implements En {
});
assertTrue(result.get());
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* True if response has results, meaning value.length > 0
*/
And("^the response has results$", () -> {
try {
int count = from(responseData.get()).getList(JSON_VALUE_PATH, HashMap.class).size();
LOG.info("Results count is: " + count);
assertTrue(count > 0);
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* True if data are present in the response
*/
And("^the response has singleton results in \"([^\"]*)\"", (String parameterFieldName) -> {
try {
String value = Settings.resolveParametersString(parameterFieldName, settings);
boolean isPresent = from(responseData.get()).get() != null;
LOG.info("Response value is: " + value);
LOG.info("IsPresent: " + isPresent);
assertTrue(isPresent);
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* True if results count less than or equal to limit
*/
And("^the number of results is less than or equal to \"([^\"]*)\"$", (String limitField) -> {
try {
int count = from(responseData.get()).getList(JSON_VALUE_PATH, HashMap.class).size(),
limit = Integer.parseInt(Settings.resolveParametersString(limitField, settings));
LOG.info("Results count is: " + count + ", Limit is: " + limit);
assertTrue(count <= limit);
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* 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) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
Integer assertedLhsValue = Integer.parseInt(Settings.resolveParametersString(parameterAssertedLhsValue, settings)),
assertedRhsValue = Integer.parseInt(Settings.resolveParametersString(parameterAssertedRhsValue, settings));
@ -456,12 +528,16 @@ public class WebAPIServer_1_0_2 implements En {
assertTrue(itemResult.get());
}
});
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* Date Comparison glue
*/
And("^Date data in \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String parameterFieldName, String op, String parameterAssertedValue) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
AtomicReference<Date> fieldValue = new AtomicReference<>();
AtomicReference<Date> assertedValue = new AtomicReference<>();
@ -473,16 +549,21 @@ public class WebAPIServer_1_0_2 implements En {
try {
fieldValue.set(Utils.parseDateFromEdmDateTimeOffsetString(item.get(fieldName).toString()));
assertTrue(Utils.compare(fieldValue.get(), op, assertedValue.get()));
} catch (Exception ex){
LOG.error(ex.toString());
} catch (Exception ex) {
fail(ex.toString());
}
});
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* Time comparison glue
*/
And("^TimeOfDay data in \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String parameterFieldName, String op, String parameterAssertedValue) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
AtomicReference<Time> fieldValue = new AtomicReference<>();
AtomicReference<Time> assertedValue = new AtomicReference<>();
@ -494,35 +575,42 @@ public class WebAPIServer_1_0_2 implements En {
try {
fieldValue.set(Utils.parseTimeOfDayFromEdmDateTimeOffsetString(item.get(fieldName).toString()));
assertTrue(Utils.compare(fieldValue.get(), op, assertedValue.get()));
} catch (Exception ex){
LOG.error(ex.toString());
} catch (Exception ex) {
LOG.error(ex.getMessage());
}
});
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* Year comparison glue
*/
/*
* Timestamp comparison glue
*/
And("^DateTimeOffset data in \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String parameterFieldName, String op, String parameterAssertedValue) -> {
try {
Utils.assertDateTimeOffset(parameterFieldName, op, parameterAssertedValue, responseData.get());
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* Timestamp comparison to now()
*/
And("^DateTimeOffset data in \"([^\"]*)\" \"([^\"]*)\" now\\(\\)$", (String parameterFieldName, String op) -> {
try {
Utils.assertDateTimeOffset(parameterFieldName, op, Timestamp.from(Instant.now()), responseData.get());
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* Single-Valued enumerations
*/
And("^Single Valued Enumeration Data in \"([^\"]*)\" has \"([^\"]*)\"$", (String parameterFieldName, String parameterAssertedValue) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
AtomicReference<String> fieldValue = new AtomicReference<>();
AtomicReference<String> assertedValue = new AtomicReference<>();
@ -538,6 +626,9 @@ public class WebAPIServer_1_0_2 implements En {
LOG.info("Assert True: " + fieldValue.get() + " equals " + assertedValue.get() + " ==> " + result.get());
assertTrue(result.get());
});
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
@ -545,6 +636,7 @@ public class WebAPIServer_1_0_2 implements En {
* TODO: turn array into JSON array and parse values from there
*/
And("^Multiple Valued Enumeration Data in \"([^\"]*)\" has \"([^\"]*)\"$", (String parameterFieldName, String parameterAssertedValue) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
AtomicReference<String> fieldValue = new AtomicReference<>();
AtomicReference<String> assertedValue = new AtomicReference<>();
@ -560,12 +652,16 @@ public class WebAPIServer_1_0_2 implements En {
LOG.info("Assert True: " + fieldValue.get() + " has " + assertedValue.get() + " ==> " + result.get());
assertTrue(result.get());
});
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* Date comparison ordering (asc, desc).
*/
And("^DateTimeOffset data in \"([^\"]*)\" is sorted in \"([^\"]*)\" order$", (String parameterFieldName, String parameterOrderByDirection) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
final String ASC = "asc", DESC = "desc";
AtomicReference<String> orderBy = new AtomicReference<>(parameterOrderByDirection.toLowerCase());
@ -592,10 +688,12 @@ public class WebAPIServer_1_0_2 implements En {
}
count.getAndIncrement();
} catch (EdmPrimitiveTypeException ptex) {
LOG.error("ERROR: exception thrown parsing Timestamp from given string." + ptex);
fail();
fail(ptex.getMessage());
}
});
} catch (Exception ex) {
fail(ex.getMessage());
}
});
And("^\"([^\"]*)\" data in Date Field \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String stringDatePart, String parameterFieldName, String op, String parameterAssertedValue) -> {
@ -614,13 +712,11 @@ public class WebAPIServer_1_0_2 implements En {
fieldValue.set(Utils.getDatePart(datePart.get(), item.get(fieldName)));
assertTrue(Utils.compare(fieldValue.get(), operator.get(), assertedValue.get()));
} catch (Exception ex){
//fail();
LOG.error("ERROR: exception thrown." + ex);
fail(ex.getMessage());
}
});
} catch (Exception ex) {
fail();
LOG.error("ERROR: exception thrown." + ex);
fail(ex.getMessage());
}
});
@ -629,6 +725,7 @@ public class WebAPIServer_1_0_2 implements En {
* TODO: consolidate with Year comparison with Date Field
*/
And("^\"([^\"]*)\" data in Timestamp Field \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String stringDatePart, String parameterFieldName, String op, String parameterAssertedValue) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
AtomicReference<Integer> fieldValue = new AtomicReference<>();
AtomicReference<Integer> assertedValue = new AtomicReference<>();
@ -644,13 +741,14 @@ public class WebAPIServer_1_0_2 implements En {
fieldValue.set(Utils.getTimestampPart(datePart.get(), item.get(fieldName).toString()));
assertTrue(Utils.compare(fieldValue.get(), operator.get(), assertedValue.get()));
} catch (Exception ex){
fail();
LOG.error("ERROR: exception thrown." + ex);
fail(ex.getMessage());
}
});
} catch (Exception ex) {
fail();
LOG.error("ERROR: exception thrown." + ex);
fail(ex.getMessage());
}
} catch (Exception ex) {
fail(ex.getMessage());
}
});
@ -658,6 +756,7 @@ public class WebAPIServer_1_0_2 implements En {
* String functions
*/
And("^String data in \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\"$", (String parameterFieldName, String op, String parameterAssertedValue) -> {
try {
String fieldName = Settings.resolveParametersString(parameterFieldName, settings);
AtomicReference<String> assertedValue = new AtomicReference<>(Settings.resolveParametersString(parameterAssertedValue, settings));
AtomicReference<String> operator = new AtomicReference<>(op.toLowerCase());
@ -665,8 +764,120 @@ public class WebAPIServer_1_0_2 implements En {
from(responseData.get()).getList(JSON_VALUE_PATH, HashMap.class).forEach(item -> {
assertTrue(Utils.compare(item.get(fieldName).toString(), operator.get(), assertedValue.get()));
});
} catch (Exception ex) {
fail(ex.getMessage());
}
});
/*
* Metadata validation methods
*/
/*
Checks that metadata are accessible and contain the resource name specified in generic.resoscript
*/
And("^the metadata contains the \"([^\"]*)\" resource$", (String parameterResourceName) -> {
final String resourceName = Settings.resolveParametersString(parameterResourceName, settings);
AtomicReference<CsdlEntityContainer> entityContainer = new AtomicReference<>();
try {
entityContainer.set(ODataHelper.findDefaultEntityContainer(commander.get(), xmlMetadata.get()));
assertNotNull("ERROR: server metadata does not contain the given resource name: " + resourceName,
entityContainer.get().getEntitySet(resourceName));
LOG.info("Found EntityContainer for the given resource: '" + resourceName + "'");
} catch (Exception ex) {
fail(ex.getMessage());
}
});
And("^resource metadata for \"([^\"]*)\" contains the fields in \"([^\"]*)\"$", (String parameterResourceName, String parameterSelectList) -> {
final String FIELD_SEPARATOR = ",";
final String selectList = Settings.resolveParametersString(parameterSelectList, settings);
try {
final String resourceName = Settings.resolveParametersString(parameterResourceName, settings);
List<String> fieldNames = Arrays.asList(selectList.split(FIELD_SEPARATOR));
//create field lookup
Map<String, CsdlProperty> fieldMap = new HashMap<>();
ODataHelper.findEntityTypesForEntityTypeName(commander.get(), xmlMetadata.get(), resourceName)
.forEach(csdlProperty -> fieldMap.put(csdlProperty.getName(), csdlProperty));
LOG.info("Searching metadata for fields in given select list: " + selectList);
fieldNames.forEach(fieldName -> {
//trim string just in case spaces were used after the commas
assertNotNull("ERROR: Field name '" + fieldName + "' is not present in server metadata!", fieldMap.get(fieldName.trim()));
LOG.info("Found: '" + fieldName.trim() + "'");
});
} catch (Exception ex) {
fail(ex.getMessage());
}
});
When("^a successful metadata request is made to the service root in \"([^\"]*)\"$", (String clientSettingsServiceRoot) -> {
final String serviceRoot = Settings.resolveParametersString(clientSettingsServiceRoot, settings);
assertEquals("ERROR: given service root doesn't match the one configured in the Commander", serviceRoot, commander.get().getServiceRoot());
try {
xmlMetadata.set(commander.get().getXMLMetadata());
} catch (ODataClientErrorException cex) {
responseCode.set(cex.getStatusLine().getStatusCode());
fail(cex.getMessage());
} catch (Exception ex) {
fail(ex.getMessage());
}
});
}
private static final class ODataHelper {
/**
* Finds the default entity container for the given configuration.
* @param commander a commander instance with a valid service root.
* @param xmlMetadata XML Metadata to search through
* @return the default CSDL Container for the given XMLMetadata
* @throws Exception if required metadata cannot be parsed, an exception will be thrown with an appropriate message.
*/
private static CsdlEntityContainer findDefaultEntityContainer(Commander commander, XMLMetadata xmlMetadata) throws Exception {
Edm edm = commander.getEdm();
if (edm == null)
throw new Exception("ERROR: Could not retrieve Edm from server using the given service root!");
if (xmlMetadata == null)
throw new Exception("ERROR: the provided XML metadata was null!");
if (edm.getEntityContainer() == null)
throw new Exception("ERROR: Could not find default EntityContainer for service root: " + commander.getServiceRoot());
String entityContainerNamespace = edm.getEntityContainer().getNamespace();
if (entityContainerNamespace == null)
throw new Exception("ERROR: no default EntityContainer namespace could be found");
return xmlMetadata.getSchema(entityContainerNamespace).getEntityContainer();
}
/**
* Gets a list of CsdlProperty items for the given entityTypeName.
* @param commander a Commander instance with a valid service root.
* @param xmlMetadata the metadata to search.
* @param entityTypeName the name of the entityType to search for. MUST be in the default EntityContainer.
* @return a list of CsdlProperty items for the given entityTypeName
* @throws Exception is thrown if the given metadata doesn't contain the given type name.
*/
private static List<CsdlProperty> findEntityTypesForEntityTypeName(Commander commander, XMLMetadata xmlMetadata, String entityTypeName) throws Exception {
CsdlEntityContainer entityContainer = findDefaultEntityContainer(commander, xmlMetadata);
CsdlSchema schemaForType = xmlMetadata.getSchema(entityContainer.getEntitySet(entityTypeName).getTypeFQN().getNamespace());
if (schemaForType == null)
throw new Exception("ERROR: could not find type corresponding to given type name: " + entityTypeName);
return schemaForType.getEntityType(entityTypeName).getProperties();
}
}
/**
@ -766,7 +977,6 @@ public class WebAPIServer_1_0_2 implements En {
} else if (operator.contentEquals(Operators.LESS_THAN_OR_EQUAL)) {
result = lhs <= rhs;
}
LOG.info("Compare: " + lhs + " " + operator + " " + rhs + " ==> " + result);
return result;
}
@ -793,8 +1003,7 @@ public class WebAPIServer_1_0_2 implements En {
} else if (operator.contentEquals(Operators.TO_UPPER)) {
result = lhs.toUpperCase().equals(rhs);
}
LOG.info("Compare: " + operator + "(" + lhs + ") == " + rhs + " ==> " + result);
LOG.info("Compare: \"" + lhs + "\" " + operator + " \"" + rhs + "\" ==> " + result);
return result;
}

View File

@ -207,7 +207,7 @@ public class App {
if (request.getOutputFile().toLowerCase().contains(EDMX_EXTENSION.toLowerCase())) {
//Main.main(new String[]{"-g", "org.reso.certification.stepdefs#WebAPIServer_1_0_2", "/home/jdarnell/work/reso/github/web-api-commander/src/main/java/org/reso/certification/features/web-api-server-1.0.2.feature"});
if (validateMetadata(commander, outputFilePath)) {
metadata = commander.getMetadata(outputFilePath);
metadata = commander.getEdm(outputFilePath);
} else {
LOG.error("Error: Invalid metadata retrieved. Cannot continue!!");
System.exit(NOT_OK);
@ -248,7 +248,7 @@ public class App {
if (cmd.hasOption(APP_OPTIONS.ACTIONS.GET_METADATA)) {
APP_OPTIONS.validateAction(cmd, APP_OPTIONS.ACTIONS.GET_METADATA);
metadata = commander.getMetadata();
metadata = commander.getEdm();
getMetadataReport(metadata);
} else if (cmd.hasOption(APP_OPTIONS.ACTIONS.VALIDATE_METADATA)) {

View File

@ -1,25 +1,20 @@
package org.reso.commander;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.commons.io.output.WriterOutputStream;
import org.apache.http.HttpStatus;
import org.apache.http.client.utils.URIBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.olingo.client.api.ODataClient;
import org.apache.olingo.client.api.communication.ODataClientErrorException;
import org.apache.olingo.client.api.communication.request.retrieve.EdmMetadataRequest;
import org.apache.olingo.client.api.communication.response.ODataRawResponse;
import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
import org.apache.olingo.client.api.domain.ClientEntity;
import org.apache.olingo.client.api.domain.ClientEntitySet;
import org.apache.olingo.client.api.edm.xml.Edmx;
import org.apache.olingo.client.api.edm.xml.XMLMetadata;
import org.apache.olingo.client.core.ODataClientFactory;
import org.apache.olingo.client.core.domain.ClientEntitySetImpl;
import org.apache.olingo.client.core.edm.ClientCsdlXMLMetadata;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.format.ContentType;
import org.reso.auth.OAuth2HttpClientFactory;
@ -52,7 +47,14 @@ public class Commander {
private ODataClient client;
private boolean useEdmEnabledClient;
String serviceRoot, bearerToken, clientId, clientSecret, authorizationUri, tokenUri, redirectUri, scope;
private String serviceRoot;
String bearerToken;
String clientId;
String clientSecret;
String authorizationUri;
String tokenUri;
String redirectUri;
String scope;
boolean isTokenClient, isOAuthClient;
private static final Logger LOG = LogManager.getLogger(Commander.class);
@ -69,6 +71,14 @@ public class Commander {
this.client = client;
}
public String getTokenUri() {
return tokenUri;
}
public String getServiceRoot() {
return serviceRoot;
}
/**
* Builder pattern for creating Commander instances.
*/
@ -167,24 +177,50 @@ public class Commander {
//private constructor for internal use, use Builder to construct instances
private Commander() { }
private Edm edm;
private XMLMetadata xmlMetadata;
/**
* Gets server metadata in Edm format.
* <p>
* @return XMLMetadata representation of the server metadata.
* @implNote the data in this item are cached in the commander once fetched
* @return Edm representation of the server metadata.
*/
public Edm getMetadata() {
Edm metadata = null;
public Edm getEdm() {
if (edm == null) {
try {
LOG.info("Fetching Metadata from " + serviceRoot + "...");
metadata = client.getRetrieveRequestFactory().getMetadataRequest(serviceRoot).execute().getBody();
LOG.info("Transfer complete! KBytes received: " + (metadata.toString().getBytes().length / 1024));
EdmMetadataRequest metadataRequest = client.getRetrieveRequestFactory().getMetadataRequest(serviceRoot);
LOG.info("Fetching Edm with OData Client from: " + metadataRequest.getURI().toString());
edm = metadataRequest.execute().getBody();
} catch (ODataClientErrorException cex) {
LOG.error(cex.getStackTrace());
LOG.error("ERROR: could not retrieve Metadata for the given service root!");
LOG.error(cex.getStatusLine().toString());
throw cex;
}
return metadata;
}
return edm;
}
public Edm getMetadata(String pathToXmlMetadata) {
/**
* Gets server metadata in XMLMetadata format.
* @implNote the data in this item are cached in the commander once fetched
* @return XMLMetadata representation of the server metadata.
*/
public XMLMetadata getXMLMetadata() {
if (xmlMetadata == null) {
try {
EdmMetadataRequest metadataRequest = client.getRetrieveRequestFactory().getMetadataRequest(serviceRoot);
LOG.info("Fetching XMLMetadata with OData Client from: " + metadataRequest.getURI().toString());
xmlMetadata = metadataRequest.getXMLMetadata();
} catch (ODataClientErrorException cex) {
LOG.error("ERROR: could not retrieve Metadata for the given service root!");
LOG.error(cex.getStatusLine().toString());
throw cex;
}
}
return xmlMetadata;
}
public Edm getEdm(String pathToXmlMetadata) {
try {
return client.getReader().readMetadata(new FileInputStream(pathToXmlMetadata));
} catch (FileNotFoundException fex) {
@ -202,31 +238,6 @@ public class Commander {
}
}
/**
* Converts Edm to XMLMetadata
* @param edm
* @return the XMLMetadata representation of the given Edm
*/
private XMLMetadata convertEdmToXMLMetadata(Edm edm) {
XMLMetadata xmlMetadata = null;
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(edm.toString().getBytes());
xmlMetadata = client.getDeserializer(ContentType.APPLICATION_XML).toMetadata(inputStream);
} catch (Exception ex) {
LOG.error(ex.getStackTrace());
}
return xmlMetadata;
}
public boolean validateMetadata(Edm metadata) {
try {
return validateMetadata(convertEdmToXMLMetadata(metadata));
} catch (Exception ex) {
LOG.error(ex.getStackTrace());
}
return false;
}
/**
* Validates given XMLMetadata
*
@ -256,10 +267,8 @@ public class Commander {
public boolean validateMetadata(InputStream inputStream) {
try {
// deserialize metadata from given file
XMLMetadata metadata =
client.getDeserializer(ContentType.APPLICATION_XML).toMetadata(inputStream);
XMLMetadata metadata = client.getDeserializer(ContentType.APPLICATION_XML).toMetadata(inputStream);
return validateMetadata(metadata);
} catch (Exception ex) {
LOG.error("ERROR in validateMetadata! " + ex.toString() );
}
@ -283,6 +292,14 @@ public class Commander {
return false;
}
public boolean isTokenClient() {
return isTokenClient;
}
public boolean isOAuthClient() {
return isOAuthClient;
}
public static boolean validateXML(String xmlString) {
return validateXML(new ByteArrayInputStream(xmlString.getBytes(UTF_8)));
}