From f1aa58cd09e6609a261376673f6460369de13187 Mon Sep 17 00:00:00 2001 From: preetamnpr <128618622+preetamnpr@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:32:42 +0100 Subject: [PATCH 1/8] SD-1823 revisit of the Ovs checks with unit test cases. --- ovs/pom.xml | 5 + .../standards/ovs/OvsScenarioListBuilder.java | 11 +- .../standards/ovs/checks/OvsChecks.java | 306 ++++++++---------- .../ovs/party/OvsFilterParameter.java | 45 ++- .../standards/ovs/party/OvsPublisher.java | 38 ++- ...s-300-response-wrong-attribute-values.json | 119 +++++++ .../ovs-300-response-wrong-date-times.json | 118 +++++++ .../ovs-300-response-wrong-structure.json | 119 +++++++ .../ovs/messages/ovs-300-response.json | 45 ++- .../standards/ovs/OvsChecksTest.java | 273 ++++++++++++++++ 10 files changed, 883 insertions(+), 196 deletions(-) create mode 100644 ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-attribute-values.json create mode 100644 ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-date-times.json create mode 100644 ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-structure.json create mode 100644 ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java diff --git a/ovs/pom.xml b/ovs/pom.xml index 34701936..d9e08d71 100644 --- a/ovs/pom.xml +++ b/ovs/pom.xml @@ -23,5 +23,10 @@ org.projectlombok lombok + + + org.junit.jupiter + junit-jupiter + diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java index 819ad016..5b5e7fdc 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java @@ -41,13 +41,13 @@ public static LinkedHashMap createModuleScenario CARRIER_SERVICE_NAME, "Red Falcon Service", START_DATE, - "2026-01-01")), + "2024-01-01")), scenarioWithParameters( Map.of( CARRIER_SERVICE_NAME, "Great Lion Service", END_DATE, - "2021-01-01")), + "2025-01-01")), scenarioWithParameters(Map.of(CARRIER_SERVICE_CODE, "BW1")), scenarioWithParameters(Map.of(CARRIER_SERVICE_CODE, "BW1", LIMIT, "1")), scenarioWithParameters(Map.of(UNIVERSAL_SERVICE_REFERENCE, "SR12345A")), @@ -64,9 +64,10 @@ public static LinkedHashMap createModuleScenario noAction() .thenEither( scenarioWithParameters(Map.of(UN_LOCATION_CODE, "NLAMS")), - scenarioWithParameters(Map.of(UN_LOCATION_CODE, "USNYC", LIMIT, "1")), - scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM")), - scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM", LIMIT, "1")))), + scenarioWithParameters(Map.of(UN_LOCATION_CODE, "USNYC", LIMIT, "1")) + //TODO: Check schema validation for this + /*scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM")), + scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM", LIMIT, "1"))*/)), Map.entry( "Voyage schedules", noAction() diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java index 5cff8bd2..2e3c4c0d 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java @@ -13,6 +13,7 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.*; import java.util.function.BiPredicate; import java.util.function.Supplier; @@ -30,124 +31,70 @@ public static ActionCheck responseContentChecks( checks.add( JsonAttribute.customValidator( - "Validate carrierServiceCode exists and " - + "matches in JSON response if request parameter has carrierServiceCode", - body -> - validateParameter( - body, - sspSupplier, - OvsFilterParameter.CARRIER_SERVICE_CODE, - "*/carrierServiceCode"))); - - checks.add( - JsonAttribute.customValidator( - "Validate universalServiceReference exists and " - + "matches in JSON response if request parameter has universalServiceReference", - body -> - validateParameter( - body, - sspSupplier, - OvsFilterParameter.UNIVERSAL_SERVICE_REFERENCE, - "*/universalServiceReference"))); - - checks.add( - JsonAttribute.customValidator( - "Validate vesselIMONumber exists and" - + " matches in JSON response if request parameter has vesselIMONumber", - body -> - validateParameter( - body, - sspSupplier, - OvsFilterParameter.VESSEL_IMO_NUMBER, - "*/vesselSchedules/*/vesselIMONumber"))); - - checks.add( - JsonAttribute.customValidator( - "Validate vesselName exists and" - + " matches in JSON response if request parameter has vesselName", - body -> - validateParameter( - body, - sspSupplier, - OvsFilterParameter.VESSEL_NAME, - "*/vesselSchedules/*/vesselName"))); - - checks.add( - JsonAttribute.customValidator( - "Validate carrierVoyageNumber exists and " - + "matches in JSON response if request parameter has carrierVoyageNumber", - body -> - validateParameter( - body, - sspSupplier, - OvsFilterParameter.CARRIER_VOYAGE_NUMBER, - "*/vesselSchedules/*/transportCalls/*/carrierExportVoyageNumber", - "*/vesselSchedules/*/transportCalls/*/carrierImportVoyageNumber"))); - - checks.add( - JsonAttribute.customValidator( - "Validate universalVoyageReference exists and" - + " matches in JSON response if request parameter has universalVoyageReference", - body -> - validateParameter( - body, - sspSupplier, - OvsFilterParameter.UNIVERSAL_VOYAGE_REFERENCE, - "*/vesselSchedules/*/transportCalls/*/universalImportVoyageReference", - "*/vesselSchedules/*/transportCalls/*/universalExportVoyageReference"))); - - checks.add( - JsonAttribute.customValidator( - "Validate UNLocationCode exists and " - + "matches in JSON response if request parameter has UNLocationCode", - body -> - validateParameter( - body, - sspSupplier, - OvsFilterParameter.UN_LOCATION_CODE, - "*/vesselSchedules/*/transportCalls/*/location/UNLocationCode"))); + "Every response received during a conformance test must contain schedules", + body -> { + Set validationErrors = new LinkedHashSet<>(); + checkServiceSchedulesExist(body) + .forEach( + validationError -> + validationErrors.add( + "CheckServiceSchedules failed: %s".formatted(validationError))); + if (validationErrors.isEmpty()) { + return Set.of(); + } + return validationErrors; + })); checks.add( JsonAttribute.customValidator( - "Validate facilitySMDGCode exists and " - + "matches in JSON response if request parameter has facilitySMDGCode", - body -> - validateParameter( - body, - sspSupplier, - OvsFilterParameter.FACILITY_SMDG_CODE, - "*/vesselSchedules/*/transportCalls/*/location/facilitySMDGCode"))); + "If present, at least schedule attribute must match the corresponding query parameters", + body -> { + Map filterParametersMap = sspSupplier.get().getMap(); + Set validationErrors = new LinkedHashSet<>(); + checkThatScheduleValuesMatchParamValues(body, filterParametersMap) + .forEach( + validationError -> + validationErrors.add( + "Schedule Param Value Validation failed: %s" + .formatted(validationError))); + return validationErrors; + })); checks.add( JsonAttribute.customValidator( - "Validate startDate exists and " - + "matches in JSON response if request parameter has startDate", - body -> - validateDate( - body, - sspSupplier, - OvsFilterParameter.START_DATE, - (eventDate, expectedStartDate) -> - !eventDate.isAfter( - expectedStartDate)))); // Check if eventDate is before or equal to - // startDate + "Check eventDateTime is greater than or equal to startDate filter parameter if present", + body -> { + Set validationErrors = new LinkedHashSet<>(); + Map filterParametersMap = sspSupplier.get().getMap(); + validateDate( + body, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore) + .forEach( + validationError -> + validationErrors.add( + "Start Date EventDateTime validation failed: %s" + .formatted(validationError))); + return validationErrors; + })); checks.add( JsonAttribute.customValidator( - "Validate endDate exists and " - + "matches in JSON response if request parameter has endDate", - body -> - validateDate( - body, - sspSupplier, - OvsFilterParameter.END_DATE, - (eventDate, expectedEndDate) -> - !eventDate.isBefore( - expectedEndDate)))); // Check if eventDate is after or equal to endDate + "Check eventDateTime is less than or equal to endDate filter parameter if present", + body -> { + Set validationErrors = new LinkedHashSet<>(); + Map filterParametersMap = sspSupplier.get().getMap(); + validateDate( + body, filterParametersMap, OvsFilterParameter.END_DATE, LocalDate::isAfter) + .forEach( + validationError -> + validationErrors.add( + "EndDate EventDateTime validation failed: %s" + .formatted(validationError))); + return validationErrors; + })); checks.add( JsonAttribute.customValidator( - "Validate transportCallReference is unique across each array node", + "Check transportCallReference is unique across each service schedules", OvsChecks::validateUniqueTransportCallReference)); checks.add( @@ -166,7 +113,6 @@ public static ActionCheck responseContentChecks( "The number of schedules exceeds the limit parameter: " + expectedLimit); } } - return Set.of(); })); @@ -174,73 +120,56 @@ public static ActionCheck responseContentChecks( OvsRole::isPublisher, matched, HttpMessageType.RESPONSE, standardVersion, checks); } - private Set validateParameter( - JsonNode body, - Supplier sspSupplier, - OvsFilterParameter parameter, - String... jsonPaths) { - Optional> param = - sspSupplier.get().getMap().entrySet().stream() - .filter(e -> e.getKey().equals(parameter)) - .findFirst(); - - if (param.isPresent()) { - Set expectedValues = - Arrays.stream(param.get().getValue().split(",")) - .map(String::trim) - .collect(Collectors.toSet()); - - // Check if ANY of the jsonPaths match the expectedValues - if (jsonPaths.length > 1) { - boolean anyMatch = - Stream.of(jsonPaths) - .anyMatch( - jsonPath -> - findMatchingNodes(body, jsonPath) - .anyMatch( - valueNode -> - !(valueNode.isMissingNode() || valueNode.isNull()) - && expectedValues.contains(valueNode.asText()))); - - if (!anyMatch) { - return Set.of( - "Missing or mismatched values for parameter: " - + parameter.getQueryParamName() - + " in any of the paths: " - + Arrays.toString(jsonPaths)); - } - } else { - Set errors = - Stream.of(jsonPaths) - .flatMap( - jsonPath -> - findMatchingNodes(body, jsonPath) - .filter( - valueNode -> - !(valueNode.isMissingNode() || valueNode.isNull()) - && !expectedValues.contains(valueNode.asText())) - .map( - valueNode -> - "Missing or mismatched " - + jsonPath - + ": " - + valueNode.asText())) - .collect(Collectors.toSet()); + public Set checkThatScheduleValuesMatchParamValues( + JsonNode schedulesNode, Map filterParametersMap) { + Set validationErrors = new LinkedHashSet<>(); + Arrays.stream(OvsFilterParameter.values()) + .filter(param -> !param.getJsonPaths().isEmpty()) + .filter(param -> !param.isSeparateCheckRequired()) + .filter(filterParametersMap::containsKey) + .forEach( + filterParameter -> { + Set parameterValues = + Arrays.stream(filterParametersMap.get(filterParameter).split(",")) + .collect(Collectors.toSet()); + Set attributeValues = new HashSet<>(); + Set jsonPaths = filterParameter.getJsonPaths(); + jsonPaths.forEach( + jsonPathExpression -> { + findMatchingNodes(schedulesNode, jsonPathExpression) + .forEach( + node -> { + if (!node.isMissingNode() && !node.isNull()) { + attributeValues.add(node.asText()); + } + }); + }); + if (!attributeValues.isEmpty() + && parameterValues.stream().noneMatch(attributeValues::contains)) { + validationErrors.add( + "Value%s '%s' at path%s '%s' do%s not match value%s of query parameter '%s'" + .formatted( + attributeValues.size() > 1 ? "s" : "", + String.join(", ", attributeValues), + String.join(", ", jsonPaths), + attributeValues.size() > 1 ? "" : "es", + filterParametersMap.get(filterParameter).contains(",") ? "s" : "", + String.join(", ", filterParametersMap.get(filterParameter).split(",")), + filterParameter.getQueryParamName())); + } + }); - return errors.isEmpty() ? Set.of() : errors; - } - } - return Set.of(); + return validationErrors; } - private Set validateDate( + public Set validateDate( JsonNode body, - Supplier sspSupplier, + Map filterParametersMap, OvsFilterParameter dateParameter, BiPredicate dateComparison) { Optional> dateParam = - sspSupplier.get().getMap().entrySet().stream() + filterParametersMap.entrySet().stream() .filter(e -> e.getKey().equals(dateParameter)) .findFirst(); @@ -258,11 +187,12 @@ private Set validateDate( StreamSupport.stream(timestampsNode.spliterator(), false) .filter( timestampNode -> { - OffsetDateTime eventDateTime = - OffsetDateTime.parse( - timestampNode.path("eventDateTime").asText(), - DateTimeFormatter.ISO_DATE_TIME); - return dateComparison.test(eventDateTime.toLocalDate(), expectedDate); + LocalDate eventDateTime = + stringToISODateTime(timestampNode.path("eventDateTime").asText()); + if (eventDateTime != null) { + return dateComparison.test(eventDateTime, expectedDate); + } + return false; })) .map( timestampNode -> @@ -277,14 +207,21 @@ private Set validateDate( return errors.isEmpty() ? Set.of() : errors; } - private Set validateUniqueTransportCallReference(JsonNode body) { + private static LocalDate stringToISODateTime(String dateTimeString) { + try { + return OffsetDateTime.parse(dateTimeString, DateTimeFormatter.ISO_DATE_TIME).toLocalDate(); + } catch (DateTimeParseException e) { + return null; + } + } + + public Set validateUniqueTransportCallReference(JsonNode body) { Set transportCallReferences = new HashSet<>(); Set errors = new HashSet<>(); - // Iterate over each array node in the response body for (JsonNode node : body) { - // Assuming the path to transportCallReference is consistent across array nodes - findMatchingNodes(node, "*/vesselSchedules/*/transportCalls/*/transportCallReference") + findMatchingNodes(node, "vesselSchedules/*/transportCalls/*/transportCallReference") + .filter(tcrNode -> !tcrNode.isMissingNode() && !tcrNode.isNull()) .forEach( transportCallReferenceNode -> { String transportCallReference = transportCallReferenceNode.asText(); @@ -293,15 +230,13 @@ private Set validateUniqueTransportCallReference(JsonNode body) { } }); } - return errors; } - private Stream findMatchingNodes(JsonNode node, String jsonPath) { + public Stream findMatchingNodes(JsonNode node, String jsonPath) { if (jsonPath.isEmpty() || jsonPath.equals("/")) { return Stream.of(node); } - String[] pathSegments = jsonPath.split("/"); if (pathSegments[0].equals("*")) { if (node.isArray()) { @@ -324,4 +259,19 @@ private Stream findMatchingNodes(JsonNode node, String jsonPath) { String.join("/", Arrays.copyOfRange(pathSegments, 1, pathSegments.length))); } } + + public Set checkServiceSchedulesExist(JsonNode body) { + Set validationErrors = new LinkedHashSet<>(); + if (body == null || body.isMissingNode() || body.isNull()) { + validationErrors.add("Response body is missing or null."); + } else { + boolean hasVesselSchedules = + findMatchingNodes(body, "*/vesselSchedules") + .anyMatch(node -> !node.isMissingNode() && node.isArray() && !node.isEmpty()); + if (!hasVesselSchedules) { + validationErrors.add("Response doesn't have schedules."); + } + } + return validationErrors; + } } diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsFilterParameter.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsFilterParameter.java index fdb2abe2..cce5403b 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsFilterParameter.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsFilterParameter.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -11,15 +12,25 @@ public enum OvsFilterParameter { CARRIER_SERVICE_NAME("carrierServiceName"), CARRIER_SERVICE_CODE("carrierServiceCode"), UNIVERSAL_SERVICE_REFERENCE("universalServiceReference"), - VESSEL_IMO_NUMBER("vesselIMONumber"), - VESSEL_NAME("vesselName"), - CARRIER_VOYAGE_NUMBER("carrierVoyageNumber"), - UNIVERSAL_VOYAGE_REFERENCE("universalVoyageReference"), - UN_LOCATION_CODE("UNLocationCode"), - FACILITY_SMDG_CODE("facilitySMDGCode"), - START_DATE("startDate"), - END_DATE("endDate"), - LIMIT("limit"), + VESSEL_IMO_NUMBER("vesselIMONumber", false, "*/vesselSchedules/*/vesselIMONumber"), + VESSEL_NAME("vesselName", false, "*/vesselSchedules/*/vesselName"), + CARRIER_VOYAGE_NUMBER( + "carrierVoyageNumber", + false, + "*/vesselSchedules/*/transportCalls/*/carrierExportVoyageNumber", + "*/vesselSchedules/*/transportCalls/*/carrierImportVoyageNumber"), + UNIVERSAL_VOYAGE_REFERENCE( + "universalVoyageReference", + false, + "*/vesselSchedules/*/transportCalls/*/universalImportVoyageReference", + "*/vesselSchedules/*/transportCalls/*/universalExportVoyageReference"), + UN_LOCATION_CODE( + "UNLocationCode", false, "*/vesselSchedules/*/transportCalls/*/location/UNLocationCode"), + FACILITY_SMDG_CODE( + "facilitySMDGCode", false, "*/vesselSchedules/*/transportCalls/*/location/facilitySMDGCode"), + START_DATE("startDate", true, "*/vesselSchedules/*/transportCalls/*/timestamps/*/eventDateTime"), + END_DATE("endDate", true, "*/vesselSchedules/*/transportCalls/*/timestamps/*/eventDateTime"), + LIMIT("limit", true), ; public static final Map byQueryParamName = @@ -30,7 +41,21 @@ public enum OvsFilterParameter { @Getter private final String queryParamName; - OvsFilterParameter(String queryParamName) { + @Getter private final Set jsonPaths; + + @Getter private final boolean isSeparateCheckRequired; + + OvsFilterParameter(String queryParamName, boolean isSeparateCheckRequired, String... jsonPaths) { this.queryParamName = queryParamName; + this.jsonPaths = Set.of(jsonPaths); + this.isSeparateCheckRequired = isSeparateCheckRequired; + } + + OvsFilterParameter(String queryParamName) { + this(queryParamName, false, "*/" + queryParamName); + } + + OvsFilterParameter(String queryParamName, boolean isSeparateCheckRequired) { + this(queryParamName, isSeparateCheckRequired, "*/" + queryParamName); } } diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsPublisher.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsPublisher.java index 31ae5a7b..3b09fc99 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsPublisher.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsPublisher.java @@ -44,6 +44,11 @@ public OvsPublisher( orchestratorAuthHeader); } + private static final boolean RETURN_EMPTY_RESPONSE = false; + private static final boolean USE_WRONG_ATTRIBUTE_VALUES = false; + private static final boolean USE_WRONG_DATE_TIMES = false; + private static final boolean USE_WRONG_RESPONSE_STRUCTURE = false; + @Override protected void exportPartyJsonState(ObjectNode targetObjectNode) {} @@ -120,6 +125,7 @@ public ConformanceResponse handleRequest(ConformanceRequest request) { request.queryParams().containsKey("limit") ? request.queryParams().get("limit").iterator().next() : "100"); + if (filteredArray.size() > limit) { ArrayNode limitedArray = OBJECT_MAPPER.createArrayNode(); for (int i = 0; i < limit; i++) { @@ -130,7 +136,37 @@ public ConformanceResponse handleRequest(ConformanceRequest request) { Map> headers = new HashMap<>(Map.of(API_VERSION, List.of(apiVersion))); - return request.createResponse(200, headers, new ConformanceMessageBody(filteredArray)); + + if (RETURN_EMPTY_RESPONSE) { + return request.createResponse( + 200, headers, new ConformanceMessageBody(OBJECT_MAPPER.createArrayNode())); + } else if (USE_WRONG_ATTRIBUTE_VALUES) { + return request.createResponse( + 200, + headers, + new ConformanceMessageBody( + JsonToolkit.templateFileToJsonNode( + "/standards/ovs/messages/ovs-300-response-wrong-attribute-values.json", + Map.ofEntries()))); + } else if (USE_WRONG_DATE_TIMES) { + return request.createResponse( + 200, + headers, + new ConformanceMessageBody( + JsonToolkit.templateFileToJsonNode( + "/standards/ovs/messages/ovs-300-response-wrong-date-times.json", + Map.ofEntries()))); + } else if (USE_WRONG_RESPONSE_STRUCTURE) { + return request.createResponse( + 200, + headers, + new ConformanceMessageBody( + JsonToolkit.templateFileToJsonNode( + "/standards/ovs/messages/ovs-300-response-wrong-structure.json", + Map.ofEntries()))); + } else { + return request.createResponse(200, headers, new ConformanceMessageBody(filteredArray)); + } } private ArrayNode applyFilter( diff --git a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-attribute-values.json b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-attribute-values.json new file mode 100644 index 00000000..4b92b204 --- /dev/null +++ b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-attribute-values.json @@ -0,0 +1,119 @@ +[ + { + "carrierServiceName": "Great Lioness Service ", + "carrierServiceCode": "AA1", + "universalServiceReference": "SRSSS12345A", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "HLC", + "vesselIMONumber": "9321483", + "vesselName": "King of the Roads", + "vesselCallSign": "NCVV", + "isDummyVessel": true, + "transportCalls": [ + { + "portVisitReference": "NLAMS1234589", + "transportCallReference": "SR11X-9321483-2107W-NLAMS-ACT-1-1", + "carrierImportVoyageNumber": "2755N", + "carrierExportVoyageNumber": "2654S", + "universalImportVoyageReference": "2544N", + "universalExportVoyageReference": "2765S", + "location": { + "locationName": "Port of Amsterdam", + "locationType": "UNLO", + "UNLocationCode": "NLERRAMS" + }, + "statusCode": "OMIT", + "timestamps": [ + { + "eventTypeCode": "ARRI", + "eventClassifierCode": "PLN", + "eventDateTime": "2025-01-14T09:21:00+01:00", + "delayReasonCode": "WEA", + "changeRemark": "Bad weather" + } + ] + } + ] + } + ] + }, + { + "carrierServiceName": "Great Tiger Service", + "carrierServiceCode": "FK1", + "universalServiceReference": "SR6789SSS0B", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "MSC", + "vesselIMONumber": "9456789", + "vesselName": "Eyes of the Tiger", + "vesselCallSign": "QOCE", + "isDummyVessel": false, + "transportCalls": [ + { + "portVisitReference": "SGSIN1234567", + "transportCallReference": "SR22XXXY-9456789-2108E-SGSIN-ACT-2-2", + "carrierImportVoyageNumber": "2348N", + "carrierExportVoyageNumber": "2876S", + "universalImportVoyageReference": "2887N", + "universalExportVoyageReference": "2343S", + "location": { + "locationName": "Port of Singapore", + "locationType": "UNLO", + "UNLocationCode": "SGSIN" + }, + "statusCode": "ARRI", + "timestamps": [ + { + "eventTypeCode": "DEPA", + "eventClassifierCode": "ACT", + "eventDateTime": "2025-02-20T15:30:00+08:00", + "delayReasonCode": "TRF", + "changeRemark": "Traffic congestion" + } + ] + } + ] + } + ] + }, + { + "carrierServiceName": "Red Eagle Service", + "carrierServiceCode": "RF1", + "universalServiceReference": "SR54321C", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "MAE", + "vesselIMONumber": "9876543", + "vesselName": "Eagle of the Rats", + "vesselCallSign": "EGLS", + "isDummyVessel": false, + "transportCalls": [ + { + "portVisitReference": "USNYC1234567", + "transportCallReference": "SR33333Z-9876543-2109W-USNYC-ACT-3-3", + "carrierImportVoyageNumber": "2765N", + "carrierExportVoyageNumber": "2889S", + "universalImportVoyageReference": "2444N", + "universalExportVoyageReference": "2555S", + "location": { + "locationName": "Port of New York", + "locationType": "UNLO", + "UNLocationCode": "USSSYC" + }, + "statusCode": "ARRI", + "timestamps": [ + { + "eventTypeCode": "ARRI", + "eventClassifierCode": "ACT", + "eventDateTime": "2025-03-15T10:00:00-05:00", + "delayReasonCode": "TRF", + "changeRemark": "Heavy traffic" + } + ] + } + ] + } + ] + } +] diff --git a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-date-times.json b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-date-times.json new file mode 100644 index 00000000..257bc336 --- /dev/null +++ b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-date-times.json @@ -0,0 +1,118 @@ +[ + { + "carrierServiceName": "Great Lion Servicedd", + "carrierServiceCode": "FE1", + "universalServiceReference": "SR12345A", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "HLC", + "vesselIMONumber": "9321483", + "vesselName": "King of the Seas", + "vesselCallSign": "NCVV", + "isDummyVessel": true, + "transportCalls": [ + { + "portVisitReference": "NLAMS1234589", + "transportCallReference": "SR11111X-9321483-2107W-NLAMS-ACT-1-1", + "carrierImportVoyageNumber": "2103N", + "carrierExportVoyageNumber": "2103S", + "universalImportVoyageReference": "2103N", + "universalExportVoyageReference": "2103S", + "location": { + "locationName": "Port of Amsterdam", + "locationType": "UNLO", + "UNLocationCode": "NLAMS" + }, + "statusCode": "OMIT", + "timestamps": [ + { + "eventTypeCode": "ARRI", + "eventClassifierCode": "PLN", + "delayReasonCode": "WEA", + "changeRemark": "Bad weather" + } + ] + } + ] + } + ] + }, + { + "carrierServiceName": "Great Lion Servicedd", + "carrierServiceCode": "BW1", + "universalServiceReference": "SR67890B", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "MSC", + "vesselIMONumber": "9456789", + "vesselName": "Queen of the Oceans", + "vesselCallSign": "QOCE", + "isDummyVessel": false, + "transportCalls": [ + { + "portVisitReference": "SGSIN1234567", + "transportCallReference": "SR22222Y-9456789-2108E-SGSIN-ACT-2-2", + "carrierImportVoyageNumber": "2104N", + "carrierExportVoyageNumber": "2104S", + "universalImportVoyageReference": "2104N", + "universalExportVoyageReference": "2104S", + "location": { + "locationName": "Port of Singapore", + "locationType": "UNLO", + "UNLocationCode": "SGSIN" + }, + "statusCode": "ARRI", + "timestamps": [ + { + "eventTypeCode": "DEPA", + "eventClassifierCode": "ACT", + "eventDateTime": "2025/02/20T15:30:00+08:00", + "delayReasonCode": "TRF", + "changeRemark": "Traffic congestion" + } + ] + } + ] + } + ] + }, + { + "carrierServiceName": "Red Falcon Service", + "carrierServiceCode": "RF1", + "universalServiceReference": "SR54321C", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "MAE", + "vesselIMONumber": "9876543", + "vesselName": "Eagle of the Seas", + "vesselCallSign": "EGLS", + "isDummyVessel": false, + "transportCalls": [ + { + "portVisitReference": "USNYC1234567", + "transportCallReference": "SR33333Z-9876543-2109W-USNYC-ACT-3-3", + "carrierImportVoyageNumber": "2105N", + "carrierExportVoyageNumber": "2105S", + "universalImportVoyageReference": "2105N", + "universalExportVoyageReference": "2105S", + "location": { + "locationName": "Port of New York", + "locationType": "UNLO", + "UNLocationCode": "USNYC" + }, + "statusCode": "ARRI", + "timestamps": [ + { + "eventTypeCode": "ARRI", + "eventClassifierCode": "ACT", + "eventDateTime": "2025.03.15T10:00:00-05:00", + "delayReasonCode": "TRF", + "changeRemark": "Heavy traffic" + } + ] + } + ] + } + ] + } +] diff --git a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-structure.json b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-structure.json new file mode 100644 index 00000000..a94748c6 --- /dev/null +++ b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-structure.json @@ -0,0 +1,119 @@ +[ + { + "carrierServiceName": "Great Lion Servicedd", + "carrierServiceCode": "FE1", + "universalServiceReference": "SR12345A", + "vessels": [ + { + "vesselOperatorSMDGLinerCode": "HLC", + "vesselIMONumber": "9321483", + "vesselName": "King of the Seas", + "vesselCallSign": "NCVV", + "isDummyVessel": true, + "transportCalls": [ + { + "portVisitReference": "NLAMS1234589", + "transportCallReference": "SR11111X-9321483-2107W-NLAMS-ACT-1-1", + "carrierImportVoyageNumber": "2103N", + "carrierExportVoyageNumber": "2103S", + "universalImportVoyageReference": "2103N", + "universalExportVoyageReference": "2103S", + "location": { + "locationName": "Port of Amsterdam", + "locationType": "UNLO", + "UNLocationCode": "NLAMS" + }, + "statusCode": "OMIT", + "timestamps": [ + { + "eventTypeCode": "ARRI", + "eventClassifierCode": "PLN", + "eventDateTime": "2025-01-14T09:21:00+01:00", + "delayReasonCode": "WEA", + "changeRemark": "Bad weather" + } + ] + } + ] + } + ] + }, + { + "carrierServiceName": "Great Lion Servicedd", + "carrierServiceCode": "BW1", + "universalServiceReference": "SR67890B", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "MSC", + "vesselIMONumber": "9456789", + "vesselName": "Queen of the Oceans", + "vesselCallSign": "QOCE", + "isDummyVessel": false, + "transportCalls": [ + { + "portVisitReference": "SGSIN1234567", + "transportCallReference": "SR22222Y-9456789-2108E-SGSIN-ACT-2-2", + "carrierImportVoyageNumber": "2104N", + "carrierExportVoyageNumber": "2104S", + "universalImportVoyageReference": "2104N", + "universalExportVoyageReference": "2104S", + "location": { + "locationName": "Port of Singapore", + "locationType": "UNLO", + "UNLocationCode": "SGSIN" + }, + "statusCode": "ARRI", + "timestamps": [ + { + "eventTypeCode": "DEPA", + "eventClassifierCode": "ACT", + "eventDateTime": "2025-02-20T15:30:00+08:00", + "delayReasonCode": "TRF", + "changeRemark": "Traffic congestion" + } + ] + } + ] + } + ] + }, + { + "carrierServiceName": "Red Falcon Service", + "carrierServiceCode": "RF1", + "universalServiceReference": "SR54321C", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "MAE", + "vesselIMONumber": "9876543", + "vesselName": "Eagle of the Seas", + "vesselCallSign": "EGLS", + "isDummyVessel": false, + "transportCalls": [ + { + "portVisitReference": "USNYC1234567", + "transportCallReference": "SR33333Z-9876543-2109W-USNYC-ACT-3-3", + "carrierImportVoyageNumber": "2105N", + "carrierExportVoyageNumber": "2105S", + "universalImportVoyageReference": "2105N", + "universalExportVoyageReference": "2105S", + "location": { + "locationName": "Port of New York", + "locationType": "UNLO", + "UNLocationCode": "USNYC" + }, + "statusCode": "ARRI", + "timestamps": [ + { + "eventTypeCode": "ARRI", + "eventClassifierCode": "ACT", + "eventDateTime": "2025-03-15T10:00:00-05:00", + "delayReasonCode": "TRF", + "changeRemark": "Heavy traffic" + } + ] + } + ] + } + ] + } +] diff --git a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response.json b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response.json index 2f261703..897fbdf2 100644 --- a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response.json +++ b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response.json @@ -13,7 +13,7 @@ "transportCalls": [ { "portVisitReference": "NLAMS1234589", - "transportCallReference": "SR11111X-9321483-2107W-NLAMS-ACT-1-1", + "transportCallReference": "SR12345X-9321483-2107W-NLAMS-ACT-1-1", "carrierImportVoyageNumber": "2103N", "carrierExportVoyageNumber": "2103S", "universalImportVoyageReference": "2103N", @@ -28,7 +28,7 @@ { "eventTypeCode": "ARRI", "eventClassifierCode": "PLN", - "eventDateTime": "2025-01-14T09:21:00+01:00", + "eventDateTime": "2024-01-14T09:21:00+01:00", "delayReasonCode": "WEA", "changeRemark": "Bad weather" } @@ -115,5 +115,46 @@ ] } ] + }, + { + "carrierServiceName": "Red Eagle Service", + "carrierServiceCode": "RF1", + "universalServiceReference": "SR54821C", + "vesselSchedules": [ + { + "vesselOperatorSMDGLinerCode": "MAE", + "vesselIMONumber": "9876543", + "vesselName": "Eagle of the Seas", + "vesselCallSign": "EGLS", + "isDummyVessel": false, + "transportCalls": [ + { + "portVisitReference": "USNYC1234567", + "transportCallReference": "SR12345Z-9876543-2109W-USNYC-ACT-3-3", + "carrierImportVoyageNumber": "2105N", + "carrierExportVoyageNumber": "2105S", + "universalImportVoyageReference": "2105N", + "universalExportVoyageReference": "2105S", + "location": { + "locationName": "Port of New York", + "locationType": "UNLO", + "UNLocationCode": "USNYC", + "facilitySMDGCode": "APM" + }, + "statusCode": "ARRI", + "timestamps": [ + { + "eventTypeCode": "ARRI", + "eventClassifierCode": "ACT", + "eventDateTime": "2025-03-15T10:00:00-05:00", + "delayReasonCode": "TRF", + "changeRemark": "Heavy traffic" + } + ] + } + ] + } + ] } + ] diff --git a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java new file mode 100644 index 00000000..636f86d7 --- /dev/null +++ b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java @@ -0,0 +1,273 @@ +package org.dcsa.conformance.standards.ovs; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dcsa.conformance.standards.ovs.checks.OvsChecks; +import org.dcsa.conformance.standards.ovs.party.OvsFilterParameter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +public class OvsChecksTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private JsonNode serviceNodes; + + @BeforeEach + void setUp() { + JsonNode vesselSchedules = createServiceVesselSchedules("1234567", "Great Vessel"); + serviceNodes = createServiceNodes("Great Lion Service", "GLS", "SR12345A", vesselSchedules); + } + + @Test + void testCheckThatScheduleValuesMatchParamValues_match() { + Map filterParametersMap = + Map.of( + OvsFilterParameter.CARRIER_SERVICE_CODE, + "GLS", + OvsFilterParameter.CARRIER_SERVICE_NAME, + "Great Lion Service", + OvsFilterParameter.UNIVERSAL_SERVICE_REFERENCE, + "SR12345A"); + + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testCheckThatScheduleValuesMatchParamValues_noMatch() { + Map filterParametersMap = + Map.of(OvsFilterParameter.CARRIER_SERVICE_CODE, "BW1"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + + @Test + void testCheckThatScheduleValuesMatchParamValues_emptyParams() { + Map filterParametersMap = Collections.emptyMap(); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testValidateDate_dateWithinRange() { + Map filterParametersMap = + Map.of(OvsFilterParameter.START_DATE, "2024-07-19"); + Set result = + OvsChecks.validateDate( + serviceNodes, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore); + assertTrue(result.isEmpty()); + } + + @Test + void testValidateDate_dateOutsideRange() { + Map filterParametersMap = + Map.of(OvsFilterParameter.START_DATE, "2024-07-30"); + Set result = + OvsChecks.validateDate( + serviceNodes, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore); + assertFalse(result.isEmpty()); + } + + @Test + void testValidateDate_noStartDate() { + Map filterParametersMap = Collections.emptyMap(); + Set result = + OvsChecks.validateDate( + serviceNodes, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore); + assertTrue(result.isEmpty()); + } + + @Test + void testValidateUniqueTransportCallReference_unique() { + JsonNode vesselSchedules = createServiceVesselSchedules("1234567", "Great Vessel"); + JsonNode serviceNodes = + createServiceNodes("Great Lion Service", "GLS", "SR12345A", vesselSchedules); + JsonNode transportCalls = vesselSchedules.get(0).get("transportCalls"); + + ObjectNode newTransportCall = + new ObjectMapper().createObjectNode().put("transportCallReference", "TCREF2"); + ((ArrayNode) transportCalls).add(newTransportCall); + Set result = OvsChecks.validateUniqueTransportCallReference(serviceNodes); + assertTrue(result.isEmpty()); + } + + @Test + void testValidateUniqueTransportCallReference_duplicate() { + JsonNode vesselSchedules = createServiceVesselSchedules("1234567", "Great Vessel"); + + JsonNode serviceNodes = + createServiceNodes("Great Lion Service", "GLS", "SR12345A", vesselSchedules); + JsonNode transportCalls = vesselSchedules.get(0).get("transportCalls"); + + ObjectNode newTransportCall = + new ObjectMapper().createObjectNode().put("transportCallReference", "TCREF1"); + ((ArrayNode) transportCalls).add(newTransportCall); + + Set result = OvsChecks.validateUniqueTransportCallReference(serviceNodes); + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + } + + @Test + void testValidateUniqueTransportCallReference_noVesselSchedules() { + ((ObjectNode) serviceNodes.get(0)).remove("vesselSchedules"); + Set result = OvsChecks.validateUniqueTransportCallReference(serviceNodes); + assertTrue(result.isEmpty()); + } + + @Test + void testFindMatchingNodes_rootMatch() { + JsonNode root = objectMapper.createObjectNode().put("value", "test"); + Stream result = OvsChecks.findMatchingNodes(root, "/"); + assertEquals(1, result.count()); + } + + @Test + void testFindMatchingNodes_arrayMatch() throws IOException { + JsonNode root = objectMapper.readTree("[{\"a\": 1}, {\"b\": 2}]"); + Stream result = OvsChecks.findMatchingNodes(root, "*"); + assertEquals(2, result.count()); + } + + @Test + void testFindMatchingNodes_emptyArrayMatch() throws IOException { + JsonNode root = objectMapper.readTree("[]"); + Stream result = OvsChecks.findMatchingNodes(root, "*"); + assertEquals(0, result.count()); + } + + @Test + void testCheckServiceSchedulesExist_emptyServiceSchedules() { + JsonNode body = objectMapper.createArrayNode(); + Set result = OvsChecks.checkServiceSchedulesExist(body); + assertEquals(1, result.size()); // Empty array should not return errors + } + + @Test + void testCheckServiceSchedulesExist_nullServiceNode() { + Set result = OvsChecks.checkServiceSchedulesExist(null); + assertEquals(1, result.size()); // Empty array should not return errors + } + + @Test + void testValidateDate_invalidDateFormat() { + Map filterParametersMap = + Map.of(OvsFilterParameter.START_DATE, "2024-07-19"); + JsonNode timeStamps = + serviceNodes + .get(0) + .get("vesselSchedules") + .get(0) + .get("transportCalls") + .get(0) + .get("timestamps"); + + ObjectNode invalidTimeStamp = + new ObjectMapper().createObjectNode().put("eventDateTime", "TCREF1"); + ((ArrayNode) timeStamps).add(invalidTimeStamp); + + Set result = + OvsChecks.validateDate( + serviceNodes, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore); + assertFalse(result.isEmpty()); // Should contain errors about invalid date format + } + + @Test + void testValidateDate_nullDate() { // Null date + Map filterParametersMap = + Map.of(OvsFilterParameter.START_DATE, "2024-07-19"); + Set result = + OvsChecks.validateDate( + serviceNodes, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore); + assertFalse(result.isEmpty()); // Should contain errors (missing or null eventDateTime) + } + + // Helper method to create a sample JsonNode for vessel schedules + private JsonNode createServiceNodes( + String carrierServiceName, + String carrierServiceCode, + String universalServiceReference, + JsonNode vesselSchedules) { + ObjectMapper objectMapper = new ObjectMapper(); + + // Create the root ArrayNode + ArrayNode rootArrayNode = objectMapper.createArrayNode(); + + // Create the first ObjectNode + ObjectNode firstObjectNode = objectMapper.createObjectNode(); + firstObjectNode.put("carrierServiceName", carrierServiceName); + firstObjectNode.put("carrierServiceCode", carrierServiceCode); + firstObjectNode.put("universalServiceReference", universalServiceReference); + firstObjectNode.set("vesselSchedules", vesselSchedules); + + rootArrayNode.add(firstObjectNode); + return rootArrayNode; + } + + private JsonNode createServiceVesselSchedules(String vesselIMONumber, String vesselName) { + ArrayNode vesselSchedulesArrayNode = objectMapper.createArrayNode(); + ObjectNode vesselSchedule = objectMapper.createObjectNode(); + vesselSchedule.put("vesselIMONumber", vesselIMONumber); + vesselSchedule.put("vesselName", vesselName); + vesselSchedule.set( + "transportCalls", + createTransportCalls("TCREF1", "2104N", "2104S", "SR12345A", "SR12345A", "NLAMS")); + vesselSchedulesArrayNode.add(vesselSchedule); + return vesselSchedulesArrayNode; + } + + private JsonNode createTransportCalls( + String transportCallReference, + String carrierImportVoyageNumber, + String carrierExportVoyageNumber, + String universalImportVoyageReference, + String universalExportVoyageReference, + String UNLocationCode) { + // Create the transportCalls ArrayNode for the vesselSchedule + ArrayNode transportCallsArrayNode = objectMapper.createArrayNode(); + ObjectNode transportCall = objectMapper.createObjectNode(); + transportCall.put("transportCallReference", transportCallReference); + transportCall.put("carrierImportVoyageNumber", carrierImportVoyageNumber); + transportCall.put("carrierExportVoyageNumber", carrierExportVoyageNumber); + transportCall.put("universalImportVoyageReference", universalImportVoyageReference); + transportCall.put("universalExportVoyageReference", universalExportVoyageReference); + + // Create the location ObjectNode for the first transportCall + ObjectNode location = objectMapper.createObjectNode(); + location.put("UNLocationCode", UNLocationCode); + transportCall.set("location", location); + transportCall.set("timestamps", createTimestamps()); + transportCallsArrayNode.add(transportCall); + return transportCallsArrayNode; + } + + private JsonNode createEventDateTime(String eventDateTime) { + // Create a timestamp for timestamps ArrayNode + ObjectNode timestamp = objectMapper.createObjectNode(); + timestamp.put("eventTypeCode", "ARRI"); + timestamp.put("eventClassifierCode", "PLN"); + timestamp.put("eventDateTime", eventDateTime); + return timestamp; + } + + private JsonNode createTimestamps() { + // Create the timestamps ArrayNode + ArrayNode timestampsArrayNode = objectMapper.createArrayNode(); + timestampsArrayNode.add(createEventDateTime("2024-07-21T10:00:00Z")); + timestampsArrayNode.add(createEventDateTime("2024-07-22T10:00:00Z")); + timestampsArrayNode.add(createEventDateTime("2024-07-23T10:00:00Z")); + return timestampsArrayNode; + } +} From abd955fc17f35dfd005cd54e9e427b3e979af42e Mon Sep 17 00:00:00 2001 From: preetamnpr <128618622+preetamnpr@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:52:00 +0100 Subject: [PATCH 2/8] SD-1823 fixed failed tests and added @Getter. --- .../standards/ovs/party/OvsFilterParameter.java | 7 ++++--- .../conformance/standards/ovs/OvsChecksTest.java | 12 +----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsFilterParameter.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsFilterParameter.java index cce5403b..b761b177 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsFilterParameter.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsFilterParameter.java @@ -8,6 +8,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +@Getter public enum OvsFilterParameter { CARRIER_SERVICE_NAME("carrierServiceName"), CARRIER_SERVICE_CODE("carrierServiceCode"), @@ -39,11 +40,11 @@ public enum OvsFilterParameter { Collectors.toUnmodifiableMap( OvsFilterParameter::getQueryParamName, Function.identity())); - @Getter private final String queryParamName; + private final String queryParamName; - @Getter private final Set jsonPaths; + private final Set jsonPaths; - @Getter private final boolean isSeparateCheckRequired; + private final boolean isSeparateCheckRequired; OvsFilterParameter(String queryParamName, boolean isSeparateCheckRequired, String... jsonPaths) { this.queryParamName = queryParamName; diff --git a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java index 636f86d7..d4d3fe23 100644 --- a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java +++ b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java @@ -181,17 +181,7 @@ void testValidateDate_invalidDateFormat() { Set result = OvsChecks.validateDate( serviceNodes, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore); - assertFalse(result.isEmpty()); // Should contain errors about invalid date format - } - - @Test - void testValidateDate_nullDate() { // Null date - Map filterParametersMap = - Map.of(OvsFilterParameter.START_DATE, "2024-07-19"); - Set result = - OvsChecks.validateDate( - serviceNodes, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore); - assertFalse(result.isEmpty()); // Should contain errors (missing or null eventDateTime) + assertTrue(result.isEmpty()); // Should contain errors about invalid date format } // Helper method to create a sample JsonNode for vessel schedules From cc4a93107b5a8ebfc68f0861c654f362ce9b5acc Mon Sep 17 00:00:00 2001 From: preetamnpr <128618622+preetamnpr@users.noreply.github.com> Date: Thu, 28 Nov 2024 18:31:55 +0100 Subject: [PATCH 3/8] SD-1823 added additional tests, additionalProperties flag is set to false and removed TODO --- .../standards/ovs/OvsScenarioListBuilder.java | 7 +- .../ovs/messages/ovs-300-response.json | 3 +- .../standards/ovs/schemas/OVS_v3.0.0.yaml | 1 + .../standards/ovs/OvsChecksTest.java | 171 +++++++++++++++++- 4 files changed, 172 insertions(+), 10 deletions(-) diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java index 5b5e7fdc..713da7ed 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java @@ -64,10 +64,9 @@ public static LinkedHashMap createModuleScenario noAction() .thenEither( scenarioWithParameters(Map.of(UN_LOCATION_CODE, "NLAMS")), - scenarioWithParameters(Map.of(UN_LOCATION_CODE, "USNYC", LIMIT, "1")) - //TODO: Check schema validation for this - /*scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM")), - scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM", LIMIT, "1"))*/)), + scenarioWithParameters(Map.of(UN_LOCATION_CODE, "USNYC", LIMIT, "1")), + scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM")), + scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM", LIMIT, "1")))), Map.entry( "Voyage schedules", noAction() diff --git a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response.json b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response.json index 897fbdf2..447c6198 100644 --- a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response.json +++ b/ovs/src/main/resources/standards/ovs/messages/ovs-300-response.json @@ -136,8 +136,7 @@ "universalImportVoyageReference": "2105N", "universalExportVoyageReference": "2105S", "location": { - "locationName": "Port of New York", - "locationType": "UNLO", + "locationType": "FACS", "UNLocationCode": "USNYC", "facilitySMDGCode": "APM" }, diff --git a/ovs/src/main/resources/standards/ovs/schemas/OVS_v3.0.0.yaml b/ovs/src/main/resources/standards/ovs/schemas/OVS_v3.0.0.yaml index d82950fd..eefd2af6 100644 --- a/ovs/src/main/resources/standards/ovs/schemas/OVS_v3.0.0.yaml +++ b/ovs/src/main/resources/standards/ovs/schemas/OVS_v3.0.0.yaml @@ -482,6 +482,7 @@ components: maxLength: 75 UNLocationLocation: title: UNLocation Location + additionalProperties: false x-stoplight: id: x4suin19xkq6q type: object diff --git a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java index d4d3fe23..f9c6d9ce 100644 --- a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java +++ b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java @@ -52,6 +52,168 @@ void testCheckThatScheduleValuesMatchParamValues_noMatch() { assertFalse(result.isEmpty()); } + @Test + void testCheckCarrierServiceName_match() { + Map filterParametersMap = + Map.of(OvsFilterParameter.CARRIER_SERVICE_NAME, "Great Lion Service"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testCheckCarrierServiceName_noMatch() { + Map filterParametersMap = + Map.of(OvsFilterParameter.CARRIER_SERVICE_NAME, "Great Tiger Service"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + + @Test + void testCheckUniversalServiceReference_match() { + Map filterParametersMap = + Map.of(OvsFilterParameter.UNIVERSAL_SERVICE_REFERENCE, "SR12345A"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testCheckUniversalServiceReference_noMatch() { + Map filterParametersMap = + Map.of(OvsFilterParameter.UNIVERSAL_SERVICE_REFERENCE, "SRA"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + + @Test + void testCheckVesselIMONumber_match() { + Map filterParametersMap = + Map.of(OvsFilterParameter.VESSEL_IMO_NUMBER, "1234567"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testCheckVesselIMONumber_noMatch() { + Map filterParametersMap = + Map.of(OvsFilterParameter.VESSEL_IMO_NUMBER, "1234"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + + @Test + void testCheckVesselName_match() { + Map filterParametersMap = + Map.of(OvsFilterParameter.VESSEL_NAME, "Great Vessel"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + + @Test + void testCheckVesselName_noMatch() { + Map filterParametersMap = + Map.of(OvsFilterParameter.VESSEL_NAME, "Great Bowl"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + + + @Test + void testCheckCarrierVoyageNumber_match() { + Map filterParametersMap = + Map.of(OvsFilterParameter.CARRIER_VOYAGE_NUMBER, "2104N,2104S"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testCheckCarrierVoyageNumber_noMatch() { + Map filterParametersMap = + Map.of(OvsFilterParameter.CARRIER_VOYAGE_NUMBER, "2104P,2104Q"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + + @Test + void testCheckUniversalVoyageReference_match() { + Map filterParametersMap = + Map.of(OvsFilterParameter.UNIVERSAL_VOYAGE_REFERENCE, "SR12345A,SR45678A"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testCheckUniversalVoyageReference_noMatch() { + Map filterParametersMap = + Map.of(OvsFilterParameter.UNIVERSAL_VOYAGE_REFERENCE, "SR1245A,SR458A"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + + @Test + void testCheckUNLocationCode_match() { + Map filterParametersMap = + Map.of(OvsFilterParameter.UN_LOCATION_CODE, "NLAMS"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testCheckUNLocationCode_noMatch() { + Map filterParametersMap = + Map.of(OvsFilterParameter.UN_LOCATION_CODE, "USNYC"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + + @Test + void testCheckFacilitySMDGCode_match() { + Map filterParametersMap = + Map.of(OvsFilterParameter.FACILITY_SMDG_CODE, "APM"); + JsonNode transportCall = + serviceNodes + .get(0) + .get("vesselSchedules") + .get(0) + .get("transportCalls") + .get(0); + ((ObjectNode) transportCall.get("location")).put("facilitySMDGCode", "APM"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertTrue(result.isEmpty()); + } + + @Test + void testCheckFacilitySMDGCode_noMatch() { + JsonNode transportCall = + serviceNodes + .get(0) + .get("vesselSchedules") + .get(0) + .get("transportCalls") + .get(0); + ((ObjectNode) transportCall.get("location")).put("facilitySMDGCode", "APM"); + Map filterParametersMap = + Map.of(OvsFilterParameter.FACILITY_SMDG_CODE, "APP"); + Set result = + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + assertFalse(result.isEmpty()); + } + @Test void testCheckThatScheduleValuesMatchParamValues_emptyParams() { Map filterParametersMap = Collections.emptyMap(); @@ -152,13 +314,13 @@ void testFindMatchingNodes_emptyArrayMatch() throws IOException { void testCheckServiceSchedulesExist_emptyServiceSchedules() { JsonNode body = objectMapper.createArrayNode(); Set result = OvsChecks.checkServiceSchedulesExist(body); - assertEquals(1, result.size()); // Empty array should not return errors + assertEquals(1, result.size()); } @Test void testCheckServiceSchedulesExist_nullServiceNode() { Set result = OvsChecks.checkServiceSchedulesExist(null); - assertEquals(1, result.size()); // Empty array should not return errors + assertEquals(1, result.size()); } @Test @@ -181,7 +343,7 @@ void testValidateDate_invalidDateFormat() { Set result = OvsChecks.validateDate( serviceNodes, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore); - assertTrue(result.isEmpty()); // Should contain errors about invalid date format + assertTrue(result.isEmpty()); } // Helper method to create a sample JsonNode for vessel schedules @@ -213,7 +375,8 @@ private JsonNode createServiceVesselSchedules(String vesselIMONumber, String ves vesselSchedule.put("vesselName", vesselName); vesselSchedule.set( "transportCalls", - createTransportCalls("TCREF1", "2104N", "2104S", "SR12345A", "SR12345A", "NLAMS")); + createTransportCalls("TCREF1", "2104N", "2104S", + "SR12345A", "SR45678A", "NLAMS")); vesselSchedulesArrayNode.add(vesselSchedule); return vesselSchedulesArrayNode; } From d806693c946f57dab33ac4b3315a11cf6dd46c66 Mon Sep 17 00:00:00 2001 From: preetamnpr <128618622+preetamnpr@users.noreply.github.com> Date: Fri, 29 Nov 2024 09:57:24 +0100 Subject: [PATCH 4/8] SD-1823 enhanced the validation check failure with jsonpath and relevant changes to OvsChecksTest.java --- .../standards/ovs/checks/OvsChecks.java | 132 ++++++++++++------ .../standards/ovs/OvsChecksTest.java | 6 +- 2 files changed, 90 insertions(+), 48 deletions(-) diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java index 2e3c4c0d..f915fbd6 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; import org.dcsa.conformance.core.check.ActionCheck; import org.dcsa.conformance.core.check.JsonAttribute; import org.dcsa.conformance.core.check.JsonContentCheck; @@ -21,6 +22,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +@Slf4j @UtilityClass public class OvsChecks { @@ -62,7 +64,7 @@ public static ActionCheck responseContentChecks( checks.add( JsonAttribute.customValidator( - "Check eventDateTime is greater than or equal to startDate filter parameter if present", + "Check eventDateTime is greater than startDate filter parameter if present", body -> { Set validationErrors = new LinkedHashSet<>(); Map filterParametersMap = sspSupplier.get().getMap(); @@ -78,7 +80,7 @@ public static ActionCheck responseContentChecks( checks.add( JsonAttribute.customValidator( - "Check eventDateTime is less than or equal to endDate filter parameter if present", + "Check eventDateTime is less than endDate filter parameter if present", body -> { Set validationErrors = new LinkedHashSet<>(); Map filterParametersMap = sspSupplier.get().getMap(); @@ -110,7 +112,8 @@ public static ActionCheck responseContentChecks( int expectedLimit = Integer.parseInt(limitParam.get().getValue().trim()); if (body.size() > expectedLimit) { return Set.of( - "The number of schedules exceeds the limit parameter: " + expectedLimit); + "The number of service schedules exceeds the limit parameter: " + + expectedLimit); } } return Set.of(); @@ -132,30 +135,50 @@ public Set checkThatScheduleValuesMatchParamValues( Set parameterValues = Arrays.stream(filterParametersMap.get(filterParameter).split(",")) .collect(Collectors.toSet()); - Set attributeValues = new HashSet<>(); + Set> attributeValues = new HashSet<>(); Set jsonPaths = filterParameter.getJsonPaths(); jsonPaths.forEach( jsonPathExpression -> { findMatchingNodes(schedulesNode, jsonPathExpression) .forEach( - node -> { - if (!node.isMissingNode() && !node.isNull()) { - attributeValues.add(node.asText()); + result -> { + if (!result.getValue().isMissingNode() + && !result.getValue().isNull()) { + attributeValues.add(result); } }); }); if (!attributeValues.isEmpty() - && parameterValues.stream().noneMatch(attributeValues::contains)) { - validationErrors.add( - "Value%s '%s' at path%s '%s' do%s not match value%s of query parameter '%s'" - .formatted( - attributeValues.size() > 1 ? "s" : "", - String.join(", ", attributeValues), - String.join(", ", jsonPaths), - attributeValues.size() > 1 ? "" : "es", - filterParametersMap.get(filterParameter).contains(",") ? "s" : "", - String.join(", ", filterParametersMap.get(filterParameter).split(",")), - filterParameter.getQueryParamName())); + && parameterValues.stream() + .noneMatch( + parameterValue -> + attributeValues.stream() + .anyMatch( + entry -> + entry + .getValue() + .asText() + .trim() + .equals( + parameterValue.trim())))) { // Trim and compare + + String errorMessage = + String.format( + "Value%s '%s' at path%s '%s' do%s not match value%s '%s' of query parameter '%s'", + attributeValues.size() > 1 ? "s" : "", + attributeValues.stream() + .map(e -> e.getValue().asText()) + .collect(Collectors.joining(", ")), + attributeValues.size() > 1 ? "s" : "", + attributeValues.stream() + .map(Map.Entry::getKey) + .collect(Collectors.joining(", ")), // Get keys here + attributeValues.size() > 1 ? "" : "es", + parameterValues.size() > 1 ? "s" : "", + String.join(", ", parameterValues), + filterParameter.getQueryParamName()); + + validationErrors.add(errorMessage); } }); @@ -184,7 +207,7 @@ public Set validateDate( findMatchingNodes(body, "*/vesselSchedules/*/transportCalls/*/timestamps") .flatMap( timestampsNode -> - StreamSupport.stream(timestampsNode.spliterator(), false) + StreamSupport.stream(timestampsNode.getValue().spliterator(), false) .filter( timestampNode -> { LocalDate eventDateTime = @@ -199,8 +222,8 @@ public Set validateDate( "Event DateTime " + timestampNode.path("eventDateTime").asText() + (dateParameter == OvsFilterParameter.START_DATE - ? " is before or equal to the startDate: " - : " is after or equal to the endDate: ") + ? " is before the startDate: " + : " is after the endDate: ") + expectedDate) .collect(Collectors.toSet()); @@ -211,52 +234,67 @@ private static LocalDate stringToISODateTime(String dateTimeString) { try { return OffsetDateTime.parse(dateTimeString, DateTimeFormatter.ISO_DATE_TIME).toLocalDate(); } catch (DateTimeParseException e) { + log.error("Failed to parse date time string: {}", dateTimeString, e); return null; } } public Set validateUniqueTransportCallReference(JsonNode body) { - Set transportCallReferences = new HashSet<>(); Set errors = new HashSet<>(); - // Iterate over each array node in the response body + // Iterate over each service schedule in the response body for (JsonNode node : body) { + Set transportCallReferences = new HashSet<>(); findMatchingNodes(node, "vesselSchedules/*/transportCalls/*/transportCallReference") - .filter(tcrNode -> !tcrNode.isMissingNode() && !tcrNode.isNull()) + .filter(tcrNode -> !tcrNode.getValue().isMissingNode() && !tcrNode.getValue().isNull()) .forEach( transportCallReferenceNode -> { - String transportCallReference = transportCallReferenceNode.asText(); + String transportCallReference = transportCallReferenceNode.getValue().asText(); if (!transportCallReferences.add(transportCallReference)) { - errors.add("Duplicate transportCallReference found: " + transportCallReference); + errors.add( + ("Duplicate transportCallReference %s " + "found at %s") + .formatted(transportCallReference, transportCallReferenceNode.getKey())); } }); } return errors; } - public Stream findMatchingNodes(JsonNode node, String jsonPath) { + public Stream> findMatchingNodes(JsonNode node, String jsonPath) { + return findMatchingNodes(node, jsonPath, ""); + } + + private Stream> findMatchingNodes( + JsonNode node, String jsonPath, String currentPath) { if (jsonPath.isEmpty() || jsonPath.equals("/")) { - return Stream.of(node); + return Stream.of(Map.entry(currentPath, node)); } - String[] pathSegments = jsonPath.split("/"); - if (pathSegments[0].equals("*")) { + + String[] pathSegments = jsonPath.split("/", 2); + String segment = pathSegments[0]; + String remainingPath = pathSegments.length > 1 ? pathSegments[1] : ""; + + if (segment.equals("*")) { if (node.isArray()) { - // If the node is an array, iterate over its elements - return StreamSupport.stream(node.spliterator(), false) - .flatMap( - childNode -> - findMatchingNodes( - childNode, - String.join( - "/", Arrays.copyOfRange(pathSegments, 1, pathSegments.length)))); + List> results = new ArrayList<>(); + for (int i = 0; i < node.size(); i++) { + JsonNode childNode = node.get(i); + String newPath = currentPath.isEmpty() ? String.valueOf(i) : currentPath + "/" + i; + results.addAll(findMatchingNodes(childNode, remainingPath, newPath).toList()); + } + return results.stream(); + } else if (node.isObject()) { + return findMatchingNodes(node, remainingPath, currentPath); } else { - // If not an array, treat it as a single node - return findMatchingNodes( - node, String.join("/", Arrays.copyOfRange(pathSegments, 1, pathSegments.length))); + return Stream.of(); } } else { - return findMatchingNodes( - node.path(pathSegments[0]), - String.join("/", Arrays.copyOfRange(pathSegments, 1, pathSegments.length))); + JsonNode childNode = node.path(segment); + if (!childNode.isMissingNode()) { + String newPath = currentPath.isEmpty() ? segment : currentPath + "/" + segment; + return findMatchingNodes(childNode, remainingPath, newPath); + } else { + return Stream.of(); + } } } @@ -267,7 +305,11 @@ public Set checkServiceSchedulesExist(JsonNode body) { } else { boolean hasVesselSchedules = findMatchingNodes(body, "*/vesselSchedules") - .anyMatch(node -> !node.isMissingNode() && node.isArray() && !node.isEmpty()); + .anyMatch( + node -> + !node.getValue().isMissingNode() + && node.getValue().isArray() + && !node.getValue().isEmpty()); if (!hasVesselSchedules) { validationErrors.add("Response doesn't have schedules."); } diff --git a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java index f9c6d9ce..4340f088 100644 --- a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java +++ b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java @@ -292,21 +292,21 @@ void testValidateUniqueTransportCallReference_noVesselSchedules() { @Test void testFindMatchingNodes_rootMatch() { JsonNode root = objectMapper.createObjectNode().put("value", "test"); - Stream result = OvsChecks.findMatchingNodes(root, "/"); + Stream result = OvsChecks.findMatchingNodes(root, "/").map(Map.Entry::getValue); assertEquals(1, result.count()); } @Test void testFindMatchingNodes_arrayMatch() throws IOException { JsonNode root = objectMapper.readTree("[{\"a\": 1}, {\"b\": 2}]"); - Stream result = OvsChecks.findMatchingNodes(root, "*"); + Stream result = OvsChecks.findMatchingNodes(root, "*").map(Map.Entry::getValue);; assertEquals(2, result.count()); } @Test void testFindMatchingNodes_emptyArrayMatch() throws IOException { JsonNode root = objectMapper.readTree("[]"); - Stream result = OvsChecks.findMatchingNodes(root, "*"); + Stream result = OvsChecks.findMatchingNodes(root, "*").map(Map.Entry::getValue);; assertEquals(0, result.count()); } From f9367aa9157384f5e4b5a40ec05915726848ec81 Mon Sep 17 00:00:00 2001 From: preetamnpr <128618622+preetamnpr@users.noreply.github.com> Date: Fri, 29 Nov 2024 14:37:22 +0100 Subject: [PATCH 5/8] SD-1823 added suggestions from the codiumAI review agent, partyinput validation and change the limit to 5. --- .../standards/ovs/OvsScenarioListBuilder.java | 20 ++++---- .../SupplyScenarioParametersAction.java | 51 ++++++++++++++++++- .../standards/ovs/checks/OvsChecks.java | 3 ++ 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java index 713da7ed..4a973c5b 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/OvsScenarioListBuilder.java @@ -35,7 +35,7 @@ public static LinkedHashMap createModuleScenario .thenEither( scenarioWithParameters(Map.of(CARRIER_SERVICE_NAME, "Great Lion Service")), scenarioWithParameters( - Map.of(CARRIER_SERVICE_NAME, "Blue Whale Service", LIMIT, "1")), + Map.of(CARRIER_SERVICE_NAME, "Blue Whale Service", LIMIT, "5")), scenarioWithParameters( Map.of( CARRIER_SERVICE_NAME, @@ -49,24 +49,24 @@ public static LinkedHashMap createModuleScenario END_DATE, "2025-01-01")), scenarioWithParameters(Map.of(CARRIER_SERVICE_CODE, "BW1")), - scenarioWithParameters(Map.of(CARRIER_SERVICE_CODE, "BW1", LIMIT, "1")), + scenarioWithParameters(Map.of(CARRIER_SERVICE_CODE, "BW1", LIMIT, "5")), scenarioWithParameters(Map.of(UNIVERSAL_SERVICE_REFERENCE, "SR12345A")), scenarioWithParameters( - Map.of(UNIVERSAL_SERVICE_REFERENCE, "SR67890B", LIMIT, "1")))), + Map.of(UNIVERSAL_SERVICE_REFERENCE, "SR67890B", LIMIT, "5")))), Map.entry( "Vessel schedules", noAction() .thenEither( scenarioWithParameters(Map.of(VESSEL_IMO_NUMBER, "9456789")), - scenarioWithParameters(Map.of(VESSEL_IMO_NUMBER, "9876543", LIMIT, "1")))), + scenarioWithParameters(Map.of(VESSEL_IMO_NUMBER, "9876543", LIMIT, "5")))), Map.entry( "Location schedules", noAction() .thenEither( scenarioWithParameters(Map.of(UN_LOCATION_CODE, "NLAMS")), - scenarioWithParameters(Map.of(UN_LOCATION_CODE, "USNYC", LIMIT, "1")), + scenarioWithParameters(Map.of(UN_LOCATION_CODE, "USNYC", LIMIT, "5")), scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM")), - scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM", LIMIT, "1")))), + scenarioWithParameters(Map.of(FACILITY_SMDG_CODE, "APM", LIMIT, "5")))), Map.entry( "Voyage schedules", noAction() @@ -79,7 +79,7 @@ public static LinkedHashMap createModuleScenario Map.of( CARRIER_VOYAGE_NUMBER, "2104S", CARRIER_SERVICE_CODE, "BW1", - LIMIT, "1")), + LIMIT, "5")), scenarioWithParameters( Map.of( CARRIER_VOYAGE_NUMBER, "2103N", @@ -88,7 +88,7 @@ public static LinkedHashMap createModuleScenario Map.of( CARRIER_VOYAGE_NUMBER, "2103S", UNIVERSAL_SERVICE_REFERENCE, "SR12345A", - LIMIT, "1")), + LIMIT, "5")), scenarioWithParameters( Map.of( UNIVERSAL_VOYAGE_REFERENCE, "2103N", @@ -97,7 +97,7 @@ public static LinkedHashMap createModuleScenario Map.of( UNIVERSAL_VOYAGE_REFERENCE, "2103S", CARRIER_SERVICE_CODE, "FE1", - LIMIT, "1")), + LIMIT, "5")), scenarioWithParameters( Map.of( UNIVERSAL_VOYAGE_REFERENCE, "2105N", @@ -106,7 +106,7 @@ public static LinkedHashMap createModuleScenario Map.of( UNIVERSAL_VOYAGE_REFERENCE, "2105S", UNIVERSAL_SERVICE_REFERENCE, "SR54321C", - LIMIT, "1"))))) + LIMIT, "5"))))) .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/action/SupplyScenarioParametersAction.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/action/SupplyScenarioParametersAction.java index 6896ae0d..699ef07e 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/action/SupplyScenarioParametersAction.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/action/SupplyScenarioParametersAction.java @@ -4,10 +4,17 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Arrays; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import lombok.Getter; +import org.dcsa.conformance.core.UserFacingException; import org.dcsa.conformance.standards.ovs.party.OvsFilterParameter; import org.dcsa.conformance.standards.ovs.party.SuppliedScenarioParameters; @@ -90,6 +97,48 @@ public boolean isInputRequired() { @Override public void handlePartyInput(JsonNode partyInput) { super.handlePartyInput(partyInput); - suppliedScenarioParameters = SuppliedScenarioParameters.fromJson(partyInput.get("input")); + JsonNode inputNode = partyInput.get("input"); + Set inputKeys = + StreamSupport.stream( + ((Iterable) inputNode::fieldNames) + .spliterator(), + false) + .collect(Collectors.toSet()); + + Set missingKeys = + StreamSupport.stream( + ((Iterable) () -> getJsonForHumanReadablePrompt().fieldNames()) + .spliterator(), + false) + .collect(Collectors.toSet()); + missingKeys.removeAll(inputKeys); + if (!missingKeys.isEmpty()) { + throw new UserFacingException( + "The input must contain: %s".formatted(String.join(", ", missingKeys))); + } + + Arrays.stream(OvsFilterParameter.values()) + .map(OvsFilterParameter::getQueryParamName) + .filter( + queryParamName -> + queryParamName.startsWith( + OvsFilterParameter.START_DATE.getQueryParamName()) + || queryParamName.startsWith(OvsFilterParameter.END_DATE.getQueryParamName())) + .filter(inputNode::hasNonNull) + .forEach( + queryParamName -> { + String dateValue = inputNode.path(queryParamName).asText(); + try { + LocalDate.parse(dateValue, DateTimeFormatter.ISO_DATE); + } catch (DateTimeParseException e) { + throw new UserFacingException( + "Invalid date-time format '%s' for input parameter '%s'" + .formatted(dateValue, queryParamName), + e); + } + }); + + suppliedScenarioParameters = SuppliedScenarioParameters.fromJson(inputNode); + } } diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java index f915fbd6..97842aea 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java @@ -208,6 +208,9 @@ public Set validateDate( .flatMap( timestampsNode -> StreamSupport.stream(timestampsNode.getValue().spliterator(), false) + .filter( + eventDateTimeNode -> + !eventDateTimeNode.isMissingNode() && !eventDateTimeNode.isNull()) .filter( timestampNode -> { LocalDate eventDateTime = From d0ebefe0558bbab3a4518885f5596de091bf7334 Mon Sep 17 00:00:00 2001 From: preetamnpr <128618622+preetamnpr@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:08:03 +0100 Subject: [PATCH 6/8] SD-1823 moved the different publisher responses to test folder and added unit test cases. --- .../core/check/JsonAttributeBasedCheck.java | 6 +- ovs/pom.xml | 1 + .../standards/ovs/party/OvsPublisher.java | 40 +---- .../standards/ovs/OvsChecksTest.java | 154 ++++++++++++------ ...s-300-response-wrong-attribute-values.json | 0 .../ovs-300-response-wrong-date-times.json | 0 .../ovs-300-response-wrong-structure.json | 0 7 files changed, 111 insertions(+), 90 deletions(-) rename ovs/src/{main/resources/standards/ovs => test/resources}/messages/ovs-300-response-wrong-attribute-values.json (100%) rename ovs/src/{main/resources/standards/ovs => test/resources}/messages/ovs-300-response-wrong-date-times.json (100%) rename ovs/src/{main/resources/standards/ovs => test/resources}/messages/ovs-300-response-wrong-structure.json (100%) diff --git a/core/src/main/java/org/dcsa/conformance/core/check/JsonAttributeBasedCheck.java b/core/src/main/java/org/dcsa/conformance/core/check/JsonAttributeBasedCheck.java index 63bc1052..1dae74ec 100644 --- a/core/src/main/java/org/dcsa/conformance/core/check/JsonAttributeBasedCheck.java +++ b/core/src/main/java/org/dcsa/conformance/core/check/JsonAttributeBasedCheck.java @@ -9,14 +9,16 @@ import java.util.function.Predicate; import java.util.stream.Stream; +import lombok.Getter; import lombok.NonNull; import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.core.traffic.HttpMessageType; -class JsonAttributeBasedCheck extends ActionCheck { + +public class JsonAttributeBasedCheck extends ActionCheck { private final String standardsVersion; - private final List validators; + @Getter private final List validators; JsonAttributeBasedCheck( String titlePrefix, diff --git a/ovs/pom.xml b/ovs/pom.xml index d9e08d71..3af3f6e4 100644 --- a/ovs/pom.xml +++ b/ovs/pom.xml @@ -27,6 +27,7 @@ org.junit.jupiter junit-jupiter + test diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsPublisher.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsPublisher.java index 3b09fc99..a3db8b72 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsPublisher.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/party/OvsPublisher.java @@ -44,11 +44,6 @@ public OvsPublisher( orchestratorAuthHeader); } - private static final boolean RETURN_EMPTY_RESPONSE = false; - private static final boolean USE_WRONG_ATTRIBUTE_VALUES = false; - private static final boolean USE_WRONG_DATE_TIMES = false; - private static final boolean USE_WRONG_RESPONSE_STRUCTURE = false; - @Override protected void exportPartyJsonState(ObjectNode targetObjectNode) {} @@ -135,38 +130,9 @@ public ConformanceResponse handleRequest(ConformanceRequest request) { } Map> headers = - new HashMap<>(Map.of(API_VERSION, List.of(apiVersion))); - - if (RETURN_EMPTY_RESPONSE) { - return request.createResponse( - 200, headers, new ConformanceMessageBody(OBJECT_MAPPER.createArrayNode())); - } else if (USE_WRONG_ATTRIBUTE_VALUES) { - return request.createResponse( - 200, - headers, - new ConformanceMessageBody( - JsonToolkit.templateFileToJsonNode( - "/standards/ovs/messages/ovs-300-response-wrong-attribute-values.json", - Map.ofEntries()))); - } else if (USE_WRONG_DATE_TIMES) { - return request.createResponse( - 200, - headers, - new ConformanceMessageBody( - JsonToolkit.templateFileToJsonNode( - "/standards/ovs/messages/ovs-300-response-wrong-date-times.json", - Map.ofEntries()))); - } else if (USE_WRONG_RESPONSE_STRUCTURE) { - return request.createResponse( - 200, - headers, - new ConformanceMessageBody( - JsonToolkit.templateFileToJsonNode( - "/standards/ovs/messages/ovs-300-response-wrong-structure.json", - Map.ofEntries()))); - } else { - return request.createResponse(200, headers, new ConformanceMessageBody(filteredArray)); - } + new HashMap<>(Map.of(API_VERSION, List.of(apiVersion))); + + return request.createResponse(200, headers, new ConformanceMessageBody(filteredArray)); } private ArrayNode applyFilter( diff --git a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java index 4340f088..4203a04a 100644 --- a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java +++ b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java @@ -4,15 +4,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.dcsa.conformance.core.check.ActionCheck; +import org.dcsa.conformance.core.check.JsonAttributeBasedCheck; +import org.dcsa.conformance.core.toolkit.JsonToolkit; import org.dcsa.conformance.standards.ovs.checks.OvsChecks; import org.dcsa.conformance.standards.ovs.party.OvsFilterParameter; +import org.dcsa.conformance.standards.ovs.party.SuppliedScenarioParameters; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; import java.time.LocalDate; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Stream; - import static org.junit.jupiter.api.Assertions.*; public class OvsChecksTest { @@ -27,6 +31,65 @@ void setUp() { serviceNodes = createServiceNodes("Great Lion Service", "GLS", "SR12345A", vesselSchedules); } + @Test + void testResponseContentChecks_validResponse() { + Set issues = + executeResponseChecks(Map.of(OvsFilterParameter.CARRIER_SERVICE_CODE, "GLS"), serviceNodes); + assertTrue(issues.isEmpty()); + } + + @Test + void testResponseContentChecks_withWrongAttributesValuesResponse() { + JsonNode jsonBody = JsonToolkit.templateFileToJsonNode( + "/messages/ovs-300-response-wrong-attribute-values.json", + Map.ofEntries()); + + Set issues = + executeResponseChecks(Map.of(OvsFilterParameter.CARRIER_SERVICE_CODE, "GLS"), jsonBody); + assertFalse(issues.isEmpty()); + } + + @Test + void testResponseContentChecks_withWrongDateTimesResponse() { + JsonNode jsonBody = JsonToolkit.templateFileToJsonNode( + "/messages/ovs-300-response-wrong-date-times.json", + Map.ofEntries()); + + Set issues = + executeResponseChecks(Map.of(OvsFilterParameter.START_DATE, "2027-07-19"), jsonBody); + assertTrue(issues.isEmpty()); + } + + @Test + void testResponseContentChecks_withWrongStructureResponse() { + JsonNode jsonBody = JsonToolkit.templateFileToJsonNode( + "/messages/ovs-300-response-wrong-structure.json", + Map.ofEntries()); + + Set issues = + executeResponseChecks(Map.of(OvsFilterParameter.CARRIER_SERVICE_CODE, ""), jsonBody); + assertFalse(issues.isEmpty()); + } + + private Set executeResponseChecks( + Map ovsFilterParameterStringMap, JsonNode serviceNodes) { + UUID matchedId = UUID.randomUUID(); + String standardVersion = "3.0.0"; + Supplier sspSupplier = + () -> SuppliedScenarioParameters.fromMap(ovsFilterParameterStringMap); + Set issues = new HashSet<>(); + + ActionCheck actionCheck = + OvsChecks.responseContentChecks(matchedId, standardVersion, sspSupplier); + ((JsonAttributeBasedCheck) actionCheck) + .getValidators() + .forEach( + validator -> { + issues.addAll(validator.validate(serviceNodes)); + }); + return issues; + } + @Test void testCheckThatScheduleValuesMatchParamValues_match() { Map filterParametersMap = @@ -55,162 +118,150 @@ void testCheckThatScheduleValuesMatchParamValues_noMatch() { @Test void testCheckCarrierServiceName_match() { Map filterParametersMap = - Map.of(OvsFilterParameter.CARRIER_SERVICE_NAME, "Great Lion Service"); + Map.of(OvsFilterParameter.CARRIER_SERVICE_NAME, "Great Lion Service"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertTrue(result.isEmpty()); } @Test void testCheckCarrierServiceName_noMatch() { Map filterParametersMap = - Map.of(OvsFilterParameter.CARRIER_SERVICE_NAME, "Great Tiger Service"); + Map.of(OvsFilterParameter.CARRIER_SERVICE_NAME, "Great Tiger Service"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertFalse(result.isEmpty()); } @Test void testCheckUniversalServiceReference_match() { Map filterParametersMap = - Map.of(OvsFilterParameter.UNIVERSAL_SERVICE_REFERENCE, "SR12345A"); + Map.of(OvsFilterParameter.UNIVERSAL_SERVICE_REFERENCE, "SR12345A"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertTrue(result.isEmpty()); } @Test void testCheckUniversalServiceReference_noMatch() { Map filterParametersMap = - Map.of(OvsFilterParameter.UNIVERSAL_SERVICE_REFERENCE, "SRA"); + Map.of(OvsFilterParameter.UNIVERSAL_SERVICE_REFERENCE, "SRA"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertFalse(result.isEmpty()); } @Test void testCheckVesselIMONumber_match() { Map filterParametersMap = - Map.of(OvsFilterParameter.VESSEL_IMO_NUMBER, "1234567"); + Map.of(OvsFilterParameter.VESSEL_IMO_NUMBER, "1234567"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertTrue(result.isEmpty()); } @Test void testCheckVesselIMONumber_noMatch() { Map filterParametersMap = - Map.of(OvsFilterParameter.VESSEL_IMO_NUMBER, "1234"); + Map.of(OvsFilterParameter.VESSEL_IMO_NUMBER, "1234"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertFalse(result.isEmpty()); } @Test void testCheckVesselName_match() { Map filterParametersMap = - Map.of(OvsFilterParameter.VESSEL_NAME, "Great Vessel"); + Map.of(OvsFilterParameter.VESSEL_NAME, "Great Vessel"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertTrue(result.isEmpty()); } - @Test void testCheckVesselName_noMatch() { Map filterParametersMap = - Map.of(OvsFilterParameter.VESSEL_NAME, "Great Bowl"); + Map.of(OvsFilterParameter.VESSEL_NAME, "Great Bowl"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertFalse(result.isEmpty()); } - @Test void testCheckCarrierVoyageNumber_match() { Map filterParametersMap = - Map.of(OvsFilterParameter.CARRIER_VOYAGE_NUMBER, "2104N,2104S"); + Map.of(OvsFilterParameter.CARRIER_VOYAGE_NUMBER, "2104N,2104S"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertTrue(result.isEmpty()); } @Test void testCheckCarrierVoyageNumber_noMatch() { Map filterParametersMap = - Map.of(OvsFilterParameter.CARRIER_VOYAGE_NUMBER, "2104P,2104Q"); + Map.of(OvsFilterParameter.CARRIER_VOYAGE_NUMBER, "2104P,2104Q"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertFalse(result.isEmpty()); } @Test void testCheckUniversalVoyageReference_match() { Map filterParametersMap = - Map.of(OvsFilterParameter.UNIVERSAL_VOYAGE_REFERENCE, "SR12345A,SR45678A"); + Map.of(OvsFilterParameter.UNIVERSAL_VOYAGE_REFERENCE, "SR12345A,SR45678A"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertTrue(result.isEmpty()); } @Test void testCheckUniversalVoyageReference_noMatch() { Map filterParametersMap = - Map.of(OvsFilterParameter.UNIVERSAL_VOYAGE_REFERENCE, "SR1245A,SR458A"); + Map.of(OvsFilterParameter.UNIVERSAL_VOYAGE_REFERENCE, "SR1245A,SR458A"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertFalse(result.isEmpty()); } @Test void testCheckUNLocationCode_match() { Map filterParametersMap = - Map.of(OvsFilterParameter.UN_LOCATION_CODE, "NLAMS"); + Map.of(OvsFilterParameter.UN_LOCATION_CODE, "NLAMS"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertTrue(result.isEmpty()); } @Test void testCheckUNLocationCode_noMatch() { Map filterParametersMap = - Map.of(OvsFilterParameter.UN_LOCATION_CODE, "USNYC"); + Map.of(OvsFilterParameter.UN_LOCATION_CODE, "USNYC"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertFalse(result.isEmpty()); } @Test void testCheckFacilitySMDGCode_match() { Map filterParametersMap = - Map.of(OvsFilterParameter.FACILITY_SMDG_CODE, "APM"); + Map.of(OvsFilterParameter.FACILITY_SMDG_CODE, "APM"); JsonNode transportCall = - serviceNodes - .get(0) - .get("vesselSchedules") - .get(0) - .get("transportCalls") - .get(0); + serviceNodes.get(0).get("vesselSchedules").get(0).get("transportCalls").get(0); ((ObjectNode) transportCall.get("location")).put("facilitySMDGCode", "APM"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertTrue(result.isEmpty()); } @Test void testCheckFacilitySMDGCode_noMatch() { JsonNode transportCall = - serviceNodes - .get(0) - .get("vesselSchedules") - .get(0) - .get("transportCalls") - .get(0); + serviceNodes.get(0).get("vesselSchedules").get(0).get("transportCalls").get(0); ((ObjectNode) transportCall.get("location")).put("facilitySMDGCode", "APM"); Map filterParametersMap = - Map.of(OvsFilterParameter.FACILITY_SMDG_CODE, "APP"); + Map.of(OvsFilterParameter.FACILITY_SMDG_CODE, "APP"); Set result = - OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); + OvsChecks.checkThatScheduleValuesMatchParamValues(serviceNodes, filterParametersMap); assertFalse(result.isEmpty()); } @@ -299,14 +350,16 @@ void testFindMatchingNodes_rootMatch() { @Test void testFindMatchingNodes_arrayMatch() throws IOException { JsonNode root = objectMapper.readTree("[{\"a\": 1}, {\"b\": 2}]"); - Stream result = OvsChecks.findMatchingNodes(root, "*").map(Map.Entry::getValue);; + Stream result = OvsChecks.findMatchingNodes(root, "*").map(Map.Entry::getValue); + ; assertEquals(2, result.count()); } @Test void testFindMatchingNodes_emptyArrayMatch() throws IOException { JsonNode root = objectMapper.readTree("[]"); - Stream result = OvsChecks.findMatchingNodes(root, "*").map(Map.Entry::getValue);; + Stream result = OvsChecks.findMatchingNodes(root, "*").map(Map.Entry::getValue); + ; assertEquals(0, result.count()); } @@ -375,8 +428,7 @@ private JsonNode createServiceVesselSchedules(String vesselIMONumber, String ves vesselSchedule.put("vesselName", vesselName); vesselSchedule.set( "transportCalls", - createTransportCalls("TCREF1", "2104N", "2104S", - "SR12345A", "SR45678A", "NLAMS")); + createTransportCalls("TCREF1", "2104N", "2104S", "SR12345A", "SR45678A", "NLAMS")); vesselSchedulesArrayNode.add(vesselSchedule); return vesselSchedulesArrayNode; } diff --git a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-attribute-values.json b/ovs/src/test/resources/messages/ovs-300-response-wrong-attribute-values.json similarity index 100% rename from ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-attribute-values.json rename to ovs/src/test/resources/messages/ovs-300-response-wrong-attribute-values.json diff --git a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-date-times.json b/ovs/src/test/resources/messages/ovs-300-response-wrong-date-times.json similarity index 100% rename from ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-date-times.json rename to ovs/src/test/resources/messages/ovs-300-response-wrong-date-times.json diff --git a/ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-structure.json b/ovs/src/test/resources/messages/ovs-300-response-wrong-structure.json similarity index 100% rename from ovs/src/main/resources/standards/ovs/messages/ovs-300-response-wrong-structure.json rename to ovs/src/test/resources/messages/ovs-300-response-wrong-structure.json From a75e892bdd79811b88e686983193c83e9b38d541 Mon Sep 17 00:00:00 2001 From: preetamnpr <128618622+preetamnpr@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:09:39 +0100 Subject: [PATCH 7/8] SD-1823 reverted core changes by implementing the wrapper method. --- .../core/check/JsonAttributeBasedCheck.java | 5 +- .../standards/ovs/checks/OvsChecks.java | 166 +++++++++--------- .../ovs/{ => checks}/OvsChecksTest.java | 53 +++--- 3 files changed, 105 insertions(+), 119 deletions(-) rename ovs/src/test/java/org/dcsa/conformance/standards/ovs/{ => checks}/OvsChecksTest.java (89%) diff --git a/core/src/main/java/org/dcsa/conformance/core/check/JsonAttributeBasedCheck.java b/core/src/main/java/org/dcsa/conformance/core/check/JsonAttributeBasedCheck.java index 1dae74ec..1a496759 100644 --- a/core/src/main/java/org/dcsa/conformance/core/check/JsonAttributeBasedCheck.java +++ b/core/src/main/java/org/dcsa/conformance/core/check/JsonAttributeBasedCheck.java @@ -9,16 +9,15 @@ import java.util.function.Predicate; import java.util.stream.Stream; -import lombok.Getter; import lombok.NonNull; import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.core.traffic.HttpMessageType; -public class JsonAttributeBasedCheck extends ActionCheck { +class JsonAttributeBasedCheck extends ActionCheck { private final String standardsVersion; - @Getter private final List validators; + private final List validators; JsonAttributeBasedCheck( String titlePrefix, diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java index 97842aea..a0c101a7 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java @@ -26,99 +26,99 @@ @UtilityClass public class OvsChecks { - public static ActionCheck responseContentChecks( - UUID matched, String standardVersion, Supplier sspSupplier) { - + public List buildResponseContentChecks(Map filterParametersMap) { var checks = new ArrayList(); - checks.add( - JsonAttribute.customValidator( - "Every response received during a conformance test must contain schedules", - body -> { - Set validationErrors = new LinkedHashSet<>(); - checkServiceSchedulesExist(body) - .forEach( - validationError -> - validationErrors.add( - "CheckServiceSchedules failed: %s".formatted(validationError))); - if (validationErrors.isEmpty()) { - return Set.of(); - } - return validationErrors; - })); + JsonAttribute.customValidator( + "Every response received during a conformance test must contain schedules", + body -> { + Set validationErrors = new LinkedHashSet<>(); + checkServiceSchedulesExist(body) + .forEach( + validationError -> + validationErrors.add( + "CheckServiceSchedules failed: %s".formatted(validationError))); + if (validationErrors.isEmpty()) { + return Set.of(); + } + return validationErrors; + })); checks.add( - JsonAttribute.customValidator( - "If present, at least schedule attribute must match the corresponding query parameters", - body -> { - Map filterParametersMap = sspSupplier.get().getMap(); - Set validationErrors = new LinkedHashSet<>(); - checkThatScheduleValuesMatchParamValues(body, filterParametersMap) - .forEach( - validationError -> - validationErrors.add( - "Schedule Param Value Validation failed: %s" - .formatted(validationError))); - return validationErrors; - })); + JsonAttribute.customValidator( + "If present, at least one schedule attribute must match the corresponding query parameters", + body -> { + Set validationErrors = new LinkedHashSet<>(); + checkThatScheduleValuesMatchParamValues(body, filterParametersMap) + .forEach( + validationError -> + validationErrors.add( + "Schedule Param Value Validation failed: %s" + .formatted(validationError))); + return validationErrors; + })); checks.add( - JsonAttribute.customValidator( - "Check eventDateTime is greater than startDate filter parameter if present", - body -> { - Set validationErrors = new LinkedHashSet<>(); - Map filterParametersMap = sspSupplier.get().getMap(); - validateDate( - body, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore) - .forEach( - validationError -> - validationErrors.add( - "Start Date EventDateTime validation failed: %s" - .formatted(validationError))); - return validationErrors; - })); + JsonAttribute.customValidator( + "Check eventDateTime is greater than startDate filter parameter if present", + body -> { + Set validationErrors = new LinkedHashSet<>(); + validateDate( + body, filterParametersMap, OvsFilterParameter.START_DATE, LocalDate::isBefore) + .forEach( + validationError -> + validationErrors.add( + "Start Date EventDateTime validation failed: %s" + .formatted(validationError))); + return validationErrors; + })); checks.add( - JsonAttribute.customValidator( - "Check eventDateTime is less than endDate filter parameter if present", - body -> { - Set validationErrors = new LinkedHashSet<>(); - Map filterParametersMap = sspSupplier.get().getMap(); - validateDate( - body, filterParametersMap, OvsFilterParameter.END_DATE, LocalDate::isAfter) - .forEach( - validationError -> - validationErrors.add( - "EndDate EventDateTime validation failed: %s" - .formatted(validationError))); - return validationErrors; - })); + JsonAttribute.customValidator( + "Check eventDateTime is less than endDate filter parameter if present", + body -> { + Set validationErrors = new LinkedHashSet<>(); + validateDate( + body, filterParametersMap, OvsFilterParameter.END_DATE, LocalDate::isAfter) + .forEach( + validationError -> + validationErrors.add( + "EndDate EventDateTime validation failed: %s" + .formatted(validationError))); + return validationErrors; + })); checks.add( - JsonAttribute.customValidator( - "Check transportCallReference is unique across each service schedules", - OvsChecks::validateUniqueTransportCallReference)); + JsonAttribute.customValidator( + "Check transportCallReference is unique across each service schedules", + OvsChecks::validateUniqueTransportCallReference)); checks.add( - JsonAttribute.customValidator( - "Validate limit exists and the number of schedules does not exceed the limit", - body -> { - Optional> limitParam = - sspSupplier.get().getMap().entrySet().stream() - .filter(e -> e.getKey().equals(OvsFilterParameter.LIMIT)) - .findFirst(); + JsonAttribute.customValidator( + "Validate limit exists and the number of schedules does not exceed the limit", + body -> { + Optional> limitParam = + filterParametersMap.entrySet().stream() + .filter(e -> e.getKey().equals(OvsFilterParameter.LIMIT)) + .findFirst(); - if (limitParam.isPresent()) { - int expectedLimit = Integer.parseInt(limitParam.get().getValue().trim()); - if (body.size() > expectedLimit) { - return Set.of( - "The number of service schedules exceeds the limit parameter: " - + expectedLimit); - } - } - return Set.of(); - })); + if (limitParam.isPresent()) { + int expectedLimit = Integer.parseInt(limitParam.get().getValue().trim()); + if (body.size() > expectedLimit) { + return Set.of( + "The number of service schedules exceeds the limit parameter: " + + expectedLimit); + } + } + return Set.of(); + })); + return checks; + } + public static ActionCheck responseContentChecks( + UUID matched, String standardVersion, Supplier sspSupplier) { + Map filterParametersMap = sspSupplier.get().getMap(); + var checks = buildResponseContentChecks(filterParametersMap); return JsonAttribute.contentChecks( OvsRole::isPublisher, matched, HttpMessageType.RESPONSE, standardVersion, checks); } @@ -302,9 +302,9 @@ private Stream> findMatchingNodes( } public Set checkServiceSchedulesExist(JsonNode body) { - Set validationErrors = new LinkedHashSet<>(); + if (body == null || body.isMissingNode() || body.isNull()) { - validationErrors.add("Response body is missing or null."); + return Set.of("Response body is missing or null."); } else { boolean hasVesselSchedules = findMatchingNodes(body, "*/vesselSchedules") @@ -314,9 +314,9 @@ public Set checkServiceSchedulesExist(JsonNode body) { && node.getValue().isArray() && !node.getValue().isEmpty()); if (!hasVesselSchedules) { - validationErrors.add("Response doesn't have schedules."); + return Set.of("Response doesn't have schedules."); } } - return validationErrors; + return Set.of(); } } diff --git a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/checks/OvsChecksTest.java similarity index 89% rename from ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java rename to ovs/src/test/java/org/dcsa/conformance/standards/ovs/checks/OvsChecksTest.java index 4203a04a..c5d3e6dd 100644 --- a/ovs/src/test/java/org/dcsa/conformance/standards/ovs/OvsChecksTest.java +++ b/ovs/src/test/java/org/dcsa/conformance/standards/ovs/checks/OvsChecksTest.java @@ -1,27 +1,22 @@ -package org.dcsa.conformance.standards.ovs; +package org.dcsa.conformance.standards.ovs.checks; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.dcsa.conformance.core.check.ActionCheck; -import org.dcsa.conformance.core.check.JsonAttributeBasedCheck; import org.dcsa.conformance.core.toolkit.JsonToolkit; -import org.dcsa.conformance.standards.ovs.checks.OvsChecks; import org.dcsa.conformance.standards.ovs.party.OvsFilterParameter; -import org.dcsa.conformance.standards.ovs.party.SuppliedScenarioParameters; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; import java.time.LocalDate; import java.util.*; -import java.util.function.Supplier; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.*; -public class OvsChecksTest { +import static org.dcsa.conformance.core.toolkit.JsonToolkit.OBJECT_MAPPER; +import static org.junit.jupiter.api.Assertions.*; - private final ObjectMapper objectMapper = new ObjectMapper(); +class OvsChecksTest { private JsonNode serviceNodes; @@ -73,16 +68,9 @@ void testResponseContentChecks_withWrongStructureResponse() { private Set executeResponseChecks( Map ovsFilterParameterStringMap, JsonNode serviceNodes) { - UUID matchedId = UUID.randomUUID(); - String standardVersion = "3.0.0"; - Supplier sspSupplier = - () -> SuppliedScenarioParameters.fromMap(ovsFilterParameterStringMap); Set issues = new HashSet<>(); - ActionCheck actionCheck = - OvsChecks.responseContentChecks(matchedId, standardVersion, sspSupplier); - ((JsonAttributeBasedCheck) actionCheck) - .getValidators() + OvsChecks.buildResponseContentChecks(ovsFilterParameterStringMap) .forEach( validator -> { issues.addAll(validator.validate(serviceNodes)); @@ -325,7 +313,7 @@ void testValidateUniqueTransportCallReference_duplicate() { JsonNode transportCalls = vesselSchedules.get(0).get("transportCalls"); ObjectNode newTransportCall = - new ObjectMapper().createObjectNode().put("transportCallReference", "TCREF1"); + OBJECT_MAPPER.createObjectNode().put("transportCallReference", "TCREF1"); ((ArrayNode) transportCalls).add(newTransportCall); Set result = OvsChecks.validateUniqueTransportCallReference(serviceNodes); @@ -342,14 +330,14 @@ void testValidateUniqueTransportCallReference_noVesselSchedules() { @Test void testFindMatchingNodes_rootMatch() { - JsonNode root = objectMapper.createObjectNode().put("value", "test"); + JsonNode root = OBJECT_MAPPER.createObjectNode().put("value", "test"); Stream result = OvsChecks.findMatchingNodes(root, "/").map(Map.Entry::getValue); assertEquals(1, result.count()); } @Test void testFindMatchingNodes_arrayMatch() throws IOException { - JsonNode root = objectMapper.readTree("[{\"a\": 1}, {\"b\": 2}]"); + JsonNode root = OBJECT_MAPPER.readTree("[{\"a\": 1}, {\"b\": 2}]"); Stream result = OvsChecks.findMatchingNodes(root, "*").map(Map.Entry::getValue); ; assertEquals(2, result.count()); @@ -357,7 +345,7 @@ void testFindMatchingNodes_arrayMatch() throws IOException { @Test void testFindMatchingNodes_emptyArrayMatch() throws IOException { - JsonNode root = objectMapper.readTree("[]"); + JsonNode root = OBJECT_MAPPER.readTree("[]"); Stream result = OvsChecks.findMatchingNodes(root, "*").map(Map.Entry::getValue); ; assertEquals(0, result.count()); @@ -365,7 +353,7 @@ void testFindMatchingNodes_emptyArrayMatch() throws IOException { @Test void testCheckServiceSchedulesExist_emptyServiceSchedules() { - JsonNode body = objectMapper.createArrayNode(); + JsonNode body = OBJECT_MAPPER.createArrayNode(); Set result = OvsChecks.checkServiceSchedulesExist(body); assertEquals(1, result.size()); } @@ -390,7 +378,7 @@ void testValidateDate_invalidDateFormat() { .get("timestamps"); ObjectNode invalidTimeStamp = - new ObjectMapper().createObjectNode().put("eventDateTime", "TCREF1"); + OBJECT_MAPPER.createObjectNode().put("eventDateTime", "TCREF1"); ((ArrayNode) timeStamps).add(invalidTimeStamp); Set result = @@ -405,13 +393,12 @@ private JsonNode createServiceNodes( String carrierServiceCode, String universalServiceReference, JsonNode vesselSchedules) { - ObjectMapper objectMapper = new ObjectMapper(); // Create the root ArrayNode - ArrayNode rootArrayNode = objectMapper.createArrayNode(); + ArrayNode rootArrayNode = OBJECT_MAPPER.createArrayNode(); // Create the first ObjectNode - ObjectNode firstObjectNode = objectMapper.createObjectNode(); + ObjectNode firstObjectNode = OBJECT_MAPPER.createObjectNode(); firstObjectNode.put("carrierServiceName", carrierServiceName); firstObjectNode.put("carrierServiceCode", carrierServiceCode); firstObjectNode.put("universalServiceReference", universalServiceReference); @@ -422,8 +409,8 @@ private JsonNode createServiceNodes( } private JsonNode createServiceVesselSchedules(String vesselIMONumber, String vesselName) { - ArrayNode vesselSchedulesArrayNode = objectMapper.createArrayNode(); - ObjectNode vesselSchedule = objectMapper.createObjectNode(); + ArrayNode vesselSchedulesArrayNode = OBJECT_MAPPER.createArrayNode(); + ObjectNode vesselSchedule = OBJECT_MAPPER.createObjectNode(); vesselSchedule.put("vesselIMONumber", vesselIMONumber); vesselSchedule.put("vesselName", vesselName); vesselSchedule.set( @@ -441,8 +428,8 @@ private JsonNode createTransportCalls( String universalExportVoyageReference, String UNLocationCode) { // Create the transportCalls ArrayNode for the vesselSchedule - ArrayNode transportCallsArrayNode = objectMapper.createArrayNode(); - ObjectNode transportCall = objectMapper.createObjectNode(); + ArrayNode transportCallsArrayNode = OBJECT_MAPPER.createArrayNode(); + ObjectNode transportCall = OBJECT_MAPPER.createObjectNode(); transportCall.put("transportCallReference", transportCallReference); transportCall.put("carrierImportVoyageNumber", carrierImportVoyageNumber); transportCall.put("carrierExportVoyageNumber", carrierExportVoyageNumber); @@ -450,7 +437,7 @@ private JsonNode createTransportCalls( transportCall.put("universalExportVoyageReference", universalExportVoyageReference); // Create the location ObjectNode for the first transportCall - ObjectNode location = objectMapper.createObjectNode(); + ObjectNode location = OBJECT_MAPPER.createObjectNode(); location.put("UNLocationCode", UNLocationCode); transportCall.set("location", location); transportCall.set("timestamps", createTimestamps()); @@ -460,7 +447,7 @@ private JsonNode createTransportCalls( private JsonNode createEventDateTime(String eventDateTime) { // Create a timestamp for timestamps ArrayNode - ObjectNode timestamp = objectMapper.createObjectNode(); + ObjectNode timestamp = OBJECT_MAPPER.createObjectNode(); timestamp.put("eventTypeCode", "ARRI"); timestamp.put("eventClassifierCode", "PLN"); timestamp.put("eventDateTime", eventDateTime); @@ -469,7 +456,7 @@ private JsonNode createEventDateTime(String eventDateTime) { private JsonNode createTimestamps() { // Create the timestamps ArrayNode - ArrayNode timestampsArrayNode = objectMapper.createArrayNode(); + ArrayNode timestampsArrayNode = OBJECT_MAPPER.createArrayNode(); timestampsArrayNode.add(createEventDateTime("2024-07-21T10:00:00Z")); timestampsArrayNode.add(createEventDateTime("2024-07-22T10:00:00Z")); timestampsArrayNode.add(createEventDateTime("2024-07-23T10:00:00Z")); From 738fd35d6262509a6297b853bbd28d33cd4ecd6e Mon Sep 17 00:00:00 2001 From: preetamnpr <128618622+preetamnpr@users.noreply.github.com> Date: Wed, 4 Dec 2024 21:21:51 +0100 Subject: [PATCH 8/8] SD-1823 handled null sspSupplier and removed validationErrors condition to return empty set. --- .../dcsa/conformance/standards/ovs/checks/OvsChecks.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java index a0c101a7..b02fbb4d 100644 --- a/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java +++ b/ovs/src/main/java/org/dcsa/conformance/standards/ovs/checks/OvsChecks.java @@ -38,9 +38,6 @@ public List buildResponseContentChecks(Map validationErrors.add( "CheckServiceSchedules failed: %s".formatted(validationError))); - if (validationErrors.isEmpty()) { - return Set.of(); - } return validationErrors; })); @@ -117,7 +114,9 @@ public List buildResponseContentChecks(Map sspSupplier) { - Map filterParametersMap = sspSupplier.get().getMap(); + Map filterParametersMap = sspSupplier.get() != null + ? sspSupplier.get().getMap() + : Map.of(); var checks = buildResponseContentChecks(filterParametersMap); return JsonAttribute.contentChecks( OvsRole::isPublisher, matched, HttpMessageType.RESPONSE, standardVersion, checks);