From de1be022f6456230115355266477c38f680aae5f Mon Sep 17 00:00:00 2001 From: Niels Thykier Date: Tue, 28 Nov 2023 13:22:38 +0100 Subject: [PATCH 1/4] booking: Schema validate response on GET /bookings/{ref} Signed-off-by: Niels Thykier --- .../standards/booking/action/Shipper_GetBookingAction.java | 6 ++++++ .../dcsa/conformance/standards/booking/party/Carrier.java | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetBookingAction.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetBookingAction.java index 43882d7d..453cb790 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetBookingAction.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetBookingAction.java @@ -70,6 +70,12 @@ protected Stream createSubChecks() { getMatchedExchangeUuid(), HttpMessageType.RESPONSE, expectedApiVersion), + new JsonSchemaCheck( + BookingRole::isCarrier, + getMatchedExchangeUuid(), + HttpMessageType.RESPONSE, + responseSchemaValidator + ), new CarrierGetBookingPayloadResponseConformanceCheck( getMatchedExchangeUuid(), expectedBookingStatus, diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java index b2e55bd0..5dc75f9f 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java @@ -8,6 +8,7 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -224,8 +225,9 @@ private void replaceShipmentCutOffTimes(ObjectNode booking) { } } - var oneWeekPrior = firstTransportActionByCarrier.minusWeeks(1).toString(); - var twoWeeksPrior = firstTransportActionByCarrier.minusWeeks(2).toString(); + var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); + var oneWeekPrior = formatter.format(firstTransportActionByCarrier.minusWeeks(1)); + var twoWeeksPrior = formatter.format(firstTransportActionByCarrier.minusWeeks(2)); addShipmentCutOff(shipmentCutOffTimes, "DCO", oneWeekPrior); addShipmentCutOff(shipmentCutOffTimes, "VCO", oneWeekPrior); From 1cd34d435c542677f0eca1e43b480b37cb33d7b9 Mon Sep 17 00:00:00 2001 From: Niels Thykier Date: Tue, 28 Nov 2023 13:24:41 +0100 Subject: [PATCH 2/4] Refactor some code Signed-off-by: Niels Thykier --- .../standards/booking/party/Carrier.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java index 5dc75f9f..ffbb7096 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java @@ -36,10 +36,10 @@ @Slf4j public class Carrier extends ConformanceParty { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private static final Random RANDOM = new Random(); - private final ObjectMapper objectMapper = new ObjectMapper(); private final Map cbrrToCbr = new HashMap<>(); private final Map cbrToCbrr = new HashMap<>(); protected boolean isShipperNotificationEnabled = true; @@ -62,8 +62,8 @@ public Carrier( @Override protected void exportPartyJsonState(ObjectNode targetObjectNode) { - targetObjectNode.set("cbrrToCbr", StateManagementUtil.storeMap(objectMapper, cbrrToCbr)); - targetObjectNode.set("cbrToCbrr", StateManagementUtil.storeMap(objectMapper, cbrToCbrr)); + targetObjectNode.set("cbrrToCbr", StateManagementUtil.storeMap(OBJECT_MAPPER, cbrrToCbr)); + targetObjectNode.set("cbrToCbrr", StateManagementUtil.storeMap(OBJECT_MAPPER, cbrToCbrr)); } @Override @@ -119,7 +119,7 @@ private void supplyScenarioParameters(JsonNode actionPrompt) { "Carrier Service %d".formatted(RANDOM.nextInt(999999)), generateSchemaValidVesselIMONumber()); asyncOrchestratorPostPartyInput( - objectMapper + OBJECT_MAPPER .createObjectNode() .put("actionId", actionPrompt.get("actionId").asText()) .set("input", carrierScenarioParameters.toJson())); @@ -438,7 +438,7 @@ private void generateAndEmitNotificationFromBooking(JsonNode actionPrompt, Persi asyncCounterpartPost("/v2/booking-notifications", notification); } else { asyncOrchestratorPostPartyInput( - objectMapper.createObjectNode().put("actionId", actionPrompt.get("actionId").asText())); + OBJECT_MAPPER.createObjectNode().put("actionId", actionPrompt.get("actionId").asText())); } } @@ -448,7 +448,7 @@ private ConformanceResponse return405(ConformanceRequest request, String... allo Map.of( "Api-Version", List.of(apiVersion), "Allow", List.of(String.join(",", allowedMethods))), new ConformanceMessageBody( - objectMapper + OBJECT_MAPPER .createObjectNode() .put("message", "Returning 405 because the method was not supported"))); } @@ -457,24 +457,27 @@ private ConformanceResponse return400(ConformanceRequest request, String message return request.createResponse( 400, Map.of("Api-Version", List.of(apiVersion)), - new ConformanceMessageBody(objectMapper.createObjectNode().put("message", message))); + new ConformanceMessageBody(OBJECT_MAPPER.createObjectNode().put("message", message))); } private ConformanceResponse return404(ConformanceRequest request) { + return return404(request, "Returning 404 since the request did not match any known URL"); + } + private ConformanceResponse return404(ConformanceRequest request, String message) { return request.createResponse( 404, Map.of("Api-Version", List.of(apiVersion)), new ConformanceMessageBody( - objectMapper + OBJECT_MAPPER .createObjectNode() - .put("message", "Returning 404 since the request did not match any known URL"))); + .put("message", message))); } private ConformanceResponse return409(ConformanceRequest request, String message) { return request.createResponse( 409, Map.of("Api-Version", List.of(apiVersion)), - new ConformanceMessageBody(objectMapper.createObjectNode().put("message", message))); + new ConformanceMessageBody(OBJECT_MAPPER.createObjectNode().put("message", message))); } @Override @@ -530,7 +533,7 @@ private ConformanceResponse _handlePutBookingRequest(ConformanceRequest request) return return404(request); } ObjectNode updatedBookingRequest = - (ObjectNode) objectMapper.readTree(request.message().body().getJsonBody().toString()); + (ObjectNode) OBJECT_MAPPER.readTree(request.message().body().getJsonBody().toString()); var persistableCarrierBooking = PersistableCarrierBooking.fromPersistentStore(bookingData); try { persistableCarrierBooking.putBooking(bookingReference, updatedBookingRequest); @@ -600,7 +603,7 @@ private ConformanceResponse returnBookingStatusResponse( var cbrr = booking.get("carrierBookingRequestReference").asText(); var bookingStatus = booking.get("bookingStatus").asText(); var statusObject = - objectMapper + OBJECT_MAPPER .createObjectNode() .put("bookingStatus", bookingStatus) .put("carrierBookingRequestReference", cbrr); @@ -651,7 +654,7 @@ private ConformanceResponse _handleGetBookingRequest(ConformanceRequest request) @SneakyThrows private ConformanceResponse _handlePostBookingRequest(ConformanceRequest request) { ObjectNode bookingRequestPayload = - (ObjectNode) objectMapper.readTree(request.message().body().getJsonBody().toString()); + (ObjectNode) OBJECT_MAPPER.readTree(request.message().body().getJsonBody().toString()); var persistableCarrierBooking = PersistableCarrierBooking.initializeFromBookingRequest(bookingRequestPayload); persistableCarrierBooking.save(persistentMap); if (isShipperNotificationEnabled) { @@ -704,8 +707,7 @@ private String computedType() { } public ObjectNode asJsonNode() { - ObjectMapper objectMapper = new ObjectMapper(); - var notification = objectMapper.createObjectNode(); + var notification = OBJECT_MAPPER.createObjectNode(); notification.put("specversion", "1.0"); setIfNotNull(notification, "id", id); setIfNotNull(notification, "source", source); @@ -713,7 +715,7 @@ public ObjectNode asJsonNode() { notification.put("time", Instant.now().toString()); notification.put("datacontenttype", "application/json"); - var data = objectMapper.createObjectNode(); + var data = OBJECT_MAPPER.createObjectNode(); setBookingProvidedField(data, "carrierBookingReference", carrierBookingReference); if (includeCarrierBookingRequestReference) { setBookingProvidedField( From 405cffdbe3ddb144ced1fdc82c0918e8b695ce14 Mon Sep 17 00:00:00 2001 From: Niels Thykier Date: Tue, 28 Nov 2023 13:41:31 +0100 Subject: [PATCH 3/4] booking: Support getting amendedContent booking with related conformance checks Signed-off-by: Niels Thykier --- .../booking/BookingScenarioListBuilder.java | 45 +++++++++--- .../Shipper_GetAmendedBooking404Action.java | 70 +++++++++++++++++++ .../action/Shipper_GetBookingAction.java | 14 ++-- .../model/PersistableCarrierBooking.java | 2 + .../standards/booking/party/Carrier.java | 34 ++++++++- .../standards/booking/party/Shipper.java | 7 +- .../core/check/QueryParamCheck.java | 53 ++++++++++++++ .../core/party/ConformanceParty.java | 10 ++- 8 files changed, 215 insertions(+), 20 deletions(-) create mode 100644 booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetAmendedBooking404Action.java create mode 100644 core/src/main/java/org/dcsa/conformance/core/check/QueryParamCheck.java diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/BookingScenarioListBuilder.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/BookingScenarioListBuilder.java index db913d71..d783d61d 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/BookingScenarioListBuilder.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/BookingScenarioListBuilder.java @@ -68,7 +68,16 @@ public static BookingScenarioListBuilder buildTree( uc2_carrier_requestUpdateToBookingRequest() .then(shipper_GetBooking(PENDING_UPDATE)), uc3_shipper_submitUpdatedBookingRequest() - .then(shipper_GetBooking(PENDING_UPDATE_CONFIRMATION))))); + .then(shipper_GetBooking(PENDING_UPDATE_CONFIRMATION)), + shipper_GetAmendedBooking404().then( + uc5_carrier_confirmBookingRequest().then( + shipper_GetAmendedBooking404().then(uc7_shipper_submitBookingAmendment() + .then( + shipper_GetBooking( + CONFIRMED, + AMENDMENT_RECEIVED, + true + )))))))); } return carrier_SupplyScenarioParameters().thenAllPathsFrom(START); } @@ -173,19 +182,35 @@ private static BookingScenarioListBuilder shipper_GetBooking(BookingState expect private static BookingScenarioListBuilder shipper_GetBooking( BookingState expectedBookingStatus, BookingState expectedAmendedBookingStatus) { + return shipper_GetBooking(expectedBookingStatus, expectedAmendedBookingStatus, false); + } + + private static BookingScenarioListBuilder shipper_GetBooking( + BookingState expectedBookingStatus, BookingState expectedAmendedBookingStatus, boolean requestAmendedContent) { BookingComponentFactory componentFactory = threadLocalComponentFactory.get(); String carrierPartyName = threadLocalCarrierPartyName.get(); String shipperPartyName = threadLocalShipperPartyName.get(); return new BookingScenarioListBuilder( - previousAction -> - new Shipper_GetBookingAction( - carrierPartyName, - shipperPartyName, - (BookingAction) previousAction, - expectedBookingStatus, - expectedAmendedBookingStatus, - componentFactory.getMessageSchemaValidator(BOOKING_API, GET_BOOKING_SCHEMA_NAME), - false)); + previousAction -> + new Shipper_GetBookingAction( + carrierPartyName, + shipperPartyName, + (BookingAction) previousAction, + expectedBookingStatus, + expectedAmendedBookingStatus, + componentFactory.getMessageSchemaValidator(BOOKING_API, GET_BOOKING_SCHEMA_NAME), + requestAmendedContent)); + } + + private static BookingScenarioListBuilder shipper_GetAmendedBooking404() { + String carrierPartyName = threadLocalCarrierPartyName.get(); + String shipperPartyName = threadLocalShipperPartyName.get(); + return new BookingScenarioListBuilder( + previousAction -> + new Shipper_GetAmendedBooking404Action( + carrierPartyName, + shipperPartyName, + (BookingAction) previousAction)); } private static BookingScenarioListBuilder uc1_shipper_SubmitBookingRequest() { diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetAmendedBooking404Action.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetAmendedBooking404Action.java new file mode 100644 index 00000000..0c72b976 --- /dev/null +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetAmendedBooking404Action.java @@ -0,0 +1,70 @@ +package org.dcsa.conformance.standards.booking.action; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Stream; +import org.dcsa.conformance.core.check.*; +import org.dcsa.conformance.core.toolkit.JsonToolkit; +import org.dcsa.conformance.core.traffic.ConformanceExchange; +import org.dcsa.conformance.core.traffic.HttpMessageType; +import org.dcsa.conformance.standards.booking.checks.CarrierGetBookingPayloadResponseConformanceCheck; +import org.dcsa.conformance.standards.booking.party.BookingRole; +import org.dcsa.conformance.standards.booking.party.BookingState; + +public class Shipper_GetAmendedBooking404Action extends BookingAction { + + public Shipper_GetAmendedBooking404Action( + String carrierPartyName, + String shipperPartyName, + BookingAction previousAction) { + super(shipperPartyName, carrierPartyName, previousAction, "GET (amended content, non-existing)", 404); + } + + @Override + public ObjectNode asJsonNode() { + return super.asJsonNode() + .put("cbrr", getDspSupplier().get().carrierBookingRequestReference()) + .put("amendedContent", true); + } + + @Override + public String getHumanReadablePrompt() { + return "GET the (non-existing) amendment to the booking with CBR '%s'" + .formatted(getDspSupplier().get().carrierBookingReference()); + } + + @Override + public ConformanceCheck createCheck(String expectedApiVersion) { + return new ConformanceCheck(getActionTitle()) { + @Override + protected Stream createSubChecks() { + return Stream.of( + new UrlPathCheck( + BookingRole::isShipper, + getMatchedExchangeUuid(), + "/v2/bookings/" + getDspSupplier().get().carrierBookingRequestReference()), + new QueryParamCheck( + BookingRole::isShipper, + getMatchedExchangeUuid(), + "amendedContent", + "true" + ), + new ResponseStatusCheck( + BookingRole::isCarrier, getMatchedExchangeUuid(), expectedStatus), + new ApiHeaderCheck( + BookingRole::isShipper, + getMatchedExchangeUuid(), + HttpMessageType.REQUEST, + expectedApiVersion), + new ApiHeaderCheck( + BookingRole::isCarrier, + getMatchedExchangeUuid(), + HttpMessageType.RESPONSE, + expectedApiVersion)) + // .filter(Objects::nonNull) + ; + } + }; + } +} diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetBookingAction.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetBookingAction.java index 453cb790..1c06f721 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetBookingAction.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Shipper_GetBookingAction.java @@ -18,7 +18,7 @@ public class Shipper_GetBookingAction extends BookingAction { private final BookingState expectedBookingStatus; private final BookingState expectedAmendedBookingStatus; private final JsonSchemaValidator responseSchemaValidator; - private final boolean requestAmendedStatus; + private final boolean requestAmendedContent; public Shipper_GetBookingAction( String carrierPartyName, @@ -28,18 +28,18 @@ public Shipper_GetBookingAction( BookingState expectedAmendedBookingStatus, JsonSchemaValidator responseSchemaValidator, boolean requestAmendedStatus) { - super(shipperPartyName, carrierPartyName, previousAction, "GET", 200); + super(shipperPartyName, carrierPartyName, previousAction, requestAmendedStatus ? "GET (amended content)" : "GET", 200); this.expectedBookingStatus = expectedBookingStatus; this.expectedAmendedBookingStatus = expectedAmendedBookingStatus; this.responseSchemaValidator = responseSchemaValidator; - this.requestAmendedStatus = requestAmendedStatus; + this.requestAmendedContent = requestAmendedStatus; } @Override public ObjectNode asJsonNode() { - ObjectNode jsonNode = super.asJsonNode(); - jsonNode.put("cbrr", getDspSupplier().get().carrierBookingRequestReference()); - return jsonNode; + return super.asJsonNode() + .put("cbrr", getDspSupplier().get().carrierBookingRequestReference()) + .put("amendedContent", requestAmendedContent); } @Override @@ -80,7 +80,7 @@ protected Stream createSubChecks() { getMatchedExchangeUuid(), expectedBookingStatus, expectedAmendedBookingStatus, - requestAmendedStatus + requestAmendedContent ), new ActionCheck( "GET returns the expected Booking data", diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/model/PersistableCarrierBooking.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/model/PersistableCarrierBooking.java index 1b628062..275b56e9 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/model/PersistableCarrierBooking.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/model/PersistableCarrierBooking.java @@ -5,11 +5,13 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; + import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Predicate; + import org.dcsa.conformance.core.state.JsonNodeMap; import org.dcsa.conformance.standards.booking.party.BookingState; diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java index ffbb7096..831c2648 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Carrier.java @@ -522,6 +522,20 @@ private String readCancelOperation(ConformanceRequest request) { return operation; } + private String readAmendedContent(ConformanceRequest request) { + var queryParams = request.queryParams(); + var operationParams = queryParams.get("amendedContent"); + if (operationParams == null || operationParams.isEmpty()) { + return "false"; + } + var operation = operationParams.iterator().next(); + if (operationParams.size() > 1 + || !(operation.equals("true") || operation.equals("false"))) { + return "!INVALID-VALUE!"; + } + return operation; + } + @SneakyThrows private ConformanceResponse _handlePutBookingRequest(ConformanceRequest request) { @@ -631,18 +645,36 @@ private ConformanceResponse returnBookingStatusResponse( } private ConformanceResponse _handleGetBookingRequest(ConformanceRequest request) { + var amendedContentRaw = readAmendedContent(request); + boolean amendedContent; + if (amendedContentRaw.equals("true") || amendedContentRaw.equals("false")) { + amendedContent = amendedContentRaw.equals("true"); + } else { + return return400(request, "The amendedContent queryParam must be used at most once and" + + " must be one of true or false"); + } var bookingReference = lastUrlSegment(request.url()); // bookingReference can either be a CBR or CBRR. var cbrr = cbrToCbrr.getOrDefault(bookingReference, bookingReference); var persistedBookingData = persistentMap.load(cbrr); + if (persistedBookingData != null) { var persistableCarrierBooking = PersistableCarrierBooking.fromPersistentStore(persistedBookingData); + JsonNode body; + if (amendedContent) { + body = persistableCarrierBooking.getAmendedBooking().orElse(null); + if (body == null) { + return return404(request, "No amended version of booking with reference: " + bookingReference); + } + } else { + body = persistableCarrierBooking.getBooking(); + } ConformanceResponse response = request.createResponse( 200, Map.of("Api-Version", List.of(apiVersion)), - new ConformanceMessageBody(persistableCarrierBooking.getBooking())); + new ConformanceMessageBody(body)); addOperatorLogEntry( "Responded to GET booking request '%s' (in state '%s')" .formatted(bookingReference, persistableCarrierBooking.getBookingState().wireName())); diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Shipper.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Shipper.java index 580af769..00d6a28a 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Shipper.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/party/Shipper.java @@ -59,6 +59,7 @@ protected Map, Consumer> getActionP return Map.ofEntries( Map.entry(UC1_Shipper_SubmitBookingRequestAction.class, this::sendBookingRequest), Map.entry(Shipper_GetBookingAction.class, this::getBookingRequest), + Map.entry(Shipper_GetAmendedBooking404Action.class, this::getBookingRequest), Map.entry(UC3_Shipper_SubmitUpdatedBookingRequestAction.class, this::sendUpdatedBooking), Map.entry(UC7_Shipper_SubmitBookingAmendment.class, this::sendUpdatedConfirmedBooking), Map.entry(UC12_Shipper_CancelEntireBookingAction.class, this::sendCancelEntireBooking)); @@ -67,8 +68,12 @@ protected Map, Consumer> getActionP private void getBookingRequest(JsonNode actionPrompt) { log.info("Shipper.getBookingRequest(%s)".formatted(actionPrompt.toPrettyString())); String cbrr = actionPrompt.get("cbrr").asText(); + boolean requestAmendment = actionPrompt.path("amendedContent").asBoolean(false); + Map> queryParams = requestAmendment + ? Map.of("amendedContent", List.of("true")) + : Collections.emptyMap(); - asyncCounterpartGet("/v2/bookings/" + cbrr); + asyncCounterpartGet("/v2/bookings/" + cbrr, queryParams); addOperatorLogEntry("Sent a GET request for booking with CBRR: %s".formatted(cbrr)); } diff --git a/core/src/main/java/org/dcsa/conformance/core/check/QueryParamCheck.java b/core/src/main/java/org/dcsa/conformance/core/check/QueryParamCheck.java new file mode 100644 index 00000000..1e9aa204 --- /dev/null +++ b/core/src/main/java/org/dcsa/conformance/core/check/QueryParamCheck.java @@ -0,0 +1,53 @@ +package org.dcsa.conformance.core.check; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Predicate; +import org.dcsa.conformance.core.traffic.ConformanceExchange; +import org.dcsa.conformance.core.traffic.HttpMessageType; + +public class QueryParamCheck extends ActionCheck { + private final String queryParamName; + private final String queryParamValue; + + public QueryParamCheck( + Predicate isRelevantForRoleName, UUID matchedExchangeUuid, String queryParamName, String queryParamValue) { + this("", isRelevantForRoleName, matchedExchangeUuid, queryParamName, queryParamValue); + } + + public QueryParamCheck( + String titlePrefix, + Predicate isRelevantForRoleName, + UUID matchedExchangeUuid, + String queryParamName, + String queryParamValue) { + super( + titlePrefix, + "The query param of the HTTP request is correct", + isRelevantForRoleName, + matchedExchangeUuid, + HttpMessageType.REQUEST); + this.queryParamName = queryParamName; + this.queryParamValue = queryParamValue; + } + + @Override + protected Set checkConformance(Function getExchangeByUuid) { + ConformanceExchange exchange = getExchangeByUuid.apply(matchedExchangeUuid); + if (exchange == null) return Set.of(); + var queryParams = exchange.getRequest().queryParams(); + var values = queryParams.get(queryParamName); + if (values == null || values.isEmpty()) { + return Set.of("Missing the query parameter '%s' (which should be set to '%s')".formatted(queryParamName, queryParamValue)); + } + if (values.size() != 1) { + return Set.of("The query parameter '%s' should be given exactly once".formatted(queryParamName)); + } + var actualValue = values.iterator().next(); + return queryParamValue.equals(actualValue) + ? Collections.emptySet() + : Set.of("The query parameter '%s' should have been '%s' but was '%s'".formatted(queryParamName, queryParamValue, actualValue)); + } +} diff --git a/core/src/main/java/org/dcsa/conformance/core/party/ConformanceParty.java b/core/src/main/java/org/dcsa/conformance/core/party/ConformanceParty.java index 32277d10..9e09ee84 100644 --- a/core/src/main/java/org/dcsa/conformance/core/party/ConformanceParty.java +++ b/core/src/main/java/org/dcsa/conformance/core/party/ConformanceParty.java @@ -148,12 +148,20 @@ protected void asyncCounterpartGet(String path) { asyncCounterpartGet(path, conformanceResponse -> {}); } + protected void asyncCounterpartGet(String path, Map> queryParams) { + asyncCounterpartGet(path, queryParams, conformanceResponse -> {}); + } + protected void asyncCounterpartGet(String path, Consumer responseCallback) { + asyncCounterpartGet(path, Collections.emptyMap(), responseCallback); + } + + protected void asyncCounterpartGet(String path, Map> queryParams, Consumer responseCallback) { asyncWebClient.accept( new ConformanceRequest( "GET", counterpartConfiguration.getUrl() + path, - Collections.emptyMap(), + queryParams, new ConformanceMessage( partyConfiguration.getName(), partyConfiguration.getRole(), From 1dceec95721c4369e5d47f98300610caaf1dff20 Mon Sep 17 00:00:00 2001 From: gj0dcsa <135594855+gj0dcsa@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:53:05 +0100 Subject: [PATCH 4/4] DSP updates no longer breaking the checks of earlier actions --- .../booking/action/BookingAction.java | 92 ++++++++++++------- ...arrier_SupplyScenarioParametersAction.java | 20 ---- .../core/scenario/OverwritingReference.java | 35 +++++++ 3 files changed, 93 insertions(+), 54 deletions(-) create mode 100644 core/src/main/java/org/dcsa/conformance/core/scenario/OverwritingReference.java diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/BookingAction.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/BookingAction.java index 30749e5a..e921051d 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/BookingAction.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/BookingAction.java @@ -2,8 +2,15 @@ import com.fasterxml.jackson.core.JsonPointer; import com.fasterxml.jackson.databind.JsonNode; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.node.ObjectNode; import org.dcsa.conformance.core.check.*; import org.dcsa.conformance.core.scenario.ConformanceAction; +import org.dcsa.conformance.core.scenario.OverwritingReference; import org.dcsa.conformance.core.traffic.ConformanceExchange; import org.dcsa.conformance.core.traffic.HttpMessageType; import org.dcsa.conformance.standards.booking.checks.CarrierBookingNotificationDataPayloadRequestConformanceCheck; @@ -12,14 +19,9 @@ import org.dcsa.conformance.standards.booking.party.CarrierScenarioParameters; import org.dcsa.conformance.standards.booking.party.DynamicScenarioParameters; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Stream; - public abstract class BookingAction extends ConformanceAction { protected final int expectedStatus; + private final OverwritingReference dspReference; public BookingAction( String sourcePartyName, @@ -29,6 +31,36 @@ public BookingAction( int expectedStatus) { super(sourcePartyName, targetPartyName, previousAction, actionTitle); this.expectedStatus = expectedStatus; + this.dspReference = + previousAction == null + ? new OverwritingReference<>(null, new DynamicScenarioParameters(null, null)) + : new OverwritingReference<>(previousAction.dspReference, null); + } + + @Override + public void reset() { + super.reset(); + if (previousAction != null) { + this.dspReference.set(null); + } + } + + @Override + public ObjectNode exportJsonState() { + ObjectNode jsonState = super.exportJsonState(); + if (dspReference.hasCurrentValue()) { + jsonState.set("currentDsp", dspReference.get().toJson()); + } + return jsonState; + } + + @Override + public void importJsonState(JsonNode jsonState) { + super.importJsonState(jsonState); + JsonNode dspNode = jsonState.get("currentDsp"); + if (dspNode != null) { + dspReference.set(DynamicScenarioParameters.fromJson(dspNode)); + } } protected BookingAction getPreviousBookingAction() { @@ -39,37 +71,32 @@ protected Consumer getCspConsumer() { return getPreviousBookingAction().getCspConsumer(); } - protected Consumer getDspConsumer() { - return getPreviousBookingAction().getDspConsumer(); - } - protected Supplier getCspSupplier() { return getPreviousBookingAction().getCspSupplier(); } protected Supplier getDspSupplier() { - return getPreviousBookingAction().getDspSupplier(); + return dspReference::get; } protected void storeCbrAndCbrrIfPresent(ConformanceExchange exchange) { + DynamicScenarioParameters dsp = dspReference.get(); + String oldCbrr = dsp.carrierBookingRequestReference(); + String oldCbr = dsp.carrierBookingReference(); + JsonNode responseJsonNode = exchange.getResponse().message().body().getJsonBody(); - if (getDspSupplier().get().carrierBookingRequestReference() == null) { - if (responseJsonNode.has("carrierBookingRequestReference")) { - getDspConsumer() - .accept( - new DynamicScenarioParameters( - responseJsonNode.get("carrierBookingRequestReference").asText(), - getDspSupplier().get().carrierBookingReference())); - } - } - if (getDspSupplier().get().carrierBookingReference() == null) { - if (responseJsonNode.has("carrierBookingReference")) { - getDspConsumer() - .accept( - new DynamicScenarioParameters( - getDspSupplier().get().carrierBookingRequestReference(), - responseJsonNode.get("carrierBookingReference").asText())); - } + String newCbrr = + responseJsonNode.has("carrierBookingRequestReference") + ? responseJsonNode.get("carrierBookingRequestReference").asText() + : oldCbrr; + String newCbr = + responseJsonNode.has("carrierBookingReference") + ? responseJsonNode.get("carrierBookingReference").asText() + : oldCbr; + + if ((newCbrr != null && !newCbrr.equals(oldCbrr)) + || (newCbr != null && !newCbr.equals(oldCbr))) { + dspReference.set(new DynamicScenarioParameters(newCbrr, newCbr)); } } @@ -79,8 +106,8 @@ protected Stream getNotificationChecks( BookingState bookingState, BookingState amendedBookingState) { String titlePrefix = "[Notification]"; - var cbr = getDspSupplier().get().carrierBookingReference(); - var cbrr = getDspSupplier().get().carrierBookingRequestReference(); + var cbr = dspReference.get().carrierBookingReference(); + var cbrr = dspReference.get().carrierBookingRequestReference(); return Stream.of( new HttpMethodCheck( titlePrefix, BookingRole::isCarrier, getMatchedNotificationExchangeUuid(), "POST"), @@ -92,10 +119,7 @@ protected Stream getNotificationChecks( new ResponseStatusCheck( titlePrefix, BookingRole::isShipper, getMatchedNotificationExchangeUuid(), 204), new CarrierBookingNotificationDataPayloadRequestConformanceCheck( - getMatchedNotificationExchangeUuid(), - bookingState, - amendedBookingState - ), + getMatchedNotificationExchangeUuid(), bookingState, amendedBookingState), new ApiHeaderCheck( titlePrefix, BookingRole::isCarrier, diff --git a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Carrier_SupplyScenarioParametersAction.java b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Carrier_SupplyScenarioParametersAction.java index f78b6d74..fb21b338 100644 --- a/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Carrier_SupplyScenarioParametersAction.java +++ b/booking/src/main/java/org/dcsa/conformance/standards/booking/action/Carrier_SupplyScenarioParametersAction.java @@ -5,11 +5,9 @@ import java.util.function.Consumer; import java.util.function.Supplier; import org.dcsa.conformance.standards.booking.party.CarrierScenarioParameters; -import org.dcsa.conformance.standards.booking.party.DynamicScenarioParameters; public class Carrier_SupplyScenarioParametersAction extends BookingAction { private CarrierScenarioParameters carrierScenarioParameters = null; - private DynamicScenarioParameters dynamicScenarioParameters = new DynamicScenarioParameters(null, null); public Carrier_SupplyScenarioParametersAction(String carrierPartyName) { super(carrierPartyName, null, null, "SupplyCSP", -1); @@ -19,7 +17,6 @@ public Carrier_SupplyScenarioParametersAction(String carrierPartyName) { public void reset() { super.reset(); carrierScenarioParameters = null; - dynamicScenarioParameters = new DynamicScenarioParameters(null, null); } @Override @@ -28,9 +25,6 @@ public ObjectNode exportJsonState() { if (carrierScenarioParameters != null) { jsonState.set("carrierScenarioParameters", carrierScenarioParameters.toJson()); } - if (dynamicScenarioParameters != null) { - jsonState.set("dynamicScenarioParameters", dynamicScenarioParameters.toJson()); - } return jsonState; } @@ -41,10 +35,6 @@ public void importJsonState(JsonNode jsonState) { if (cspNode != null) { carrierScenarioParameters = CarrierScenarioParameters.fromJson(cspNode); } - JsonNode dspNode = jsonState.get("dynamicScenarioParameters"); - if (dspNode != null) { - dynamicScenarioParameters = DynamicScenarioParameters.fromJson(dspNode); - } } @Override @@ -73,18 +63,8 @@ protected Consumer getCspConsumer() { return csp -> this.carrierScenarioParameters = csp; } - @Override - protected Consumer getDspConsumer() { - return dsp -> this.dynamicScenarioParameters = dsp; - } - @Override protected Supplier getCspSupplier() { return () -> carrierScenarioParameters; } - - @Override - protected Supplier getDspSupplier() { - return () -> dynamicScenarioParameters; - } } diff --git a/core/src/main/java/org/dcsa/conformance/core/scenario/OverwritingReference.java b/core/src/main/java/org/dcsa/conformance/core/scenario/OverwritingReference.java new file mode 100644 index 00000000..2f0119fb --- /dev/null +++ b/core/src/main/java/org/dcsa/conformance/core/scenario/OverwritingReference.java @@ -0,0 +1,35 @@ +package org.dcsa.conformance.core.scenario; + +import java.util.Objects; + +/** + * Reference that points to the value of a previous reference until a current value is set. + * To be used in conformance scenarios for storing values that are "learned" during later actions + * without affecting the later validations of earlier actions. + * + * @param the referenced value type + */ +public class OverwritingReference { + private final OverwritingReference previousReference; + private V currentValue; + + public OverwritingReference(OverwritingReference previousReference, V value) { + this.currentValue = value; + this.previousReference = value != null ? previousReference : Objects.requireNonNull(previousReference); + } + + public boolean hasCurrentValue() { + return currentValue != null; + } + + public V get() { + return currentValue != null ? currentValue : previousReference.get(); + } + + public void set(V value) { + if (previousReference == null && value == null) { + throw new IllegalArgumentException("Cannot empty a reference that has no previous reference to default to."); + } + this.currentValue = value; + } +}