Issue #56 - Intermediate Commit
This commit is contained in:
parent
094fc33d19
commit
dc93fbb9b6
31
build.gradle
31
build.gradle
|
@ -175,6 +175,37 @@ task testDataDictionary_1_7() {
|
|||
}
|
||||
}
|
||||
|
||||
task testIdxPayload_1_7() {
|
||||
group = 'RESO Certification'
|
||||
description = 'Runs IDX Payload 1.7 Automated Acceptance Tests.' +
|
||||
'\n Example: ' +
|
||||
'\n $ ./gradlew testIdxPayload_1_7 -DpathToRESOScript=/path/to/web-api-core-1.0.2.resoscript -DshowResponses=true\n' +
|
||||
'\n Note: by default the Web API tests assume Collection(Edm.EnumType).' +
|
||||
'\n Pass -DuseCollections=false if using OData IsFlags.\n'
|
||||
|
||||
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',
|
||||
'pretty',
|
||||
'--plugin',
|
||||
'json:build/idx-payload.dd-1.7.json',
|
||||
'--plugin',
|
||||
'html:build/idx-payload.dd-1.7.html',
|
||||
'--glue',
|
||||
'org.reso.certification.stepdefs#IDXPayload',
|
||||
'src/main/java/org/reso/certification/features/payloads/idx-payload.feature'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task generateCertificationReport_DD_1_7() {
|
||||
group = 'RESO Certification'
|
||||
description = 'Runs Data Dictionary 1.7 tests and creates a certification report' +
|
||||
|
|
|
@ -16,35 +16,39 @@ Feature: Web API Server Add/Edit Endorsement
|
|||
# OData-Version: 4.01
|
||||
# Content-Type: application/json;odata.metadata=minimal
|
||||
# Accept: application/json
|
||||
#
|
||||
# This is without the prefer header and minimal value
|
||||
#
|
||||
@create @create-succeeds @add-edit-endorsement @rcp-010 @1.0.2
|
||||
Scenario: Create operation succeeds using a given payload
|
||||
Given valid metadata have been retrieved
|
||||
And request data has been provided in "create-succeeds.json"
|
||||
And request data in "create-succeeds.json" is valid JSON
|
||||
And schema in "create-succeeds.json" matches the metadata
|
||||
And the request header "OData-Version" is "4.01"
|
||||
And the request header "Content-Type" contains "application/json"
|
||||
And the request header "Accept" is "application/json"
|
||||
And the request header "OData-Version" "equals" one of the following values
|
||||
|4.0|4.01|
|
||||
And the request header "Content-Type" "contains" "application/json"
|
||||
And the request header "Accept" "contains" "application/json"
|
||||
When a "POST" request is made to the "resource-endpoint" URL with data in "create-succeeds.json"
|
||||
Then the test is skipped if the server responds with a status code of 401
|
||||
# TODO: check spec for 204
|
||||
When the server responds with one of the following status codes
|
||||
|200|201|
|
||||
Then the response header "OData-Version" is "4.01"
|
||||
And the response header "EntityId" is present
|
||||
And the response header "Location" is present
|
||||
And the response header "Location" is a valid URL
|
||||
When the server responds with a 200 status code "valid JSON exists" in the JSON response
|
||||
When the server responds with a 200 status code "@odata.context" "is present" in the JSON response
|
||||
When the server responds with a 200 status code "@odata.context" "is a valid URL" in the JSON response
|
||||
When the server responds with a 200 status code "@odata.id" "is present" in the JSON response
|
||||
When the server responds with a 200 status code "@odata.id" "is a valid URL" in the JSON response
|
||||
When the server responds with a 200 status code "@odata.editLink" "is present" in the JSON response
|
||||
When the server responds with a 200 status code "@odata.editLink" "is a valid URL" in the JSON response
|
||||
When the server responds with a 200 status code "@odata.etag" "is present" in the JSON response
|
||||
When the server responds with a 200 status code "@odata.etag" "starts with" "W/" in the JSON response
|
||||
When the server responds with a 200 status code data from "create-succeeds.json" "exists" in the JSON response
|
||||
When a "GET" request is made to the response header "Location" URL
|
||||
Then the server responds with one of the following status codes
|
||||
|201|
|
||||
And the response header "OData-Version" "equals" one of the following values
|
||||
|4.0|4.01|
|
||||
And the response header "EntityId" "MUST" "be present"
|
||||
And the response header "Location" "MUST" "be present"
|
||||
And the response header "Location" "is a valid URL"
|
||||
And the response header "Location" "MUST" reference the resource being created
|
||||
And the response is valid JSON
|
||||
And the JSON response "MUST" contain "@odata.context"
|
||||
And the JSON response value "@odata.context" "is a valid URL"
|
||||
And the JSON response "MUST" contain "@odata.id"
|
||||
And the JSON response value "@odata.id" "is a valid URL"
|
||||
And the JSON response "MAY" contain "@odata.editLink"
|
||||
And the JSON response value "@odata.editLink" "is a valid URL"
|
||||
And the JSON response "MAY" contain "@odata.etag"
|
||||
And the JSON response value "@odata.etag" "starts with" "W/"
|
||||
And the JSON response "MUST" contain all JSON data in "create-succeeds.json"
|
||||
When a "GET" request is made to the URL in response header "Location"
|
||||
Then the server responds with a status code of 200
|
||||
And the response has header "OData-Version" with one of the following values
|
||||
|4.0|4.01|
|
||||
|
@ -58,7 +62,7 @@ Feature: Web API Server Add/Edit Endorsement
|
|||
# SEE: https://reso.atlassian.net/wiki/spaces/RESOWebAPIRCP/pages/2239399511/RCP+-+WEBAPI-010+Add+Functionality+to+Web+API+Specification#Error-Message-Example
|
||||
# POST serviceRoot/Property
|
||||
# OData-Version: 4.01
|
||||
# Content-Type: application/json;odata.metadata=minimal
|
||||
# Content-Type: application/json
|
||||
# Accept: application/json
|
||||
@create @create-fails @add-edit-endorsement @rcp-010 @1.0.2
|
||||
Scenario: Create operation fails using a given payload
|
||||
|
@ -66,12 +70,16 @@ Feature: Web API Server Add/Edit Endorsement
|
|||
And request data has been provided in "create-fails.json"
|
||||
And request data in "create-fails.json" is valid JSON
|
||||
And schema in "create-fails.json" matches the metadata
|
||||
And the request header "OData-Version" is "4.01"
|
||||
And the request header "Content-Type" is "application/json;odata.metadata=minimal"
|
||||
And the request header "Accept" is "application/json"
|
||||
And the request header "OData-Version" "equals" one of the following values
|
||||
|4.0|4.01|
|
||||
And the request header "Content-Type" "MUST" "be present"
|
||||
And the request header "Content-Type" "equals" "application/json"
|
||||
And the request header "Accept" "MUST" "be present"
|
||||
And the request header "Accept" "contains" "application/json"
|
||||
When a "POST" request is made to the "resource-endpoint" URL with data in "create-fails.json"
|
||||
Then the server responds with one of the following error codes
|
||||
|400|401|403|405|408|500|501|503|
|
||||
And the response header "OData-Version" is "4.01"
|
||||
|400|
|
||||
And the response has header "OData-Version" with one of the following values
|
||||
|4.0|4.01|
|
||||
And the error response is in a valid format
|
||||
And the values in the "target" field in the JSON payload "error.details" path are contained within the metadata
|
||||
And the values in the "target" field in the JSON payload "error.details" path are contained within the metadata
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.apache.logging.log4j.LogManager;
|
|||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.olingo.client.api.data.ResWrap;
|
||||
import org.apache.olingo.commons.api.data.EntityCollection;
|
||||
import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef;
|
||||
import org.apache.olingo.commons.api.format.ContentType;
|
||||
import org.reso.certification.containers.WebAPITestContainer;
|
||||
import org.reso.commander.Commander;
|
||||
|
@ -20,6 +21,7 @@ import org.reso.models.Settings;
|
|||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -27,12 +29,15 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.hash.Hashing.sha256;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.reso.certification.containers.WebAPITestContainer.EMPTY_STRING;
|
||||
import static org.reso.commander.common.ErrorMsg.getDefaultErrorMessage;
|
||||
import static org.reso.commander.common.TestUtils.failAndExitWithErrorMessage;
|
||||
|
||||
public class IDXPayload {
|
||||
private static final Logger LOG = LogManager.getLogger(IDXPayload.class);
|
||||
private static final CharSequence SEPARATOR_CHARACTER = "|";
|
||||
private static final CharSequence MODIFICATION_TIMESTAMP_FIELD = "ModificationTimestamp";
|
||||
private static Scenario scenario;
|
||||
|
||||
private final static AtomicBoolean hasStandardResources = new AtomicBoolean(false);
|
||||
|
@ -76,7 +81,8 @@ public class IDXPayload {
|
|||
.map(Object::toString)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
standardResources.set(resources.stream().filter(DataDictionaryMetadata.v1_7.WELL_KNOWN_RESOURCES::contains).collect(Collectors.toSet()));
|
||||
standardResources.set(resources.stream()
|
||||
.filter(DataDictionaryMetadata.v1_7.WELL_KNOWN_RESOURCES::contains).collect(Collectors.toSet()));
|
||||
nonStandardResources.set(Sets.difference(resources, standardResources.get()));
|
||||
|
||||
hasStandardResources.set(standardResources.get().size() > 0);
|
||||
|
@ -91,7 +97,7 @@ public class IDXPayload {
|
|||
}
|
||||
|
||||
public static String hashValues(String ...values) {
|
||||
return sha256().hashString(String.join(EMPTY_STRING, values), StandardCharsets.UTF_8).toString();
|
||||
return sha256().hashString(String.join(SEPARATOR_CHARACTER, values), StandardCharsets.UTF_8).toString();
|
||||
}
|
||||
|
||||
@When("{int} records are sampled from each RESO Standard resource in the server metadata")
|
||||
|
@ -100,44 +106,108 @@ public class IDXPayload {
|
|||
scenario.log("No RESO Standard Resources to sample");
|
||||
assumeTrue(true);
|
||||
} else {
|
||||
AtomicReference<ResWrap<EntityCollection>> entitySet = new AtomicReference<>();
|
||||
final AtomicReference<ResWrap<EntityCollection>> entitySet = new AtomicReference<>();
|
||||
|
||||
//Map of String Resource Names to Key-Mapped Hash of Field and SHA value
|
||||
AtomicReference<Map<String, Map<String, List<String>>>> encodedSamples = new AtomicReference<>(new LinkedHashMap<>());
|
||||
final AtomicReference<Map<String, Map<String, List<Vector<String>>>>> encodedSamples = new AtomicReference<>(new LinkedHashMap<>());
|
||||
|
||||
standardResources.get().forEach(resourceName -> {
|
||||
//create placeholder hashmap for this resource name
|
||||
encodedSamples.get().put(resourceName, new LinkedHashMap<>());
|
||||
|
||||
try {
|
||||
container.get().setRequestUri(Commander.prepareURI(
|
||||
container.get().getCommander().getClient().newURIBuilder(
|
||||
container.get().getServiceRoot()).appendEntitySetSegment(resourceName).build().toString()));
|
||||
final URI uri = Commander.prepareURI(container.get().getCommander().getClient().newURIBuilder(
|
||||
container.get().getServiceRoot()).appendEntitySetSegment(resourceName).build().toString());
|
||||
|
||||
container.get().setRequestUri(uri);
|
||||
|
||||
assertNotNull(getDefaultErrorMessage("Request URI was null!"), uri);
|
||||
|
||||
LOG.info("Requesting " + resourceName + " data from: " + uri.toString());
|
||||
container.get().executePreparedRawGetRequest();
|
||||
entitySet.set(container.get().getCommander().getClient().getDeserializer(ContentType.APPLICATION_JSON).toEntitySet(new ByteArrayInputStream(container.get().getResponseData().getBytes())));
|
||||
|
||||
entitySet.set(container.get().getCommander().getClient()
|
||||
.getDeserializer(ContentType.APPLICATION_JSON)
|
||||
.toEntitySet(new ByteArrayInputStream(container.get().getResponseData().getBytes())));
|
||||
|
||||
} catch (Exception exception) {
|
||||
LOG.error(exception);
|
||||
} finally {
|
||||
final String keyField = container.get().getEdm().getEntityContainer().getEntitySet(resourceName).getEntityType().getKeyPropertyRefs().get(0).getName();
|
||||
|
||||
//assumes the 0th key is always used. TODO: determine if we need to scan the keys.
|
||||
final List<EdmKeyPropertyRef> keyFields = container.get().getEdm().getEntityContainer()
|
||||
.getEntitySet(resourceName).getEntityType().getKeyPropertyRefs();
|
||||
|
||||
LOG.debug("Keys is: " + keyFields.stream().map(EdmKeyPropertyRef::getName).collect(Collectors.joining(", ")));
|
||||
|
||||
assert(keyFields.size() > 0) :
|
||||
getDefaultErrorMessage("no Key Fields found! Resources MUST have at least one key.");
|
||||
|
||||
//we will always key the hash by the first key, the other key fields will still be there
|
||||
//for the MUST requirement checks
|
||||
final String keyField = keyFields.get(0).getName();
|
||||
LOG.info("Hashing " + resourceName + " payload values...");
|
||||
entitySet.get().getPayload().getEntities().forEach(entity -> {
|
||||
entity.getProperties().forEach(property -> {
|
||||
entity.getProperties().parallelStream().forEach(property -> {
|
||||
if (!encodedSamples.get().get(resourceName).containsKey(keyField)) {
|
||||
encodedSamples.get().get(resourceName).put(keyField, new LinkedList<>());
|
||||
encodedSamples.get().get(resourceName).put(entity.getProperty(keyField).getValue().toString(), new LinkedList<>());
|
||||
}
|
||||
|
||||
encodedSamples.get().get(resourceName).get(keyField).add(property.getName());
|
||||
encodedSamples.get().get(resourceName).get(keyField).add(
|
||||
property.getName().contentEquals(keyField)
|
||||
? property.getValue().toString() : (property.getValue() != null
|
||||
? hashValues(property.getValue().toString()) : null));
|
||||
final Vector<String> fieldMeta = new Vector<>();
|
||||
fieldMeta.setSize(3);
|
||||
fieldMeta.set(0, property.getName());
|
||||
fieldMeta.set(1, property.getName().contentEquals(MODIFICATION_TIMESTAMP_FIELD) || keyFields.stream().reduce(false,
|
||||
(acc, f) -> acc && f.getName().contentEquals(property.getName()), Boolean::logicalAnd)
|
||||
? property.getValue().toString() : (property.getValue() != null
|
||||
? hashValues(property.getValue().toString()) : null));
|
||||
fieldMeta.set(2, property.getValue() == null ? null :
|
||||
(keyFields.stream().anyMatch(f -> f.getName().contentEquals(property.getName())) ? "primaryKey" : null));
|
||||
|
||||
LOG.info("Adding fieldMeta: " + fieldMeta.toString());
|
||||
|
||||
encodedSamples.get().get(resourceName).get(entity.getProperty(keyField).getValue().toString()).add(fieldMeta);
|
||||
});
|
||||
});
|
||||
LOG.info(encodedSamples.get().toString());
|
||||
LOG.info("Values encoded!");
|
||||
//createDataAvailabilityReport(encodedSamples);
|
||||
}
|
||||
LOG.info("Records Sampled: " + encodedSamples.get().get(resourceName).values().size());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void createDataAvailabilityReport(AtomicReference<Map<String, Map<String, List<Vector<String>>>>> encodedSamples) {
|
||||
AtomicReference<Map<String, Map<String, Integer>>> resourceTallies = new AtomicReference<>(new LinkedHashMap<>());
|
||||
encodedSamples.get().keySet().forEach(resourceName -> {
|
||||
LOG.debug("Processing resource: " + resourceName);
|
||||
LOG.debug("Sample size: " + encodedSamples.get().get(resourceName).keySet().size());
|
||||
//for each resource, go through the keys and tally the data presence counts for each field
|
||||
//as well as the number of samples in each case
|
||||
|
||||
resourceTallies.get().put(resourceName, new LinkedHashMap<>());
|
||||
encodedSamples.get().get(resourceName).forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
value.forEach(sample -> {
|
||||
if (sample != null) {
|
||||
//if element has a value
|
||||
if (sample.get(1) != null) {
|
||||
//if the value hasn't already been tallied, then just add one
|
||||
if (!resourceTallies.get().get(resourceName).containsKey(sample.get(0))) {
|
||||
resourceTallies.get().get(resourceName).put(sample.get(0), 1);
|
||||
} else {
|
||||
//otherwise, increment the current value
|
||||
resourceTallies.get().get(resourceName).put(sample.get(0),
|
||||
resourceTallies.get().get(resourceName).get(sample.get(0) + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
LOG.info(resourceTallies.get());
|
||||
}
|
||||
|
||||
@When("{int} records are sampled from each non standard resource in the server metadata")
|
||||
public void recordsAreSampledFromEachNonStandardResourceInTheServerMetadata(int numRecords) {
|
||||
}
|
||||
|
|
|
@ -95,4 +95,48 @@ public class WebAPIServerAddEdit {
|
|||
public void theRequestHeaderContains(String arg0, String arg1) {
|
||||
|
||||
}
|
||||
|
||||
@And("the request header {string} {string} one of the following values")
|
||||
public void theRequestHeaderOneOfTheFollowingValues(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@And("the request header {string} {string} {string}")
|
||||
public void theRequestHeader(String arg0, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
@And("the response header {string} {string} one of the following values")
|
||||
public void theResponseHeaderOneOfTheFollowingValues(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@And("the response header {string} {string} {string}")
|
||||
public void theResponseHeader(String arg0, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
@And("the response header {string} {string}")
|
||||
public void theResponseHeader(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@And("the response header {string} {string} reference the resource being created")
|
||||
public void theResponseHeaderReferenceTheResourceBeingCreated(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@And("the JSON response {string} contain {string}")
|
||||
public void theJSONResponseContain(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@And("the JSON response value {string} {string}")
|
||||
public void theJSONResponseValue(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@And("the JSON response value {string} {string} {string}")
|
||||
public void theJSONResponseValue(String arg0, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
@And("the JSON response {string} contain all JSON data in {string}")
|
||||
public void theJSONResponseContainAllJSONDataIn(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
@When("a {string} request is made to the URL in response header {string}")
|
||||
public void aRequestIsMadeToTheURLInResponseHeader(String arg0, String arg1) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,6 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"annotations",
|
||||
"fieldName",
|
||||
"isCollection",
|
||||
"nullable",
|
||||
|
@ -89,8 +88,11 @@
|
|||
},
|
||||
"Annotation": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"term": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"qt-uri-protocols": [
|
||||
|
|
Loading…
Reference in New Issue