diff --git a/src/main/java/org/reso/certification/containers/WebAPITestContainer.java b/src/main/java/org/reso/certification/containers/WebAPITestContainer.java index ed4cca2..0754b62 100644 --- a/src/main/java/org/reso/certification/containers/WebAPITestContainer.java +++ b/src/main/java/org/reso/certification/containers/WebAPITestContainer.java @@ -40,8 +40,7 @@ import java.util.stream.Collectors; import static org.junit.Assert.*; import static org.reso.commander.Commander.*; import static org.reso.commander.common.ErrorMsg.getDefaultErrorMessage; -import static org.reso.commander.common.TestUtils.HEADER_ODATA_VERSION; -import static org.reso.commander.common.TestUtils.JSON_VALUE_PATH; +import static org.reso.commander.common.TestUtils.*; import static org.reso.models.Request.loadFromRESOScript; /** @@ -390,7 +389,8 @@ public final class WebAPITestContainer implements TestContainer { URI pathToMetadata = getCommander().getPathToMetadata(request.getRequestUrl()); if (pathToMetadata != null) { - LOG.info("Requesting XML Metadata from: " + pathToMetadata.toString()); + LOG.info("Requesting XML Metadata from: " + pathToMetadata); + ODataTransportWrapper wrapper = getCommander().executeODataGetRequest(pathToMetadata.toString()); setODataRawResponse(wrapper.getODataRawResponse()); responseCode.set(wrapper.getHttpResponseCode()); @@ -404,8 +404,8 @@ public final class WebAPITestContainer implements TestContainer { xmlResponseData.set(wrapper.getResponseData()); xmlMetadata.set(Commander.deserializeXMLMetadata(xmlResponseData.get(), getCommander().getClient())); } else { - LOG.error(getDefaultErrorMessage("could not create metadata URI from given requestUri:", request.getRequestUrl())); - System.exit(NOT_OK); + failAndExitWithErrorMessage(getDefaultErrorMessage("Could not create metadata URI from given requestUri:", + request.getRequestUrl()), LOG); } } finally { haveMetadataBeenRequested.set(true); @@ -706,15 +706,23 @@ public final class WebAPITestContainer implements TestContainer { } private void processODataRequestException(ODataClientErrorException exception) { - LOG.debug("ODataClientErrorException caught. Check tests for asserted conditions..."); - LOG.debug(exception); + /* + TODO: determine whether these additional lines are needed or whether the bubbled error is sufficient + LOG.error("ODataClientErrorException caught. Check tests for asserted conditions..."); + LOG.error(exception); + */ + setODataClientErrorException(exception); setServerODataHeaderVersion(TestUtils.getHeaderData(HEADER_ODATA_VERSION, Arrays.asList(exception.getHeaderInfo()))); setResponseCode(exception.getStatusLine().getStatusCode()); } private void processODataRequestException(ODataServerErrorException exception) { - LOG.debug("ODataServerErrorException thrown in executeGetRequest. Check tests for asserted conditions..."); + /* + TODO: determine whether these additional lines are needed or whether the bubbled error is sufficient + LOG.error("ODataServerErrorException thrown in executeGetRequest. Check tests for asserted conditions..."); + */ + //TODO: look for better ways to do this in Olingo or open PR if (exception.getMessage().contains(Integer.toString(HttpStatus.SC_NOT_IMPLEMENTED))) { setResponseCode(HttpStatus.SC_NOT_IMPLEMENTED); @@ -722,7 +730,6 @@ public final class WebAPITestContainer implements TestContainer { setODataServerErrorException(exception); } - public boolean getIsValidXMLMetadata() { return isValidXMLMetadata.get(); } @@ -769,7 +776,7 @@ public final class WebAPITestContainer implements TestContainer { && edm.get() != null && getIsValidEdm(); } - public final WebAPITestContainer validateMetadata() { + public WebAPITestContainer validateMetadata() { try { if (!haveMetadataBeenRequested.get()) fetchXMLMetadata(); assertNotNull(getDefaultErrorMessage("no XML response data found!"), getXMLResponseData()); diff --git a/src/main/java/org/reso/certification/stepdefs/LookupResource.java b/src/main/java/org/reso/certification/stepdefs/LookupResource.java index 595f0e6..bc22c41 100644 --- a/src/main/java/org/reso/certification/stepdefs/LookupResource.java +++ b/src/main/java/org/reso/certification/stepdefs/LookupResource.java @@ -24,8 +24,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import static org.reso.certification.stepdefs.DataAvailability.CERTIFICATION_RESULTS_PATH; +import static org.reso.commander.common.ErrorMsg.getDefaultErrorMessage; import static org.reso.commander.common.ODataUtils.*; import static org.reso.commander.common.TestUtils.failAndExitWithErrorMessage; import static org.reso.commander.common.Utils.wrapColumns; @@ -37,7 +39,9 @@ public class LookupResource { private static final String PATH_TO_RESOSCRIPT_ARG = "pathToRESOScript"; private static final AtomicReference>> lookupResourceCache = new AtomicReference<>(new LinkedHashMap<>()); private static final String LOOKUP_RESOURCE_LOOKUP_METADATA_FILE_NAME = "lookup-resource-lookup-metadata.json"; - private static final String LOOKUP_RESOURCE_FIELD_METADATA_FILE_NAME = "lookup-resource-field-metadata.json"; + + //TODO: only output file in DEBUG mode + //private static final String LOOKUP_RESOURCE_FIELD_METADATA_FILE_NAME = "lookup-resource-field-metadata.json"; private static final String LOOKUP_NAME_FIELD = "LookupName"; @@ -78,9 +82,8 @@ public class LookupResource { lookupResourceCache.get().get(resourceName).addAll(results); - final JsonObject metadata = new JsonObject(); - metadata.add("lookups", ODataUtils.serializeLookupMetadata(container.get().getCommander().getClient(), results)); - Utils.createFile(CERTIFICATION_RESULTS_PATH, LOOKUP_RESOURCE_LOOKUP_METADATA_FILE_NAME, metadata.toString()); + Utils.createFile(CERTIFICATION_RESULTS_PATH, LOOKUP_RESOURCE_LOOKUP_METADATA_FILE_NAME, + ODataUtils.serializeLookupMetadata(container.get().getCommander().getClient(), results).toString()); } catch (Exception exception) { failAndExitWithErrorMessage("Unable to retrieve data from the Lookup Resource! " + exception.getMessage(), scenario); @@ -145,6 +148,7 @@ public class LookupResource { standardLookupFieldCache.get(resourceName).values().stream() .filter(referenceStandardField -> referenceStandardField.getLookupName() != null)).collect(Collectors.toSet()); + final ArrayList fieldsWithMissingAnnotations = new ArrayList<>(); lookupFields.forEach(referenceStandardField -> { LOG.debug("Standard Field: { " + "resourceName: \"" + referenceStandardField.getParentResourceName() + "\"" @@ -155,15 +159,19 @@ public class LookupResource { final boolean isStringDataType = foundElement != null && foundElement.getType().getFullQualifiedName().toString().contentEquals(EdmPrimitiveTypeKind.String.getFullQualifiedName().toString()); - if (foundElement != null && isStringDataType) { - if (!hasAnnotationTerm(foundElement, annotationTerm)) { - final String message = "Could not find required annotation with term \"" + annotationTerm + "\" for field: " - + referenceStandardField.getStandardName(); - LOG.info("WARN: " + message); - failAndExitWithErrorMessage(message, scenario); - } + + if (foundElement != null && isStringDataType && !hasAnnotationTerm(foundElement, annotationTerm)) { + fieldsWithMissingAnnotations.add(referenceStandardField.getStandardName()); } }); + + if (fieldsWithMissingAnnotations.size() > 0) { + final String msg = "The following fields are missing the required '" + annotationTerm + "' annotation: " + + wrapColumns(String.join(", ", fieldsWithMissingAnnotations)) + "\n"; + + LOG.error(getDefaultErrorMessage(msg)); + fail(msg); + } } @And("fields with the annotation term {string} MUST have a LookupName in the Lookup Resource") @@ -185,18 +193,14 @@ public class LookupResource { final Set missingLookupNames = Utils.getDifference(annotatedLookupNames, lookupNamesFromLookupData); if (missingLookupNames.size() > 0) { - failAndExitWithErrorMessage("LookupName elements missing from LookupMetadata: " - + wrapColumns(String.join(", ", missingLookupNames)), scenario); - } else { - if (filteredResourceFieldMap.size() > 0) { - scenario.log("Found all annotated LookupName elements in the Lookup data. Unique count: " + annotatedLookupNames.size()); - scenario.log("LookupNames: " + wrapColumns(String.join(", ", annotatedLookupNames))); + final String msg = "The following fields have LookupName annotations but are missing from the Lookup Resource: " + + wrapColumns(String.join(", ", missingLookupNames)) + "\n"; - Utils.createFile(CERTIFICATION_RESULTS_PATH, LOOKUP_RESOURCE_FIELD_METADATA_FILE_NAME, - ODataUtils.serializeFieldMetadataForLookupFields(filteredResourceFieldMap).toString()); - } else { - scenario.log("No annotated lookup names found in the OData XML Metadata."); - } + LOG.error(getDefaultErrorMessage(msg)); + fail(msg); + } else { + scenario.log("Found all annotated LookupName elements in the Lookup data. Unique count: " + annotatedLookupNames.size()); + scenario.log("LookupNames: " + wrapColumns(String.join(", ", annotatedLookupNames))); } } } diff --git a/src/main/java/org/reso/certification/stepdefs/WebAPIServerCore.java b/src/main/java/org/reso/certification/stepdefs/WebAPIServerCore.java index 1dfdeb4..c0d1160 100644 --- a/src/main/java/org/reso/certification/stepdefs/WebAPIServerCore.java +++ b/src/main/java/org/reso/certification/stepdefs/WebAPIServerCore.java @@ -36,7 +36,6 @@ import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; import static io.restassured.path.json.JsonPath.from; import static org.junit.Assert.*; @@ -49,7 +48,6 @@ import static org.reso.commander.common.ErrorMsg.getDefaultErrorMessage; import static org.reso.commander.common.TestUtils.DateParts.FRACTIONAL; import static org.reso.commander.common.TestUtils.*; import static org.reso.commander.common.TestUtils.Operators.*; -import static org.reso.models.Request.loadFromRESOScript; /** * Contains the glue code for Web API Core 2.0.0 Certification as well as previous Platinum tests, @@ -89,13 +87,6 @@ public class WebAPIServerCore implements En { if (!container.get().getIsInitialized()) { container.get().setSettings(Settings.loadFromRESOScript(new File(System.getProperty(PATH_TO_RESOSCRIPT_KEY)))); - - //moved to container initialization - // //overwrite any requests loaded with the reference queries - // container.get().getSettings().setRequests(loadFromRESOScript(new File(Objects.requireNonNull( - // getClass().getClassLoader().getResource(WEB_API_CORE_REFERENCE_REQUESTS)).getPath())) - // .stream().map(request -> Settings.resolveParameters(request, container.get().getSettings())).collect(Collectors.toList())); - container.get().initialize(); } } @@ -261,11 +252,10 @@ public class WebAPIServerCore implements En { * Rather than returning an integer response, this implementation expects the @odata.count property to be * available when requested, and a $top=0 may be used to restrict the number of items returned as results. */ - And("^the \"([^\"]*)\" value is greater than or equal to the number of results$", (String field) -> { - assertTrue(getDefaultErrorMessage("the @odata.count value MUST be present", - "and contain a non-zero value greater than or equal to the number of results!"), - TestUtils.validateODataCount(container.get().getResponseData())); - }); + And("^the \"([^\"]*)\" value is greater than or equal to the number of results$", (String field) -> + assertTrue(getDefaultErrorMessage("the @odata.count value MUST be present", + "and contain a non-zero value greater than or equal to the number of results!"), + TestUtils.validateODataCount(container.get().getResponseData()))); And("^data in the \"([^\"]*)\" fields are different in the second request than in the first$", (String parameterUniqueId) -> { try { @@ -469,7 +459,7 @@ public class WebAPIServerCore implements En { String op = andOrOp.toLowerCase(); boolean isAndOp = op.contains(AND); - //these should default to true when And and false when Or for the purpose of boolean comparisons + //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); @@ -620,7 +610,7 @@ public class WebAPIServerCore implements En { from(container.get().getResponseData()).getList(JSON_VALUE_PATH, ObjectNode.class).forEach(item -> { fieldValue.set(item.get(fieldName).toString()); - String assertMessage = EMPTY_STRING; + String assertMessage; if (useCollections) { if (item.get(fieldName).isArray()) { result.set(result.get() && TestUtils.testAnyOperator(item, fieldName, assertedValue.get())); @@ -795,16 +785,16 @@ public class WebAPIServerCore implements En { /* * Ensures that the server metadata for the given resource in parameterResourceName contains - * all of the fields in the given parameterSelectList. + * each of the fields in the given parameterSelectList. */ And("^resource metadata for \"([^\"]*)\" contains the fields in the given select list$", (String parameterResourceName) -> { assertTrue(getDefaultErrorMessage("no $select list found for requestId:", container.get().getRequest().getRequestId()), container.get().getSelectList().size() > 0); try { - LOG.info("Searching metadata for fields in given select list: " + container.get().getSelectList().toString()); + LOG.info("Searching metadata for fields in given select list: " + container.get().getSelectList()); container.get().getSelectList().forEach(fieldName -> { - //need to skip the expand field when looking through the metadata + //need to skip the expanded field when looking through the metadata if (container.get().getExpandField() != null && !fieldName.contentEquals(container.get().getExpandField())) { try { assertNotNull(getDefaultErrorMessage("Field name '" + fieldName + "' is not present in server metadata!"), @@ -850,16 +840,13 @@ public class WebAPIServerCore implements En { try { if (container.get().fetchXMLMetadata() == null) { //force exit rather than allowing the tests to finish - LOG.error(getDefaultErrorMessage("could not retrieve valid XML Metadata for given service root:", serviceRoot)); - System.exit(NOT_OK); + failAndExitWithErrorMessage("Could not retrieve valid XML Metadata for given service root: " + serviceRoot, LOG); } } catch (ODataClientErrorException cex) { container.get().setResponseCode(cex.getStatusLine().getStatusCode()); - LOG.error(getDefaultErrorMessage(cex)); - System.exit(NOT_OK); + failAndExitWithErrorMessage(getDefaultErrorMessage(cex), LOG); } catch (Exception ex) { - LOG.error(getDefaultErrorMessage(ex)); - System.exit(NOT_OK); + failAndExitWithErrorMessage(getDefaultErrorMessage(ex), LOG); } }); @@ -1107,7 +1094,7 @@ public class WebAPIServerCore implements En { */ void prepareAndExecuteRawGetRequest(String requestId) { try { - //reset local state each time a get request request is run + //reset local state each time a get request is run container.get().resetState(); assertNotNull(getDefaultErrorMessage("request Id cannot be null!"), requestId); @@ -1130,10 +1117,9 @@ public class WebAPIServerCore implements En { //execute request container.get().executePreparedRawGetRequest(); } catch (MalformedURLException urlException) { - LOG.info("Malformed URL was thrown in " + this.getClass() + ": " + urlException.toString() - + "\nSkipping Request!"); + LOG.info("Malformed URL was thrown in " + this.getClass() + ": " + urlException + "\nSkipping Request!"); } catch (Exception ex) { - LOG.info("Exception was thrown in " + this.getClass() + "!\n" + ex.toString()); + LOG.info("Exception was thrown in " + this.getClass() + "!\n" + ex); } } diff --git a/src/main/java/org/reso/commander/Commander.java b/src/main/java/org/reso/commander/Commander.java index 15d9a41..ecc8b9d 100644 --- a/src/main/java/org/reso/commander/Commander.java +++ b/src/main/java/org/reso/commander/Commander.java @@ -549,27 +549,24 @@ public class Commander { * @return a URI with the metadata path included */ public URI getPathToMetadata(String requestUri) { - if (requestUri == null) { - LOG.error(getDefaultErrorMessage("service root is null!")); - System.exit(NOT_OK); - } - - try { - String uri = requestUri; - if (!requestUri.contains(METADATA_PATH)) { - uri += METADATA_PATH; + if (requestUri == null || requestUri.length() == 0) { + TestUtils.failAndExitWithErrorMessage("OData service root is missing!", LOG); + } else { + try { + String uri = requestUri; + if (!requestUri.contains(METADATA_PATH)) { + uri += METADATA_PATH; + } + return new URI(uri).normalize(); + } catch (Exception ex) { + TestUtils.failAndExitWithErrorMessage("Could not create metadata URI.\n\t" + ex, LOG); } - return new URI(uri); - } catch (Exception ex) { - LOG.error(getDefaultErrorMessage("could not create path to metadata.\n" + ex.toString())); - System.exit(NOT_OK); } - return null; } /** - * Executes an OData GET Request w ith the current Commander instance + * Executes an OData GET Request with the current Commander instance * @param wrapper the OData transport wrapper to use for the request * @return and OData transport wrapper with the response, or exception if one was thrown */ diff --git a/src/main/java/org/reso/commander/common/ODataUtils.java b/src/main/java/org/reso/commander/common/ODataUtils.java index 6013c44..b487a37 100644 --- a/src/main/java/org/reso/commander/common/ODataUtils.java +++ b/src/main/java/org/reso/commander/common/ODataUtils.java @@ -80,22 +80,33 @@ public class ODataUtils { /** * Serializes a list of OData ClientEntity items in a JSON Array with those properties. * - * @param results list of OData ClientEntity results + * @param lookups list of OData ClientEntity results * @param client OData client to use as serializer * @return a JsonArray of results */ - public static JsonArray serializeLookupMetadata(ODataClient client, List results) { - final JsonArray lookups = new JsonArray(); + public static JsonObject serializeLookupMetadata(ODataClient client, List lookups) { + final String + DESCRIPTION_KEY = "description", DESCRIPTION = "Data Dictionary Lookup Resource Metadata", + VERSION_KEY = "version", VERSION = "1.7", + GENERATED_ON_KEY = "generatedOn", + LOOKUPS_KEY = "lookups"; + + JsonObject metadataReport = new JsonObject(); + metadataReport.addProperty(DESCRIPTION_KEY, DESCRIPTION); + metadataReport.addProperty(VERSION_KEY, VERSION); + metadataReport.addProperty(GENERATED_ON_KEY, Utils.getIsoTimestamp()); + + final JsonArray lookupsArray = new JsonArray(); try { final Gson gson = new Gson(); final JsonSerializer jsonSerializer = new JsonSerializer(false, ContentType.APPLICATION_JSON); - results.forEach(clientEntity -> { + lookups.forEach(clientEntity -> { try { StringWriter writer = new StringWriter(); jsonSerializer.write(writer, client.getBinder().getEntity(clientEntity)); Optional element = Optional.ofNullable(gson.fromJson(writer.toString(), JsonElement.class)); - element.ifPresent(lookups::add); + element.ifPresent(lookupsArray::add); } catch (ODataSerializerException e) { LOG.error("ERROR: could not deserialize. Exception: " + e); } @@ -104,15 +115,17 @@ public class ODataUtils { LOG.error(exception); } - return lookups; + metadataReport.add(LOOKUPS_KEY, lookupsArray); + return metadataReport; } + //TODO: Only output the field metadata in DEBUG mode public static JsonObject serializeFieldMetadataForLookupFields(Map> resourceFieldMap) { //TODO: migrate to test file final String LOOKUP_ANNOTATION_TERM = "RESO.OData.Metadata.LookupName"; final String - DESCRIPTION_KEY = "description", DESCRIPTION = "Lookup Resource Annotated Fields Metadata", + DESCRIPTION_KEY = "description", DESCRIPTION = "Data Dictionary Lookup Resource Annotated Fields Metadata", VERSION_KEY = "version", VERSION = "1.7", GENERATED_ON_KEY = "generatedOn", FIELDS_KEY = "fields"; diff --git a/src/main/java/org/reso/commander/common/TestUtils.java b/src/main/java/org/reso/commander/common/TestUtils.java index d5cedb2..a5668bb 100644 --- a/src/main/java/org/reso/commander/common/TestUtils.java +++ b/src/main/java/org/reso/commander/common/TestUtils.java @@ -65,7 +65,7 @@ public final class TestUtils { return URI.create( queryString.replace(" ", "%20") /* add other handlers here */ - ); + ).normalize(); } /** @@ -560,7 +560,7 @@ public final class TestUtils { } /** - * Helper method to find headers with a given key in an an array of headers + * Helper method to find headers with a given key in an array of headers * * @param key the header to get * @param headers an array containing Header objects @@ -701,7 +701,7 @@ public final class TestUtils { */ public static String convertInputStreamToString(InputStream inputStream) { try { - InputStreamReader isReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8.name()); + InputStreamReader isReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); BufferedReader reader = new BufferedReader(isReader); StringBuilder sb = new StringBuilder(); String str; @@ -796,27 +796,29 @@ public final class TestUtils { */ public static void assertXMLMetadataAreRequestedFromTheServer(WebAPITestContainer container, Scenario scenario) { if (container == null || container.getCommander() == null) { - failAndExitWithErrorMessage("Cannot create Commander instance!", scenario); + failAndExitWithErrorMessage("Cannot create Commander instance!", LOG); return; } if (!container.getHaveMetadataBeenRequested()) { final String serviceRoot = Settings.resolveParametersString(container.getServiceRoot(), container.getSettings()); if (!serviceRoot.contentEquals(container.getCommander().getServiceRoot())) { - failAndExitWithErrorMessage("given service root doesn't match the one configured in the Commander", scenario); + failAndExitWithErrorMessage("Given service root doesn't match the one configured in the Commander", scenario); return; } try { if (container.fetchXMLMetadata() == null) { - failAndExitWithErrorMessage("could not retrieve valid XML Metadata for given service root: " + serviceRoot, scenario); + failAndExitWithErrorMessage("Could not retrieve valid XML Metadata for given service root: " + + serviceRoot, LOG); } - } catch (ODataClientErrorException cex) { container.setResponseCode(cex.getStatusLine().getStatusCode()); - failAndExitWithErrorMessage(cex.getMessage(), scenario); + failAndExitWithErrorMessage("Could not retrieve valid XML Metadata for given service root: " + + serviceRoot + "\n\tException: " + cex.getMessage(), LOG); } catch (Exception ex) { - failAndExitWithErrorMessage(ex.toString(), scenario); + failAndExitWithErrorMessage("Could not retrieve valid XML Metadata for given service root: " + + serviceRoot + "\n\tException: " + ex, LOG); } } } @@ -829,11 +831,17 @@ public final class TestUtils { public static void assertXMLMetadataHasValidServiceDocument(WebAPITestContainer container, Scenario scenario) { try { if (container == null || container.getEdm() == null || container.getEdm().getEntityContainer() == null) { - failAndExitWithErrorMessage("Could not find default entity container for given service root: " + container.getServiceRoot(), scenario); + final String serviceRoot = container != null && container.getServiceRoot() != null + ? container.getServiceRoot() : ""; + + failAndExitWithErrorMessage("Could not find default entity container for given service root: " + serviceRoot, scenario); + } else { + LOG.info("Found Default Entity Container: '" + container.getEdm().getEntityContainer().getNamespace() + "'"); } - LOG.info("Found Default Entity Container: '" + container.getEdm().getEntityContainer().getNamespace() + "'"); } catch (ODataClientErrorException cex) { - container.setResponseCode(cex.getStatusLine().getStatusCode()); + if (container != null) { + container.setResponseCode(cex.getStatusLine().getStatusCode()); + } failAndExitWithErrorMessage(cex.toString(), scenario); } catch (Exception ex) { failAndExitWithErrorMessage(getDefaultErrorMessage(ex), scenario); @@ -863,7 +871,7 @@ public final class TestUtils { * Validates that the given response data have a valid OData count * * @param responseData the data to check for a count against - * @return true if the there is a count present and it's greater than or equal to the number of results + * @return true if the there is a count present, and it's greater than or equal to the number of results */ public static boolean validateODataCount(String responseData) { List items = from(responseData).getList(JSON_VALUE_PATH); @@ -948,6 +956,13 @@ public final class TestUtils { System.exit(NOT_OK); } + public static void failAndExitWithErrorMessage(String msg, Logger logger) { + if (logger != null) { + logger.error(getDefaultErrorMessage(msg)); + } + System.exit(NOT_OK); + } + /** * Builds a Data Dictionary Cache *