This commit is contained in:
2026-09-29 18:47:37 +08:00
parent 2c60a29e0f
commit c2927ab9a6
291 changed files with 4008 additions and 6298 deletions
@@ -11,6 +11,31 @@ import static org.assertj.core.api.Assertions.*;
class EventDeliveryFeignConfigurationTest {
interface Endpoint { @RequestLine("POST /events") String send(); }
interface TypedEndpoint { @RequestLine("POST /events") com.baiyue.common.core.api.ApiResponse<Map<String, Object>> send(); }
@Test void wireCodeMustBeAStringAndAmbiguousJsonCannotBeAcknowledged() {
for (String body : List.of("{\"code\":200}", "{\"code\":true}", "{\"code\":\"200\",\"code\":\"500\"}", "{\"code\":\"200\"} {}", "{}")) {
var decoded = new AtomicInteger();
TypedEndpoint endpoint = Feign.builder()
.decoder((response, type) -> { decoded.incrementAndGet(); return com.baiyue.common.core.api.ApiResponse.success(Map.of()); })
.addCapability(new EventDeliveryFeignConfiguration().eventDeliveryCapability(3000, 15000, 60))
.client((request, options) -> Response.builder().request(request).status(200).reason("OK").headers(Map.of()).body(body, StandardCharsets.UTF_8).build())
.target(TypedEndpoint.class, "http://event-consumer.invalid");
assertThatThrownBy(endpoint::send).isInstanceOf(feign.codec.DecodeException.class);
assertThat(decoded.get()).isZero();
}
}
@Test void validStringCodePreservesBodyForTheConfiguredDecoder() {
String body = "{\"code\":\"200\",\"data\":{\"outcome\":\"APPLIED\"}}";
TypedEndpoint endpoint = Feign.builder().decoder((response, type) -> {
assertThat(new String(response.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8)).isEqualTo(body);
return com.baiyue.common.core.api.ApiResponse.success(Map.of("outcome", "APPLIED"));
}).addCapability(new EventDeliveryFeignConfiguration().eventDeliveryCapability(3000, 15000, 60))
.client((request, options) -> Response.builder().request(request).status(200).reason("OK").headers(Map.of()).body(body, StandardCharsets.UTF_8).build())
.target(TypedEndpoint.class, "http://event-consumer.invalid");
assertThat(endpoint.send().data()).containsEntry("outcome", "APPLIED");
}
@Test void builtClientOverridesLegacyTimeoutAndRedirectSettings() {
var capability = new EventDeliveryFeignConfiguration().eventDeliveryCapability(3000, 15000, 60);
@@ -0,0 +1,71 @@
package com.baiyue.common.web;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.time.LocalDateTime;
import java.sql.Timestamp;
import java.util.Map;
import java.util.List;
import static org.mockito.ArgumentMatchers.*;
import java.util.UUID;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
class EventDeliveryMetricsTest {
@Test void samplesBoundedTypesAndPreservesPriorValuesWhenDatabaseFailsWithoutQueryingDuringScrapes() {
var jdbc = spy(new JdbcTemplate(new DriverManagerDataSource("jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", "")));
jdbc.execute("CREATE TABLE ll_event_outbox(event_type VARCHAR(200),status VARCHAR(32),created_at TIMESTAMP)");
var now = LocalDateTime.now();
jdbc.update("INSERT INTO ll_event_outbox VALUES('BusinessFeeSourceChanged','PENDING',?)", now.minusSeconds(60));
jdbc.update("INSERT INTO ll_event_outbox VALUES('BusinessFeeSourceChanged','PROCESSING',?)", now.minusSeconds(30));
jdbc.update("INSERT INTO ll_event_outbox VALUES('BusinessFeeSourceChanged','DEAD',?)", now.minusSeconds(120));
jdbc.update("INSERT INTO ll_event_outbox VALUES('user-controlled-unknown-type','PENDING',?)", now.minusYears(1));
var registry = new SimpleMeterRegistry();
var metrics = new EventDeliveryMetrics(jdbc, registry);
metrics.sample(EventDeliveryMetrics.Stream.FEE, now);
assertThat(registry.get("baiyue.event.delivery.pending").gauge().value()).isEqualTo(2);
assertThat(registry.get("baiyue.event.delivery.dead").gauge().value()).isEqualTo(1);
assertThat(registry.get("baiyue.event.delivery.oldest.pending.seconds").gauge().value()).isBetween(59.99, 60.01);
double sampledAt = registry.get("baiyue.event.delivery.sample.timestamp").gauge().value();
jdbc.execute("DROP TABLE ll_event_outbox");
metrics.sample(EventDeliveryMetrics.Stream.FEE, now.plusMinutes(1));
clearInvocations(jdbc);
assertThat(registry.get("baiyue.event.delivery.pending").gauge().value()).isEqualTo(2);
assertThat(registry.get("baiyue.event.delivery.sample.timestamp").gauge().value()).isEqualTo(sampledAt);
assertThat(registry.get("baiyue.event.delivery.sample.success").gauge().value()).isZero();
registry.getMeters().forEach(meter -> {
assertThat(meter.getId().getTag("event_type")).isEqualTo("BusinessFeeSourceChanged");
meter.measure().forEach(measurement -> measurement.getValue());
});
verifyNoInteractions(jdbc);
}
@Test void supportsBothMysqlLocalDateTimeAndJdbcTimestampAndRejectsUnknownTypes() {
var jdbc = mock(JdbcTemplate.class);
var now = LocalDateTime.of(2026, 9, 29, 12, 0);
var registry = new SimpleMeterRegistry();
var metrics = new EventDeliveryMetrics(jdbc, registry);
for (Object oldest : List.of(now.minusSeconds(45), Timestamp.valueOf(now.minusSeconds(45)))) {
Map<String, Object> row = Map.of("pending_count", 1L, "dead_count", 0L, "oldest", oldest);
when(jdbc.queryForMap(anyString(), eq("BusinessFeeSourceChanged"))).thenReturn(row);
metrics.sample(EventDeliveryMetrics.Stream.FEE, now);
assertThat(registry.get("baiyue.event.delivery.oldest.pending.seconds").gauge().value()).isEqualTo(45);
assertThat(registry.get("baiyue.event.delivery.sample.success").gauge().value()).isEqualTo(1);
}
when(jdbc.queryForMap(anyString(), eq("BusinessFeeSourceChanged")))
.thenReturn(Map.of("pending_count", 0L, "dead_count", 0L, "oldest", "not a JDBC time"));
metrics.sample(EventDeliveryMetrics.Stream.FEE, now);
assertThat(registry.get("baiyue.event.delivery.pending").gauge().value()).isEqualTo(1);
assertThat(registry.get("baiyue.event.delivery.oldest.pending.seconds").gauge().value()).isEqualTo(45);
assertThat(registry.get("baiyue.event.delivery.sample.success").gauge().value()).isZero();
}
@Test void initialFailureIsUnknownRatherThanFalseZero() {
var jdbc = new JdbcTemplate(new DriverManagerDataSource("jdbc:h2:mem:" + UUID.randomUUID(), "sa", ""));
var registry = new SimpleMeterRegistry();
new EventDeliveryMetrics(jdbc, registry).sample(EventDeliveryMetrics.Stream.FINANCE, LocalDateTime.now());
assertThat(registry.get("baiyue.event.delivery.pending").gauge().value()).isNaN();
assertThat(registry.get("baiyue.event.delivery.sample.timestamp").gauge().value()).isZero();
assertThat(registry.get("baiyue.event.delivery.sample.success").gauge().value()).isZero();
}
}
@@ -0,0 +1,53 @@
package com.baiyue.common.web;
import com.baiyue.common.core.outbox.EventDeliveryJournal;
import com.baiyue.common.core.outbox.EventDeliveryProducer;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.*;
class EventDeliveryObservationAutoConfigurationTest {
@Test void configuresMandatoryAuditAndMetricsForServiceLocalJdbc() {
var jdbc = new JdbcTemplate(new DriverManagerDataSource("jdbc:h2:mem:auto", "sa", ""));
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(EventDeliveryObservationAutoConfiguration.class))
.withBean(EventDeliveryProducer.class, () -> new EventDeliveryProducer() { })
.withBean(JdbcTemplate.class, () -> jdbc)
.withBean(PlatformTransactionManager.class, () -> new DataSourceTransactionManager(jdbc.getDataSource()))
.withBean(MeterRegistry.class, SimpleMeterRegistry::new)
.run(context -> {
assertThat(context).hasSingleBean(EventDeliveryJournal.class);
assertThat(context).hasSingleBean(EventDeliveryMetrics.class);
});
}
@Test void otherJdbcServicesDoNotAcquireTransactionOrMetricsRequirements() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(EventDeliveryObservationAutoConfiguration.class))
.withBean(JdbcTemplate.class, () -> new JdbcTemplate(new DriverManagerDataSource("jdbc:h2:mem:other", "sa", "")))
.run(context -> {
assertThat(context).hasNotFailed();
assertThat(context).doesNotHaveBean(EventDeliveryJournal.class);
assertThat(context).doesNotHaveBean(EventDeliveryMetrics.class);
});
}
@Test void producerWithoutMeterRegistryFailsInsteadOfSilentlyDisablingObservation() {
var jdbc = new JdbcTemplate(new DriverManagerDataSource("jdbc:h2:mem:noRegistry", "sa", ""));
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(EventDeliveryObservationAutoConfiguration.class))
.withBean(EventDeliveryProducer.class, () -> new EventDeliveryProducer() { })
.withBean(JdbcTemplate.class, () -> jdbc)
.withBean(PlatformTransactionManager.class, () -> new DataSourceTransactionManager(jdbc.getDataSource()))
.run(context -> assertThat(context).hasFailed());
}
@Test void missingTransactionManagerFailsRatherThanInstallingNonTransactionalFallback() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(EventDeliveryObservationAutoConfiguration.class))
.withBean(EventDeliveryProducer.class, () -> new EventDeliveryProducer() { })
.withBean(JdbcTemplate.class, () -> new JdbcTemplate(new DriverManagerDataSource("jdbc:h2:mem:missing", "sa", "")))
.withBean(MeterRegistry.class, SimpleMeterRegistry::new)
.run(context -> assertThat(context).hasFailed());
}
}
@@ -0,0 +1,22 @@
package com.baiyue.common.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class EventDeliveryObservationTest {
@Test void auditIdentitySupportsTheFormalUuidAndUlidContractWithoutSavingMalformedPayload() {
var mapper = new ObjectMapper();
for (String id : new String[]{"00000000-0000-4000-8000-000000000019", "01ARZ3NDEKTSV4RRFFQ69G5FAV"}) {
assertThat(EventDeliveryObservation.eventId(mapper, "{\"eventId\":\"" + id + "\",\"payload\":\"secret\"}")).isEqualTo(id);
}
assertThat(EventDeliveryObservation.eventId(mapper, "bad secret JSON")).isNull();
assertThat(EventDeliveryObservation.eventId(mapper, "{\"eventId\":\"Bearer secret\"}")).isNull();
}
@Test void arbitraryExceptionsAndRemoteMessagesCannotLeakIntoAttemptAudit() {
var failure = new IllegalStateException("payload={secret} Authorization=secret");
assertThat(EventDeliveryObservation.errorCode(failure, false, true)).isEqualTo("DELIVERY_FAILURE");
assertThat(EventDeliveryObservation.errorCode(failure, true, true)).isEqualTo("PERMANENT_FAILURE");
assertThat(EventDeliveryObservation.errorCode(failure, true, false)).isEqualTo("INVALID_EVENT");
}
}