1
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
package com.baiyue.lastleg.inboundplan.pda.service;
|
||||
|
||||
import com.baiyue.common.event.EventEnvelope;
|
||||
import com.baiyue.common.event.EventTypes;
|
||||
import com.baiyue.common.event.OverseasContainerUnloadedV1;
|
||||
import com.baiyue.lastleg.entity.LastlegEventOutbox;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlan;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.InboundPdaTask;
|
||||
import com.baiyue.lastleg.mapper.LastlegEventOutboxMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class InboundUnloadCompletedOutboxServiceTest {
|
||||
private final LastlegEventOutboxMapper mapper = mock(LastlegEventOutboxMapper.class);
|
||||
private final LastlegOrderMapper orders = mock(LastlegOrderMapper.class);
|
||||
private final ObjectMapper json = new ObjectMapper().findAndRegisterModules();
|
||||
private final InboundUnloadCompletedOutboxService service = new InboundUnloadCompletedOutboxService();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
ReflectionTestUtils.setField(service, "mapper", mapper);
|
||||
ReflectionTestUtils.setField(service, "orders", orders);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", json);
|
||||
}
|
||||
|
||||
@Test
|
||||
void freezesContainerCompletionSeparatelyFromPhotoUpload() throws Exception {
|
||||
Instant photoUploadedAt = Instant.parse("2026-08-24T08:00:00Z");
|
||||
Instant containerCompletedAt = Instant.parse("2026-08-24T09:30:00Z");
|
||||
|
||||
service.publishContainerCompleted("100", "booking-1", "MSCU1234567",
|
||||
containerCompletedAt, photoUploadedAt, 7);
|
||||
|
||||
ArgumentCaptor<LastlegEventOutbox> captured = ArgumentCaptor.forClass(LastlegEventOutbox.class);
|
||||
verify(mapper).insert(captured.capture());
|
||||
LastlegEventOutbox row = captured.getValue();
|
||||
EventEnvelope envelope = json.readValue(row.getPayloadJson(), EventEnvelope.class);
|
||||
OverseasContainerUnloadedV1 payload = json.treeToValue(
|
||||
envelope.payload(), OverseasContainerUnloadedV1.class);
|
||||
assertThat(payload.unloadingCompletedAt()).isEqualTo(containerCompletedAt);
|
||||
assertThat(payload.unloadingPhotoUploadedAt()).isEqualTo(photoUploadedAt);
|
||||
assertThat(row.getEventKey()).isEqualTo(EventTypes.OVERSEAS_CONTAINER_UNLOADED + ":booking-1");
|
||||
assertThat(envelope.aggregateId()).isEqualTo("booking-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacySinglePlanEntryUsesTaskCompletionTime() throws Exception {
|
||||
InboundPlan plan = new InboundPlan();
|
||||
plan.setSourceOrderId(11L);
|
||||
plan.setContainerNo("MSCU1234567");
|
||||
InboundPdaTask task = new InboundPdaTask();
|
||||
task.setCustomerId("100");
|
||||
task.setCompletedAt(LocalDateTime.parse("2026-08-24T09:30:00"));
|
||||
task.setUnloadPhotoUploadedAt(LocalDateTime.parse("2026-08-24T08:00:00"));
|
||||
task.setVersion(3);
|
||||
LastlegOrder order = new LastlegOrder();
|
||||
order.setSourceContainerBookingId("booking-1");
|
||||
when(orders.selectById(11L)).thenReturn(order);
|
||||
|
||||
service.publish(plan, task, List.of(1L));
|
||||
|
||||
ArgumentCaptor<LastlegEventOutbox> captured = ArgumentCaptor.forClass(LastlegEventOutbox.class);
|
||||
verify(mapper).insert(captured.capture());
|
||||
EventEnvelope envelope = json.readValue(captured.getValue().getPayloadJson(), EventEnvelope.class);
|
||||
OverseasContainerUnloadedV1 payload = json.treeToValue(
|
||||
envelope.payload(), OverseasContainerUnloadedV1.class);
|
||||
assertThat(payload.unloadingCompletedAt()).isEqualTo(Instant.parse("2026-08-24T09:30:00Z"));
|
||||
assertThat(payload.unloadingPhotoUploadedAt()).isEqualTo(Instant.parse("2026-08-24T08:00:00Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatedContainerCompletionDoesNotPublishAnotherEvent() {
|
||||
LastlegEventOutbox existing = existing("100", "booking-1", "MSCU1234567");
|
||||
when(mapper.findByEventKey(EventTypes.OVERSEAS_CONTAINER_UNLOADED + ":booking-1"))
|
||||
.thenReturn(existing);
|
||||
|
||||
service.publishContainerCompleted("100", "booking-1", "MSCU1234567",
|
||||
Instant.parse("2026-08-24T10:00:00Z"), Instant.parse("2026-08-24T08:00:00Z"), 8);
|
||||
|
||||
verify(mapper, never()).insert(any(LastlegEventOutbox.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameIdempotencyKeyCannotRepresentAnotherPhysicalContainer() {
|
||||
LastlegEventOutbox existing = existing("100", "booking-1", "MSCU1234567");
|
||||
when(mapper.findByEventKey(EventTypes.OVERSEAS_CONTAINER_UNLOADED + ":booking-1"))
|
||||
.thenReturn(existing);
|
||||
|
||||
assertThatThrownBy(() -> service.publishContainerCompleted("100", "booking-1", "TGHU7654321",
|
||||
Instant.parse("2026-08-24T10:00:00Z"), Instant.parse("2026-08-24T08:00:00Z"), 8))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("conflicts with another container");
|
||||
}
|
||||
|
||||
private LastlegEventOutbox existing(String customerId, String bookingId, String containerNo) {
|
||||
OverseasContainerUnloadedV1 payload = new OverseasContainerUnloadedV1(bookingId, containerNo,
|
||||
Instant.parse("2026-08-24T09:30:00Z"), Instant.parse("2026-08-24T08:00:00Z"), null);
|
||||
EventEnvelope envelope = new EventEnvelope("123e4567-e89b-12d3-a456-426614174000",
|
||||
EventTypes.OVERSEAS_CONTAINER_UNLOADED, 1, Instant.parse("2026-08-24T09:30:01Z"),
|
||||
com.baiyue.common.event.RegionCode.OVERSEAS, com.baiyue.common.event.RegionCode.CN,
|
||||
customerId, "CONTAINER_BOOKING", bookingId, 7, 1L,
|
||||
"123e4567-e89b-12d3-a456-426614174001",
|
||||
EventTypes.OVERSEAS_CONTAINER_UNLOADED + ':' + bookingId, null,
|
||||
json.valueToTree(payload), "sha256:" + "0".repeat(64));
|
||||
LastlegEventOutbox row = new LastlegEventOutbox();
|
||||
row.setCustomerId(customerId);
|
||||
row.setAggregateId(bookingId);
|
||||
row.setPayloadJson(write(envelope));
|
||||
return row;
|
||||
}
|
||||
|
||||
private String write(Object value) {
|
||||
try {
|
||||
return json.writeValueAsString(value);
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user