Files
baiyue-firstleg/common-audit/src/test/java/com/baiyue/common/audit/ReliableAuditDeliveryTest.java
T
2026-08-05 14:20:04 +08:00

114 lines
4.2 KiB
Java

package com.baiyue.common.audit;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
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.messaging.Message;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.support.TransactionOperations;
import java.time.Instant;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SpringJUnitConfig(ReliableAuditDeliveryTest.Config.class)
class ReliableAuditDeliveryTest {
@MockBean
private AuditOutboxRepository repository;
@MockBean
private RocketMQTemplate rocketMQ;
@MockBean
private TransactionOperations transactions;
@Resource
private OutboxAuditSink sink;
@Resource
private AuditOutboxPublisher publisher;
@Test
void storesEventBeforeDelivery() {
AuditEvent event = event("audit-1");
org.mockito.Mockito.doAnswer(invocation -> {
java.util.function.Consumer<?> callback = invocation.getArgument(0);
((java.util.function.Consumer<Object>) callback).accept(null);
return null;
}).when(transactions).executeWithoutResult(any());
sink.publish(event);
verify(repository).append(eq(event), anyString());
}
@Test
void marksDeliveredMessageSent() {
when(repository.publishableIds(50)).thenReturn(List.of("audit-1"));
when(repository.claim("audit-1", "test-owner", 60)).thenReturn(true);
when(repository.find("audit-1"))
.thenReturn(new AuditOutboxRepository.OutboxRow("audit-1", "{}", 1));
publisher.publish();
verify(rocketMQ).syncSend(anyString(), any(Message.class), anyLong());
verify(repository).sent("audit-1", "test-owner");
}
@Test
void schedulesRetryAfterMqFailure() {
when(repository.publishableIds(50)).thenReturn(List.of("audit-2"));
when(repository.claim("audit-2", "test-owner", 60)).thenReturn(true);
when(repository.find("audit-2"))
.thenReturn(new AuditOutboxRepository.OutboxRow("audit-2", "{}", 1));
doThrow(new IllegalStateException("broker unavailable"))
.when(rocketMQ).syncSend(anyString(), any(Message.class), anyLong());
publisher.publish();
verify(repository).failed("audit-2", "test-owner", 5, 5, "broker unavailable");
}
private static AuditEvent event(String id) {
return new AuditEvent(id, "ROLE_UPDATE", "ROLE", "1", "修改角色",
"request-1", "trace-1", 1L, 1L, 1L, "admin", "PLATFORM", "WEB",
"PUT", "/roles/1", "127.0.0.1", 10, true, null, null,
null, null, Instant.parse("2026-08-05T00:00:00Z"), "iam-service");
}
@Configuration
static class Config {
@Bean
ObjectMapper objectMapper() { return new ObjectMapper().findAndRegisterModules(); }
@Bean
AuditProperties properties() {
AuditProperties value = new AuditProperties();
value.getOutbox().setBatchSize(50);
value.getOutbox().setLeaseSeconds(60);
value.getOutbox().setMaxAttempts(5);
value.getRocketmq().setTopic("test-baiyue-audit-event-v1");
return value;
}
@Bean
OutboxAuditSink sink(AuditOutboxRepository repository, ObjectMapper json,
TransactionOperations transactions) {
return new OutboxAuditSink(repository, json, transactions);
}
@Bean
AuditOutboxPublisher publisher(AuditOutboxRepository repository, RocketMQTemplate rocketMQ,
AuditProperties properties) {
return new AuditOutboxPublisher(repository, rocketMQ, properties, "test-owner");
}
}
}