暂存提交

This commit is contained in:
2026-08-19 16:24:02 +08:00
parent f5c4bf130a
commit 6acb6178da
354 changed files with 25000 additions and 198 deletions
@@ -6,16 +6,8 @@ import com.baiyue.common.event.EventPayloadHash;
import com.baiyue.common.event.RegionCode;
import com.baiyue.eventbridge.domain.BridgeInbox;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.Instant;
@@ -25,35 +17,17 @@ 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.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@DisplayName("跨区域事件收件箱")
@SpringJUnitConfig(classes = {EventInboxService.class, EventInboxServiceTest.TestConfig.class})
@TestPropertySource(properties = "baiyue.region=OVERSEAS")
class EventInboxServiceTest {
@Resource
private ObjectMapper objectMapper;
@MockBean
private InboxStore store;
@Resource
private EventInboxService service;
@Configuration(proxyBeanMethods = false)
static class TestConfig {
@Bean
ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
@AfterEach
void resetRegion() {
ReflectionTestUtils.setField(service, "localRegion", "OVERSEAS");
}
private final ObjectMapper objectMapper = new ObjectMapper();
private final InboxStore store = mock(InboxStore.class);
private final EventInboxService service = service("OVERSEAS");
@Test
void firstEventPersistsAndReturnsReceived() {
void 首次事件落库并返回已接收() {
EventEnvelope envelope = envelope("sha256:placeholder");
envelope = withHash(envelope);
@@ -62,19 +36,31 @@ class EventInboxServiceTest {
}
@Test
void identicalEventAndDigestDoesNotPersistAgain() {
void 数字规范化上线前的历史摘要仍可接收() {
EventEnvelope source = envelope("sha256:placeholder");
source.payload().withObject("/snapshot").put("weight", 30.0).put("volume", 0.3150);
String legacyHash = EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalizeLegacy(source.payload()));
EventEnvelope envelope = new EventEnvelope(source.eventId(), source.eventType(), source.eventVersion(),
source.occurredAt(), source.sourceRegion(), source.targetRegion(), source.tenantId(),
source.aggregateType(), source.aggregateId(), source.aggregateVersion(), source.handoverVersion(),
source.traceId(), source.correlationId(), source.causationId(), source.payload(), legacyHash);
assertThat(service.receive(envelope).result()).isEqualTo("已接收");
}
@Test
void 相同事件和摘要重复接收不再次写入() {
EventEnvelope envelope = withHash(envelope("sha256:placeholder"));
BridgeInbox existing = existing(envelope, envelope.payloadHash());
BridgeInbox existing = existing(envelope.payloadHash());
when(store.findByEventId(envelope.eventId())).thenReturn(existing);
assertThat(service.receive(envelope).result()).isEqualTo("重复");
}
@Test
void sameEventWithDifferentDigestRejectsOverwrite() {
void 相同事件但摘要不同时拒绝覆盖() {
EventEnvelope envelope = withHash(envelope("sha256:placeholder"));
when(store.findByEventId(envelope.eventId()))
.thenReturn(existing(envelope, "sha256:" + "0".repeat(64)));
when(store.findByEventId(envelope.eventId())).thenReturn(existing("sha256:" + "0".repeat(64)));
assertThatThrownBy(() -> service.receive(envelope))
.isInstanceOf(IllegalArgumentException.class)
@@ -82,45 +68,20 @@ class EventInboxServiceTest {
}
@Test
void sameEventAndPayloadWithDifferentTenantRejectsOverwrite() {
EventEnvelope original = withHash(envelope("sha256:placeholder"));
EventEnvelope otherTenant = withTenant(original, "20002");
when(store.findByEventId(otherTenant.eventId()))
.thenReturn(existing(original, original.payloadHash()));
assertThatThrownBy(() -> service.receive(otherTenant))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("信封身份不一致");
}
@Test
void sameEventAndPayloadWithDifferentAggregateVersionRejectsOverwrite() {
EventEnvelope original = withHash(envelope("sha256:placeholder"));
EventEnvelope otherEnvelope = withAggregateVersion(original, 2L);
when(store.findByEventId(otherEnvelope.eventId()))
.thenReturn(existing(original, original.payloadHash()));
assertThatThrownBy(() -> service.receive(otherEnvelope))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("信封身份不一致");
}
@Test
void duplicateInsertAfterRaceIsTreatedAsDuplicate() {
void 并发插入冲突后按既有记录判定为重复() {
EventEnvelope envelope = withHash(envelope("sha256:placeholder"));
when(store.findByEventId(envelope.eventId()))
.thenReturn(null, existing(envelope, envelope.payloadHash()));
when(store.findByEventId(envelope.eventId())).thenReturn(null, existing(envelope.payloadHash()));
doThrow(new DuplicateKeyException("并发重复")).when(store).insert(any());
assertThat(service.receive(envelope).result()).isEqualTo("重复");
}
@Test
void targetRegionDifferentFromCurrentDeploymentIsRejected() {
ReflectionTestUtils.setField(service, "localRegion", "CN");
void 目标区域与当前部署区域不一致时拒绝接收() {
EventInboxService cnService = service("CN");
EventEnvelope envelope = withHash(envelope("sha256:placeholder"));
assertThatThrownBy(() -> service.receive(envelope))
assertThatThrownBy(() -> cnService.receive(envelope))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("目标区域");
}
@@ -132,6 +93,14 @@ class EventInboxServiceTest {
objectMapper.createObjectNode().put("orderId", "90001"), hash);
}
private EventInboxService service(String region) {
EventInboxService service = new EventInboxService();
ReflectionTestUtils.setField(service, "inboxStore", store);
ReflectionTestUtils.setField(service, "objectMapper", objectMapper);
ReflectionTestUtils.setField(service, "localRegion", region);
return service;
}
private EventEnvelope withHash(EventEnvelope source) {
String hash = EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(source.payload()));
return new EventEnvelope(source.eventId(), source.eventType(), source.eventVersion(), source.occurredAt(),
@@ -140,32 +109,9 @@ class EventInboxServiceTest {
source.correlationId(), source.causationId(), source.payload(), hash);
}
private EventEnvelope withTenant(EventEnvelope source, String tenantId) {
return new EventEnvelope(source.eventId(), source.eventType(), source.eventVersion(), source.occurredAt(),
source.sourceRegion(), source.targetRegion(), tenantId, source.aggregateType(),
source.aggregateId(), source.aggregateVersion(), source.handoverVersion(), source.traceId(),
source.correlationId(), source.causationId(), source.payload(), source.payloadHash());
}
private EventEnvelope withAggregateVersion(EventEnvelope source, Long aggregateVersion) {
return new EventEnvelope(source.eventId(), source.eventType(), source.eventVersion(), source.occurredAt(),
source.sourceRegion(), source.targetRegion(), source.tenantId(), source.aggregateType(),
source.aggregateId(), aggregateVersion, source.handoverVersion(), source.traceId(),
source.correlationId(), source.causationId(), source.payload(), source.payloadHash());
}
private BridgeInbox existing(EventEnvelope envelope, String hash) {
private BridgeInbox existing(String hash) {
BridgeInbox inbox = new BridgeInbox();
inbox.setEventId(envelope.eventId());
inbox.setTenantId(envelope.tenantId());
inbox.setEventType(envelope.eventType());
inbox.setEventVersion(envelope.eventVersion());
inbox.setSourceRegion(envelope.sourceRegion().name());
inbox.setTargetRegion(envelope.targetRegion().name());
inbox.setAggregateType(envelope.aggregateType());
inbox.setAggregateId(envelope.aggregateId());
inbox.setAggregateVersion(envelope.aggregateVersion());
inbox.setHandoverVersion(envelope.handoverVersion());
inbox.setEventId("550e8400-e29b-41d4-a716-446655440000");
inbox.setPayloadHash(hash);
inbox.setReceivedAt(LocalDateTime.of(2026, 7, 29, 18, 0));
return inbox;