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(); } }