package com.baiyue.eventbridge.service; import com.baiyue.eventbridge.config.EventDispatcherProperties; import com.baiyue.eventbridge.domain.BridgeDeliveryAttempt; import com.baiyue.eventbridge.domain.BridgeInbox; import com.baiyue.eventbridge.mapper.BridgeDeliveryAttemptMapper; import com.baiyue.eventbridge.mapper.BridgeInboxMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.test.util.ReflectionTestUtils; import java.net.URI; import java.time.Duration; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; class OverseasEventDispatcherTest { private final BridgeInboxMapper inboxMapper = mock(BridgeInboxMapper.class); private final BridgeDeliveryAttemptMapper attemptMapper = mock(BridgeDeliveryAttemptMapper.class); private final LastlegEventClient eventClient = mock(LastlegEventClient.class); private OverseasEventDispatcher dispatcher; private BridgeInbox event; @BeforeEach void setUp() { EventDispatcherProperties properties = new EventDispatcherProperties( URI.create("https://lastleg.internal/events"), 50, 3, Duration.ofSeconds(30), Duration.ofSeconds(5), Duration.ofMinutes(1), Duration.ofSeconds(3), Duration.ofSeconds(10)); dispatcher = new OverseasEventDispatcher(); ReflectionTestUtils.setField(dispatcher, "inboxMapper", inboxMapper); ReflectionTestUtils.setField(dispatcher, "attemptMapper", attemptMapper); ReflectionTestUtils.setField(dispatcher, "eventClient", eventClient); ReflectionTestUtils.setField(dispatcher, "properties", properties); event = new BridgeInbox(); event.setEventId("event-1"); event.setSourceRegion("CN"); event.setTargetRegion("OVERSEAS"); event.setRetryCount(0); when(inboxMapper.claim(eq("event-1"), anyString(), anyLong())).thenReturn(1); when(inboxMapper.selectById("event-1")).thenReturn(event); when(inboxMapper.nextAttemptNo("event-1")).thenReturn(1); } @Test void successfulDeliveryIsConfirmedAndAudited() throws Exception { when(eventClient.deliver(event)).thenReturn("200"); when(inboxMapper.markProcessed(eq("event-1"), anyString())).thenReturn(1); dispatcher.dispatchOne("event-1"); verify(inboxMapper).markProcessed(eq("event-1"), anyString()); assertThat(updatedAttempt().getStatus()).isEqualTo("DELIVERED"); } @Test void failedDeliveryIsScheduledForRetry() throws Exception { when(eventClient.deliver(event)).thenThrow(new IllegalStateException("network down")); dispatcher.dispatchOne("event-1"); verify(inboxMapper).markFailed(eq("event-1"), anyString(), eq(3), anyLong(), eq("network down")); assertThat(updatedAttempt().getStatus()).isEqualTo("RETRY"); } @Test void finalFailureIsMovedToDeadState() throws Exception { event.setRetryCount(2); when(eventClient.deliver(event)).thenThrow(new IllegalStateException("still down")); dispatcher.dispatchOne("event-1"); assertThat(updatedAttempt().getStatus()).isEqualTo("DEAD"); } @Test void eventClaimedByAnotherInstanceIsSkipped() { when(inboxMapper.claim(eq("event-1"), anyString(), anyLong())).thenReturn(0); dispatcher.dispatchOne("event-1"); verifyNoInteractions(eventClient); verify(attemptMapper, never()).insert(any(BridgeDeliveryAttempt.class)); } private BridgeDeliveryAttempt updatedAttempt() { ArgumentCaptor captor = ArgumentCaptor.forClass(BridgeDeliveryAttempt.class); verify(attemptMapper).updateById(captor.capture()); return captor.getValue(); } }