132 lines
7.7 KiB
Java
132 lines
7.7 KiB
Java
package com.baiyue.integration.provider;
|
|
|
|
import com.baiyue.integration.dto.CarrierScheduleDtos.QueryRequest;
|
|
import com.baiyue.integration.provider.MatsonTrackingClient.TrackingResponse;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import org.junit.jupiter.api.Test;
|
|
import java.time.Instant;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.mockito.Mockito.mock;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
class MatsonCarrierScheduleProviderAdapterTest {
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
@Test
|
|
void appendedEventAndChangedEtaKeepOldNodeAndHoldFactsButChangeResponseAudit() throws Exception {
|
|
String raw = """
|
|
{"bookingNumber":"MATSON-REAL","containerNumber":"MATU459895","checkDigit":"6",
|
|
"originPort":"NINGBO","destPort":"LONG BEACH","originCode":"NGB","destCode":"LAX","vesselETA":"08/24/26",
|
|
"events":[{"date":1786940100000,"location":"NINGBO","status":"INGATE"},
|
|
{"date":1787160480000,"location":"","status":"CONTAINER ON HOLD - Customs Clearance"}]}
|
|
""";
|
|
String next = raw.replace("08/24/26", "08/25/26").replace("]}", ",{\"date\":1787033640000,\"location\":\"NINGBO\",\"status\":\"LOAD TO VESSEL\"}]}");
|
|
var adapter = new MatsonCarrierScheduleProviderAdapter(mock(MatsonTrackingClient.class));
|
|
var query = new QueryRequest("MATSON", "CONTAINER_NO", "MATU4598956");
|
|
var time = java.time.LocalDateTime.of(2026, 8, 21, 12, 0);
|
|
var before = adapter.observations(query, objectMapper.readTree(raw), raw, time);
|
|
var after = adapter.observations(query, objectMapper.readTree(next), next, time.plusHours(3));
|
|
CarrierResponseAuditAssertions.stableSharedFacts(before, after, raw, next);
|
|
assertThat(after).hasSize(before.size() + 1);
|
|
var hold = adapter.exceptions(query, objectMapper.readTree(raw), raw, time).get(0);
|
|
var replay = adapter.exceptions(query, objectMapper.readTree(next), next, time.plusHours(3)).get(0);
|
|
assertThat(replay).usingRecursiveComparison().ignoringFields("observedAt", "rawPayloadHash").isEqualTo(hold);
|
|
assertThat(replay.rawPayloadHash()).isNotEqualTo(hold.rawPayloadHash());
|
|
}
|
|
|
|
@Test
|
|
void realMatu4646247FixtureProducesTwoActualsAndEtaOnly() throws Exception {
|
|
String raw = """
|
|
{"bookingNumber":"MATSON-REAL","containerNumber":"MATU464624","checkDigit":"7",
|
|
"originPort":"LONG BEACH","destPort":"HONOLULU","originCode":"LAX","destCode":"HIL",
|
|
"loadPort":"LONG BEACH","dischargePort":"HONOLULU","vesselETA":"08/25/26","events":[
|
|
{"date":%d,"location":"LONG BEACH","status":"INGATE"},
|
|
{"date":%d,"location":"LONG BEACH","status":"LOAD TO VESSEL"}]}
|
|
""".formatted(Instant.parse("2026-08-18T19:15:00Z").toEpochMilli(),
|
|
Instant.parse("2026-08-18T22:12:00Z").toEpochMilli());
|
|
MatsonTrackingClient client = mock(MatsonTrackingClient.class);
|
|
when(client.configured()).thenReturn(true);
|
|
when(client.query(new QueryRequest("MATSON", "CONTAINER_NO", "MATU4646247")))
|
|
.thenReturn(new TrackingResponse(objectMapper.readTree(raw), raw));
|
|
|
|
var result = new MatsonCarrierScheduleProviderAdapter(client)
|
|
.query(new QueryRequest("MATSON", "CONTAINER_NO", "MATU4646247"));
|
|
|
|
assertThat(result.observations()).extracting(value -> value.standardNode())
|
|
.containsExactlyInAnyOrder("AT_ORIGIN_PORT", "LOADED_ON_VESSEL", "ARRIVED_DESTINATION_PORT");
|
|
assertThat(result.observations()).filteredOn(value -> value.actualAt() != null).hasSize(2);
|
|
assertThat(result.observations()).filteredOn(value -> value.estimatedAt() != null).singleElement();
|
|
assertThat(result.observations()).extracting(value -> value.observedAt())
|
|
.containsOnly(result.observations().get(0).observedAt());
|
|
assertThat(result.exceptions()).isEmpty();
|
|
}
|
|
|
|
@Test
|
|
void conservativelyMapsKnownMatsonEventsWithoutTreatingLoadAsDeparture() throws Exception {
|
|
String raw = """
|
|
{"bookingNumber":"9974749","containerNumber":"MATU459895","checkDigit":"6",
|
|
"originPort":"NINGBO","destPort":"LONG BEACH","originCode":"NGB","destCode":"LAX",
|
|
"vesselETA":"08/24/26",
|
|
"loadPort":"NINGBO","dischargePort":"LONG BEACH","events":[
|
|
{"date":1787160480000,"location":"","status":"CONTAINER ON HOLD - Customs Clearance"},
|
|
{"date":1787033640000,"location":"NINGBO","status":"LOAD TO VESSEL"},
|
|
{"date":1786940100000,"location":"NINGBO","status":"INGATE"}]}
|
|
""";
|
|
MatsonTrackingClient client = mock(MatsonTrackingClient.class);
|
|
when(client.configured()).thenReturn(true);
|
|
when(client.query(new QueryRequest("MATSON", "CONTAINER_NO", "MATU4598956")))
|
|
.thenReturn(new TrackingResponse(objectMapper.readTree(raw), raw));
|
|
MatsonCarrierScheduleProviderAdapter adapter = new MatsonCarrierScheduleProviderAdapter(client);
|
|
|
|
var result = adapter.query(new QueryRequest("MATSON", "CONTAINER_NO", "MATU4598956"));
|
|
|
|
assertThat(result.outcome()).isEqualTo("SUCCESS");
|
|
assertThat(result.observations()).extracting(value -> value.standardNode())
|
|
.containsExactlyInAnyOrder("AT_ORIGIN_PORT", "LOADED_ON_VESSEL", "ARRIVED_DESTINATION_PORT");
|
|
assertThat(result.observations()).filteredOn(value -> "ARRIVED_DESTINATION_PORT".equals(value.standardNode()))
|
|
.singleElement().satisfies(value -> {
|
|
assertThat(value.estimatedAt()).isNotNull();
|
|
assertThat(value.actualAt()).isNull();
|
|
});
|
|
assertThat(result.exceptions()).singleElement().satisfies(value -> {
|
|
assertThat(value.exceptionCode()).isEqualTo("CUSTOMS_HOLD");
|
|
assertThat(value.active()).isTrue();
|
|
assertThat(value.providerStatus()).isEqualTo("CONTAINER ON HOLD - Customs Clearance");
|
|
});
|
|
}
|
|
|
|
@Test
|
|
void mapsDestinationDischargeAndAvailabilityWithoutUsingEtaAsActualArrival() {
|
|
assertThat(MatsonCarrierScheduleProviderAdapter.standardNode("LOAD TO VESSEL"))
|
|
.isEqualTo("LOADED_ON_VESSEL");
|
|
assertThat(MatsonCarrierScheduleProviderAdapter.standardNode("DISCHARGE FROM VESSEL"))
|
|
.isEqualTo("DISCHARGED_FROM_VESSEL");
|
|
assertThat(MatsonCarrierScheduleProviderAdapter.standardNode("AVAILABLE FOR PICKUP"))
|
|
.isEqualTo("PICKUP_READY");
|
|
assertThat(MatsonCarrierScheduleProviderAdapter.standardNode("OUTGATE")).isEqualTo("PICKED_UP");
|
|
assertThat(MatsonCarrierScheduleProviderAdapter.standardNode("EMPTY RETURNED"))
|
|
.isEqualTo("EMPTY_RETURNED");
|
|
assertThat(MatsonCarrierScheduleProviderAdapter.standardNode("PICKUP APPOINTMENT"))
|
|
.isNull();
|
|
assertThat(MatsonCarrierScheduleProviderAdapter.standardNode("CONTAINER ON HOLD - Customs Clearance"))
|
|
.isNull();
|
|
assertThat(MatsonCarrierScheduleProviderAdapter.exceptionCode(
|
|
"CONTAINER ON HOLD - Customs Clearance")).isEqualTo("CUSTOMS_HOLD");
|
|
}
|
|
|
|
@Test
|
|
void emptyBusinessObjectReturnsNoFacts() throws Exception {
|
|
String raw = "{}";
|
|
MatsonTrackingClient client = mock(MatsonTrackingClient.class);
|
|
when(client.configured()).thenReturn(true);
|
|
when(client.query(new QueryRequest("MATSON", "CONTAINER_NO", "MATU5164086")))
|
|
.thenReturn(new TrackingResponse(objectMapper.readTree(raw), raw));
|
|
var result = new MatsonCarrierScheduleProviderAdapter(client)
|
|
.query(new QueryRequest("MATSON", "CONTAINER_NO", "MATU5164086"));
|
|
assertThat(result.outcome()).isEqualTo("NO_CHANGE");
|
|
assertThat(result.observations()).isEmpty();
|
|
assertThat(result.exceptions()).isEmpty();
|
|
}
|
|
}
|