优化
This commit is contained in:
+48
-6
@@ -8,7 +8,7 @@ import com.baiyue.common.event.EventPayloadHash;
|
||||
import com.baiyue.common.event.FirstLegLoadingPlanCompletedV1;
|
||||
import com.baiyue.common.event.EventTypes;
|
||||
import com.baiyue.common.event.RegionCode;
|
||||
import com.baiyue.firstleg.client.EventBridgeClient;
|
||||
import com.baiyue.firstleg.client.LastlegEventClient;
|
||||
import com.baiyue.firstleg.entity.InboundOutboxEntity;
|
||||
import com.baiyue.firstleg.repository.LoadingCompletedOutboxRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -31,7 +31,7 @@ import static org.mockito.Mockito.*;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LoadingCompletedOutboxRelayTest {
|
||||
@Mock private LoadingCompletedOutboxRepository repository;
|
||||
@Mock private EventBridgeClient client;
|
||||
@Mock private LastlegEventClient client;
|
||||
private ObjectMapper objectMapper;
|
||||
private LoadingCompletedOutboxRelay relay;
|
||||
|
||||
@@ -44,18 +44,18 @@ class LoadingCompletedOutboxRelayTest {
|
||||
ReflectionTestUtils.setField(relay, "objectMapper", objectMapper);
|
||||
ReflectionTestUtils.setField(relay, "internalToken", "internal-token");
|
||||
ReflectionTestUtils.setField(relay, "leaseSeconds", 60L);
|
||||
ReflectionTestUtils.setField(relay, "retrySeconds", 30L);
|
||||
ReflectionTestUtils.setField(relay, "baseDelaySeconds", 5L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void claimsExpiredLeasesAndPublishesAfterOverseasBridgeAcknowledgement() throws Exception {
|
||||
void claimsExpiredLeasesAndPublishesAfterConsumerAcknowledgement() throws Exception {
|
||||
EventEnvelope event = event();
|
||||
InboundOutboxEntity row = row(event);
|
||||
when(repository.pending(eq(50), any(), any())).thenReturn(List.of(row));
|
||||
when(repository.claim(eq(9001L), anyString(), any(), any())).thenReturn(true);
|
||||
when(repository.claimed(eq(9001L), anyString())).thenReturn(row);
|
||||
when(client.receive(eq("internal-token"), any(EventEnvelope.class))).thenReturn(ApiResponse.success(
|
||||
new EventBridgeClient.Acknowledgement(event.eventId(), "已接收", "2026-08-20T03:01:00")));
|
||||
com.baiyue.common.event.DeliveryAck.of(event, com.baiyue.common.event.DeliveryAck.Outcome.APPLIED)));
|
||||
|
||||
relay.relay();
|
||||
|
||||
@@ -110,11 +110,53 @@ class LoadingCompletedOutboxRelayTest {
|
||||
relay.relay();
|
||||
|
||||
verify(repository).retry(eq(9001L), anyString(), any(LocalDateTime.class), eq(
|
||||
"IllegalStateException: 事件桥拒绝头程跨区事件: code=S0503, message=bridge inbox unavailable, "
|
||||
"IllegalStateException: Lastleg拒绝头程业务事件: code=S0503, message=bridge inbox unavailable, "
|
||||
+ "requestId=request-123, traceId=trace-456"));
|
||||
verify(repository, never()).published(anyLong(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchingEventIdWithWrongPayloadHashDoesNotPublish() throws Exception {
|
||||
EventEnvelope event = event();
|
||||
InboundOutboxEntity row = row(event);
|
||||
when(repository.pending(eq(50), any(), any())).thenReturn(List.of(row));
|
||||
when(repository.claim(eq(9001L), anyString(), any(), any())).thenReturn(true);
|
||||
when(repository.claimed(eq(9001L), anyString())).thenReturn(row);
|
||||
when(client.receive(eq("internal-token"), any(EventEnvelope.class))).thenReturn(ApiResponse.success(
|
||||
new com.baiyue.common.event.DeliveryAck(event.eventId(), "sha256:" + "0".repeat(64),
|
||||
com.baiyue.common.event.DeliveryAck.Outcome.APPLIED)));
|
||||
relay.relay();
|
||||
verify(repository, never()).published(anyLong(), anyString(), any());
|
||||
verify(repository).retry(eq(9001L), anyString(), any(), contains("ACK does not match"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void twelfthFailureGoesDeadInsteadOfSchedulingAnotherRetry() throws Exception {
|
||||
InboundOutboxEntity row = row(event());
|
||||
row.setRetryCount(11);
|
||||
when(repository.pending(eq(50), any(), any())).thenReturn(List.of(row));
|
||||
when(repository.claim(eq(9001L), anyString(), any(), any())).thenReturn(true);
|
||||
when(repository.claimed(eq(9001L), anyString())).thenReturn(row);
|
||||
when(client.receive(anyString(), any())).thenThrow(new IllegalStateException("consumer unavailable"));
|
||||
relay.relay();
|
||||
verify(repository).dead(eq(9001L), anyString(), eq(12), contains("consumer unavailable"));
|
||||
verify(repository, never()).retry(anyLong(), anyString(), any(), anyString());
|
||||
verify(repository, never()).published(anyLong(), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void recoveredExhaustedLeaseDoesNotSendAgain() throws Exception {
|
||||
InboundOutboxEntity row = row(event());
|
||||
row.setRetryCount(12);
|
||||
when(repository.pending(eq(50), any(), any())).thenReturn(List.of(row));
|
||||
when(repository.claim(eq(9001L), anyString(), any(), any())).thenReturn(true);
|
||||
when(repository.claimed(eq(9001L), anyString())).thenReturn(row);
|
||||
relay.relay();
|
||||
verifyNoInteractions(client);
|
||||
verify(repository).dead(eq(9001L), anyString(), eq(12), contains("租约超时"));
|
||||
verify(repository, never()).retry(anyLong(), anyString(), any(), anyString());
|
||||
}
|
||||
|
||||
private InboundOutboxEntity row(EventEnvelope event) throws Exception {
|
||||
InboundOutboxEntity row = new InboundOutboxEntity();
|
||||
row.setId(9001L);
|
||||
|
||||
+14
-2
@@ -44,6 +44,7 @@ class OverseasContainerEventConsumerTest {
|
||||
void derivesEstimatedEmptyReportedAtFromUnloadCompletionAndPersistsInbox() {
|
||||
ContainerBookingEntity container = container();
|
||||
when(containers.selectActiveBookingForUpdate(1001L)).thenReturn(container);
|
||||
when(containers.applyEmptyReport(anyLong(), any(), any(), any(), any(), anyLong())).thenReturn(1);
|
||||
Instant completedAt = Instant.parse("2026-08-20T03:00:00Z");
|
||||
Instant uploadedAt = Instant.parse("2026-08-20T02:30:00Z");
|
||||
|
||||
@@ -67,10 +68,10 @@ class OverseasContainerEventConsumerTest {
|
||||
EventEnvelope event = event("00000000-0000-4000-8000-000000000002", 7, payload);
|
||||
ContainerEventInboxEntity existing = new ContainerEventInboxEntity();
|
||||
existing.setPayloadHash(event.payloadHash());
|
||||
existing.setStatus("PROCESSED");
|
||||
when(inbox.selectById(event.eventId())).thenReturn(existing);
|
||||
when(containers.selectActiveBookingForUpdate(1001L)).thenReturn(container());
|
||||
|
||||
consumer.consume(event);
|
||||
assertThat(consumer.consume(event).outcome()).isEqualTo(com.baiyue.common.event.DeliveryAck.Outcome.DUPLICATE);
|
||||
|
||||
verify(containers, never()).applyEmptyReport(anyLong(), any(), any(), any(), any(), anyLong());
|
||||
verify(inbox, never()).insert(any(ContainerEventInboxEntity.class));
|
||||
@@ -91,6 +92,17 @@ class OverseasContainerEventConsumerTest {
|
||||
verifyNoInteractions(containers, inbox);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unappliedUnloadCannotBeAcknowledgedAsSuccessful() {
|
||||
when(containers.selectActiveBookingForUpdate(1001L)).thenReturn(container());
|
||||
EventEnvelope event = event("00000000-0000-4000-8000-000000000004", 7,
|
||||
new OverseasContainerUnloadedV1("1001", "ZCSU7881970", Instant.parse("2026-08-20T03:00:00Z"),
|
||||
Instant.parse("2026-08-20T02:30:00Z"), null));
|
||||
assertThatThrownBy(() -> consumer.consume(event)).isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("卸柜事实未写入");
|
||||
verify(inbox, never()).updateById(any(ContainerEventInboxEntity.class));
|
||||
}
|
||||
|
||||
private EventEnvelope event(String eventId, long aggregateVersion, OverseasContainerUnloadedV1 payload) {
|
||||
var node = objectMapper.valueToTree(payload);
|
||||
String hash = EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node));
|
||||
|
||||
+4
-2
@@ -47,7 +47,8 @@ class OverseasInboundOrderDetainedConsumerTest {
|
||||
when(inboundOrders.selectActiveByIdForUpdate(1001L)).thenReturn(inbound);
|
||||
when(inboundOrders.applyOverseasHold(88L, 1001L, true, "等待查验", 7L, 66L)).thenReturn(1);
|
||||
|
||||
consumer.consume(event(7, true, "等待查验"));
|
||||
assertThat(consumer.consume(event(7, true, "等待查验")).outcome())
|
||||
.isEqualTo(com.baiyue.common.event.DeliveryAck.Outcome.APPLIED);
|
||||
|
||||
verify(inboundOrders).applyOverseasHold(88L, 1001L, true, "等待查验", 7L, 66L);
|
||||
ArgumentCaptor<InboundHoldRecordEntity> history = ArgumentCaptor.forClass(InboundHoldRecordEntity.class);
|
||||
@@ -61,7 +62,8 @@ class OverseasInboundOrderDetainedConsumerTest {
|
||||
void staleReleaseCannotOverwriteNewerHoldState() {
|
||||
when(inboundOrders.selectActiveByIdForUpdate(1001L)).thenReturn(inbound(9L));
|
||||
|
||||
consumer.consume(event(7, false, "解除"));
|
||||
assertThat(consumer.consume(event(7, false, "解除")).outcome())
|
||||
.isEqualTo(com.baiyue.common.event.DeliveryAck.Outcome.STALE);
|
||||
|
||||
verify(inboundOrders, never()).applyOverseasHold(anyLong(), anyLong(), anyBoolean(), any(), anyLong(), anyLong());
|
||||
verifyNoInteractions(holdRecords);
|
||||
|
||||
Reference in New Issue
Block a user