refactor(security): 重构安全配置并优化测试环境

- 移除旧的测试套件和UAT测试文件
- 更新密码编码器配置使用BCrypt strength=12
- 添加用户角色关联表和相关服务
- 优化前端日期显示格式
- 清理无用资源和配置文件
- 增强测试数据管理和清理功能
This commit is contained in:
张翔
2026-03-27 13:00:22 +08:00
parent ce30893a96
commit af44c23f21
294 changed files with 16057 additions and 22601 deletions
@@ -0,0 +1,99 @@
package cn.novalon.manage.gateway.audit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import java.net.InetSocketAddress;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* AuditLogService单元测试
*
* 文件定义:测试审计日志服务的核心功能
* 涉及业务:请求日志记录、响应日志记录、安全事件记录
*
* @author 张翔
* @date 2026-03-26
*/
@ExtendWith(MockitoExtension.class)
class AuditLogServiceTest {
private AuditLogService auditLogService;
@BeforeEach
void setUp() {
auditLogService = new AuditLogService();
}
@Test
void testLogRequest() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.header("X-Request-Id", "test-request-123")
.header("User-Agent", "TestAgent")
.remoteAddress(new InetSocketAddress("192.168.1.1", 8080))
.build();
assertDoesNotThrow(() -> auditLogService.logRequest(request, "user123"));
}
@Test
void testLogResponse() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.header("X-Request-Id", "test-request-456")
.build();
auditLogService.logRequest(request, "user123");
assertDoesNotThrow(() -> auditLogService.logResponse("test-request-456", 200, 150));
}
@Test
void testLogSecurityEvent() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/admin")
.header("X-Request-Id", "test-request-789")
.build();
auditLogService.logRequest(request, "user123");
assertDoesNotThrow(() ->
auditLogService.logSecurityEvent("test-request-789", "UNAUTHORIZED_ACCESS", "User attempted to access admin resource"));
}
@Test
void testLogError() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.POST, "/api/data")
.header("X-Request-Id", "test-request-error")
.build();
auditLogService.logRequest(request, "user123");
assertDoesNotThrow(() ->
auditLogService.logError("test-request-error", "INTERNAL_ERROR", "Database connection failed"));
}
@Test
void testLogRequestWithoutRequestId() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/test")
.remoteAddress(new InetSocketAddress("10.0.0.1", 8080))
.build();
assertDoesNotThrow(() -> auditLogService.logRequest(request, "user456"));
}
@Test
void testLogResponseWithNonExistentRequestId() {
assertDoesNotThrow(() -> auditLogService.logResponse("non-existent-id", 404, 50));
}
}
@@ -0,0 +1,195 @@
package cn.novalon.manage.gateway.cache;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import reactor.test.StepVerifier;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class RequestCacheServiceTest {
private RequestCacheService cacheService;
@BeforeEach
void setUp() {
cacheService = new RequestCacheService();
}
@Test
void testGet_CacheMiss() {
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.build();
StepVerifier.create(cacheService.get(request))
.verifyComplete();
}
@Test
void testPutAndGet_CacheHit() {
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.build();
String response = "{\"data\":\"test\"}";
cacheService.put(request, response);
StepVerifier.create(cacheService.get(request))
.expectNext(response)
.verifyComplete();
}
@Test
void testEvict() {
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.build();
String response = "{\"data\":\"test\"}";
cacheService.put(request, response);
cacheService.evict(request);
StepVerifier.create(cacheService.get(request))
.verifyComplete();
}
@Test
void testEvictByPattern() {
ServerHttpRequest request1 = MockServerHttpRequest
.get("/api/test1")
.build();
ServerHttpRequest request2 = MockServerHttpRequest
.get("/api/test2")
.build();
cacheService.put(request1, "response1");
cacheService.put(request2, "response2");
cacheService.evictByPattern("GET:/api/test.*");
assertEquals(0, cacheService.getCacheSize());
}
@Test
void testClear() {
ServerHttpRequest request1 = MockServerHttpRequest
.get("/api/test1")
.build();
ServerHttpRequest request2 = MockServerHttpRequest
.get("/api/test2")
.build();
cacheService.put(request1, "response1");
cacheService.put(request2, "response2");
cacheService.clear();
assertEquals(0, cacheService.getCacheSize());
}
@Test
void testCacheDisabled() {
cacheService.setCacheEnabled(false);
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.build();
cacheService.put(request, "response");
StepVerifier.create(cacheService.get(request))
.verifyComplete();
assertEquals(0, cacheService.getCacheSize());
}
@Test
void testCacheStatistics() {
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.build();
cacheService.put(request, "response");
StepVerifier.create(cacheService.get(request))
.expectNext("response")
.verifyComplete();
assertEquals(1, cacheService.getHitCount());
assertEquals(0, cacheService.getMissCount());
assertEquals(1.0, cacheService.getHitRate());
}
@Test
void testCacheMissStatistics() {
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.build();
StepVerifier.create(cacheService.get(request))
.verifyComplete();
assertEquals(0, cacheService.getHitCount());
assertEquals(1, cacheService.getMissCount());
assertEquals(0.0, cacheService.getHitRate());
}
@Test
void testMaxCacheSize() {
cacheService.setMaxCacheSize(5);
for (int i = 0; i < 10; i++) {
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test" + i)
.build();
cacheService.put(request, "response" + i);
}
assertTrue(cacheService.getCacheSize() <= 10);
}
@Test
void testCacheWithQueryParams() {
ServerHttpRequest request = MockServerHttpRequest
.get("/api/test?param=value")
.build();
String response = "{\"data\":\"test\"}";
cacheService.put(request, response);
StepVerifier.create(cacheService.get(request))
.expectNext(response)
.verifyComplete();
}
@Test
void testCacheWithDifferentMethods() {
ServerHttpRequest getRequest = MockServerHttpRequest
.get("/api/test")
.build();
ServerHttpRequest postRequest = MockServerHttpRequest
.post("/api/test")
.build();
cacheService.put(getRequest, "getResponse");
cacheService.put(postRequest, "postResponse");
StepVerifier.create(cacheService.get(getRequest))
.expectNext("getResponse")
.verifyComplete();
StepVerifier.create(cacheService.get(postRequest))
.expectNext("postResponse")
.verifyComplete();
}
}
@@ -0,0 +1,116 @@
package cn.novalon.manage.gateway.config;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryRegistry;
import io.github.resilience4j.timelimiter.TimeLimiter;
import io.github.resilience4j.timelimiter.TimeLimiterRegistry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.jupiter.api.Assertions.*;
/**
* ResilienceConfig单元测试
*
* 文件定义:测试Resilience4j配置类的核心功能
* 涉及业务:断路器、重试、超时配置
*
* @author 张翔
* @date 2026-03-26
*/
@ExtendWith(MockitoExtension.class)
class ResilienceConfigTest {
@InjectMocks
private ResilienceConfig resilienceConfig;
@Test
void testCircuitBreakerRegistry_ShouldCreateRegistry() {
ReflectionTestUtils.setField(resilienceConfig, "circuitBreakerEnabled", true);
ReflectionTestUtils.setField(resilienceConfig, "failureRateThreshold", 50.0f);
ReflectionTestUtils.setField(resilienceConfig, "slowCallRateThreshold", 100.0f);
ReflectionTestUtils.setField(resilienceConfig, "slowCallDurationThreshold", java.time.Duration.ofSeconds(2));
ReflectionTestUtils.setField(resilienceConfig, "permittedNumberOfCallsInHalfOpenState", 10);
ReflectionTestUtils.setField(resilienceConfig, "slidingWindowType", "COUNT_BASED");
ReflectionTestUtils.setField(resilienceConfig, "slidingWindowSize", 100);
ReflectionTestUtils.setField(resilienceConfig, "minimumNumberOfCalls", 10);
ReflectionTestUtils.setField(resilienceConfig, "waitDurationInOpenState", java.time.Duration.ofSeconds(10));
CircuitBreakerRegistry registry = resilienceConfig.circuitBreakerRegistry();
assertNotNull(registry);
assertNotNull(registry.getConfiguration("default"));
}
@Test
void testGatewayCircuitBreaker_ShouldCreateCircuitBreaker() {
ReflectionTestUtils.setField(resilienceConfig, "circuitBreakerEnabled", true);
ReflectionTestUtils.setField(resilienceConfig, "failureRateThreshold", 50.0f);
ReflectionTestUtils.setField(resilienceConfig, "slowCallRateThreshold", 100.0f);
ReflectionTestUtils.setField(resilienceConfig, "slowCallDurationThreshold", java.time.Duration.ofSeconds(2));
ReflectionTestUtils.setField(resilienceConfig, "permittedNumberOfCallsInHalfOpenState", 10);
ReflectionTestUtils.setField(resilienceConfig, "slidingWindowType", "COUNT_BASED");
ReflectionTestUtils.setField(resilienceConfig, "slidingWindowSize", 100);
ReflectionTestUtils.setField(resilienceConfig, "minimumNumberOfCalls", 10);
ReflectionTestUtils.setField(resilienceConfig, "waitDurationInOpenState", java.time.Duration.ofSeconds(10));
CircuitBreakerRegistry registry = resilienceConfig.circuitBreakerRegistry();
CircuitBreaker circuitBreaker = resilienceConfig.gatewayCircuitBreaker(registry);
assertNotNull(circuitBreaker);
assertEquals("gateway", circuitBreaker.getName());
}
@Test
void testRetryRegistry_ShouldCreateRegistry() {
ReflectionTestUtils.setField(resilienceConfig, "retryEnabled", true);
ReflectionTestUtils.setField(resilienceConfig, "retryMaxAttempts", 3);
ReflectionTestUtils.setField(resilienceConfig, "retryWaitDuration", java.time.Duration.ofMillis(500));
RetryRegistry registry = resilienceConfig.retryRegistry();
assertNotNull(registry);
assertNotNull(registry.getConfiguration("default"));
}
@Test
void testGatewayRetry_ShouldCreateRetry() {
ReflectionTestUtils.setField(resilienceConfig, "retryEnabled", true);
ReflectionTestUtils.setField(resilienceConfig, "retryMaxAttempts", 3);
ReflectionTestUtils.setField(resilienceConfig, "retryWaitDuration", java.time.Duration.ofMillis(500));
RetryRegistry registry = resilienceConfig.retryRegistry();
Retry retry = resilienceConfig.gatewayRetry(registry);
assertNotNull(retry);
assertEquals("gateway", retry.getName());
}
@Test
void testTimeLimiterRegistry_ShouldCreateRegistry() {
ReflectionTestUtils.setField(resilienceConfig, "timeoutEnabled", true);
ReflectionTestUtils.setField(resilienceConfig, "timeoutDuration", java.time.Duration.ofSeconds(3));
TimeLimiterRegistry registry = resilienceConfig.timeLimiterRegistry();
assertNotNull(registry);
assertNotNull(registry.getConfiguration("default"));
}
@Test
void testGatewayTimeLimiter_ShouldCreateTimeLimiter() {
ReflectionTestUtils.setField(resilienceConfig, "timeoutEnabled", true);
ReflectionTestUtils.setField(resilienceConfig, "timeoutDuration", java.time.Duration.ofSeconds(3));
TimeLimiterRegistry registry = resilienceConfig.timeLimiterRegistry();
TimeLimiter timeLimiter = resilienceConfig.gatewayTimeLimiter(registry);
assertNotNull(timeLimiter);
assertEquals("gateway", timeLimiter.getName());
}
}
@@ -0,0 +1,133 @@
package cn.novalon.manage.gateway.filter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.mock.web.server.MockServerWebExchange;
import reactor.core.publisher.Mono;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class CompressionFilterTest {
private CompressionFilter compressionFilter;
@Mock
private GatewayFilterChain chain;
@BeforeEach
void setUp() {
compressionFilter = new CompressionFilter();
when(chain.filter(any())).thenReturn(Mono.empty());
}
@Test
void testFilter_WithGzipSupport() {
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.header("Accept-Encoding", "gzip, deflate")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
compressionFilter.filter(exchange, chain).block();
assertEquals("gzip", exchange.getResponse().getHeaders().getFirst("Content-Encoding"));
verify(chain).filter(any());
}
@Test
void testFilter_WithDeflateSupport() {
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.header("Accept-Encoding", "deflate")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
compressionFilter.filter(exchange, chain).block();
assertEquals("deflate", exchange.getResponse().getHeaders().getFirst("Content-Encoding"));
verify(chain).filter(any());
}
@Test
void testFilter_NoAcceptEncoding() {
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
compressionFilter.filter(exchange, chain).block();
assertNull(exchange.getResponse().getHeaders().getFirst("Content-Encoding"));
verify(chain).filter(any());
}
@Test
void testFilter_CompressionDisabled() {
compressionFilter.setCompressionEnabled(false);
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.header("Accept-Encoding", "gzip")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
compressionFilter.filter(exchange, chain).block();
assertNull(exchange.getResponse().getHeaders().getFirst("Content-Encoding"));
verify(chain).filter(any());
}
@Test
void testFilter_OptionsRequest() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.OPTIONS, "/api/test")
.header("Accept-Encoding", "gzip")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
compressionFilter.filter(exchange, chain).block();
assertNull(exchange.getResponse().getHeaders().getFirst("Content-Encoding"));
verify(chain).filter(any());
}
@Test
void testFilter_VaryHeader() {
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.header("Accept-Encoding", "gzip")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
compressionFilter.filter(exchange, chain).block();
assertTrue(exchange.getResponse().getHeaders().get("Vary").contains("Accept-Encoding"));
}
@Test
void testGetOrder() {
assertEquals(Integer.MAX_VALUE - 100, compressionFilter.getOrder());
}
}
@@ -0,0 +1,285 @@
package cn.novalon.manage.gateway.filter;
import cn.novalon.manage.gateway.config.RateLimitConfig;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.net.InetSocketAddress;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class RateLimitFilterTest {
@Mock
private RateLimiter globalRateLimiter;
@Mock
private RateLimiter ipRateLimiter;
@Mock
private RateLimiter userRateLimiter;
@Mock
private RateLimitConfig rateLimitConfig;
@Mock
private GatewayFilterChain chain;
private RateLimitFilter rateLimitFilter;
@BeforeEach
void setUp() {
lenient().when(rateLimitConfig.isRateLimitEnabled()).thenReturn(true);
RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(100)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ZERO)
.build();
lenient().when(globalRateLimiter.getRateLimiterConfig()).thenReturn(config);
lenient().when(ipRateLimiter.getRateLimiterConfig()).thenReturn(config);
lenient().when(userRateLimiter.getRateLimiterConfig()).thenReturn(config);
rateLimitFilter = new RateLimitFilter(
globalRateLimiter,
ipRateLimiter,
userRateLimiter,
rateLimitConfig);
}
@Test
void testFilter_WhenRateLimitDisabled_ShouldPassThrough() {
when(rateLimitConfig.isRateLimitEnabled()).thenReturn(false);
when(chain.filter(any())).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
verify(globalRateLimiter, never()).acquirePermission();
}
@Test
void testFilter_WhenGlobalRateLimitExceeded_ShouldReturn429() {
when(globalRateLimiter.acquirePermission()).thenReturn(false);
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
assertEquals(HttpStatus.TOO_MANY_REQUESTS, exchange.getResponse().getStatusCode());
verify(chain, never()).filter(any());
}
@Test
void testFilter_WhenAllRateLimitsPass_ShouldContinueChain() {
when(globalRateLimiter.acquirePermission()).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.header("X-User-Id", "user123")
.remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
verify(globalRateLimiter).acquirePermission();
}
@Test
void testFilter_WithoutUserId_ShouldSkipUserRateLimit() {
when(globalRateLimiter.acquirePermission()).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testGetClientIp_FromXForwardedFor() {
when(globalRateLimiter.acquirePermission()).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.header("X-Forwarded-For", "10.0.0.1, 192.168.1.1")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testGetClientIp_FromXRealIP() {
when(globalRateLimiter.acquirePermission()).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.header("X-Real-IP", "10.0.0.2")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testGetClientIp_FromRemoteAddress() {
when(globalRateLimiter.acquirePermission()).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.remoteAddress(new InetSocketAddress("192.168.1.100", 12345))
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testRateLimitHeaders_WhenExceeded() {
when(globalRateLimiter.acquirePermission()).thenReturn(false);
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
assertTrue(headers.containsKey("X-RateLimit-Limit"));
assertTrue(headers.containsKey("X-RateLimit-Remaining"));
assertTrue(headers.containsKey("Retry-After"));
assertTrue(headers.containsKey("X-RateLimit-Type"));
}
@Test
void testCounters_WhenRequestsProcessed() {
when(globalRateLimiter.acquirePermission()).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
assertEquals(1, rateLimitFilter.getTotalRequests());
assertEquals(0, rateLimitFilter.getBlockedRequests());
}
@Test
void testCounters_WhenRequestsBlocked() {
when(globalRateLimiter.acquirePermission()).thenReturn(false);
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
assertEquals(1, rateLimitFilter.getTotalRequests());
assertEquals(1, rateLimitFilter.getBlockedRequests());
}
@Test
void testResetCounters() {
when(globalRateLimiter.acquirePermission()).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest
.get("/api/test")
.remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
StepVerifier.create(rateLimitFilter.filter(exchange, chain))
.verifyComplete();
assertEquals(1, rateLimitFilter.getTotalRequests());
rateLimitFilter.resetCounters();
assertEquals(0, rateLimitFilter.getTotalRequests());
assertEquals(0, rateLimitFilter.getBlockedRequests());
}
@Test
void testGetOrder() {
int order = rateLimitFilter.getOrder();
assertEquals(Ordered.HIGHEST_PRECEDENCE + 100, order);
}
}
@@ -1,5 +1,6 @@
package cn.novalon.manage.gateway.filter;
import cn.novalon.manage.gateway.service.PermissionService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -22,12 +23,15 @@ class RbacAuthorizationFilterTest {
@Mock
private GatewayFilterChain chain;
@Mock
private PermissionService permissionService;
private RbacAuthorizationFilter filter;
private ServerWebExchange exchange;
@BeforeEach
void setUp() {
filter = new RbacAuthorizationFilter();
filter = new RbacAuthorizationFilter(permissionService);
}
@Test
@@ -116,6 +120,7 @@ class RbacAuthorizationFilterTest {
.build();
exchange = MockServerWebExchange.from(request);
when(permissionService.hasPermission(eq(1L), eq("/api/users"), eq("GET"))).thenReturn(true);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
@@ -134,6 +139,7 @@ class RbacAuthorizationFilterTest {
.build();
exchange = MockServerWebExchange.from(request);
when(permissionService.hasPermission(eq(1L), eq("/api/users"), eq("POST"))).thenReturn(true);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
@@ -152,6 +158,7 @@ class RbacAuthorizationFilterTest {
.build();
exchange = MockServerWebExchange.from(request);
when(permissionService.hasPermission(eq(1L), eq("/api/users/1"), eq("PUT"))).thenReturn(true);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
@@ -170,6 +177,7 @@ class RbacAuthorizationFilterTest {
.build();
exchange = MockServerWebExchange.from(request);
when(permissionService.hasPermission(eq(1L), eq("/api/users/1"), eq("DELETE"))).thenReturn(true);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
@@ -188,15 +196,14 @@ class RbacAuthorizationFilterTest {
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
verify(chain, never()).filter(any(ServerWebExchange.class));
}
@Test
@@ -222,6 +229,7 @@ class RbacAuthorizationFilterTest {
.build();
exchange = MockServerWebExchange.from(request);
when(permissionService.hasPermission(eq(1L), eq("/api/users/profile"), eq("GET"))).thenReturn(true);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
@@ -240,6 +248,7 @@ class RbacAuthorizationFilterTest {
.build();
exchange = MockServerWebExchange.from(request);
when(permissionService.hasPermission(eq(1L), eq("/actuator/metrics"), eq("GET"))).thenReturn(true);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
@@ -0,0 +1,189 @@
package cn.novalon.manage.gateway.filter;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.timelimiter.TimeLimiter;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.util.ReflectionTestUtils;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
/**
* ResilienceFilter单元测试
*
* 文件定义:测试容错过滤器的核心功能
* 涉及业务:断路器、重试、超时、降级
*
* @author 张翔
* @date 2026-03-26
*/
@ExtendWith(MockitoExtension.class)
class ResilienceFilterTest {
@Mock
private GatewayFilterChain chain;
private CircuitBreaker circuitBreaker;
private Retry retry;
private TimeLimiter timeLimiter;
private ResilienceFilter resilienceFilter;
@BeforeEach
void setUp() {
CircuitBreakerConfig cbConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.slidingWindowSize(100)
.minimumNumberOfCalls(10)
.waitDurationInOpenState(Duration.ofSeconds(10))
.build();
RetryConfig retryConfig = RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofMillis(500))
.build();
TimeLimiterConfig tlConfig = TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(3))
.build();
circuitBreaker = CircuitBreaker.of("gateway", cbConfig);
retry = Retry.of("gateway", retryConfig);
timeLimiter = TimeLimiter.of("gateway", tlConfig);
resilienceFilter = new ResilienceFilter(circuitBreaker, retry, timeLimiter);
ReflectionTestUtils.setField(resilienceFilter, "resilienceEnabled", true);
ReflectionTestUtils.setField(resilienceFilter, "circuitBreakerEnabled", true);
ReflectionTestUtils.setField(resilienceFilter, "retryEnabled", true);
ReflectionTestUtils.setField(resilienceFilter, "timeoutEnabled", true);
}
@Test
void testFilter_WhenResilienceDisabled_ShouldContinueChain() {
ReflectionTestUtils.setField(resilienceFilter, "resilienceEnabled", false);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(resilienceFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testFilter_WhenAllPatternsEnabled_ShouldApplyResilience() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(resilienceFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testFilter_WhenCircuitBreakerDisabled_ShouldSkipCircuitBreaker() {
ReflectionTestUtils.setField(resilienceFilter, "circuitBreakerEnabled", false);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(resilienceFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testFilter_WhenRetryDisabled_ShouldSkipRetry() {
ReflectionTestUtils.setField(resilienceFilter, "retryEnabled", false);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(resilienceFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testFilter_WhenTimeoutDisabled_ShouldSkipTimeout() {
ReflectionTestUtils.setField(resilienceFilter, "timeoutEnabled", false);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(resilienceFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
}
@Test
void testFilter_WhenChainThrowsException_ShouldHandleFallback() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.error(new RuntimeException("Test error")));
StepVerifier.create(resilienceFilter.filter(exchange, chain))
.verifyComplete();
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, exchange.getResponse().getStatusCode());
}
@Test
void testGetOrder_ShouldReturnCorrectOrder() {
int order = resilienceFilter.getOrder();
assertEquals(-2147483448, order);
}
}
@@ -0,0 +1,219 @@
package cn.novalon.manage.gateway.filter;
import cn.novalon.manage.gateway.service.SignatureService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.util.ReflectionTestUtils;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
/**
* SignatureFilter单元测试
*
* 文件定义:测试签名验证过滤器的核心功能
* 涉及业务:签名验证、白名单过滤、错误响应
*
* @author 张翔
* @date 2026-03-26
*/
@ExtendWith(MockitoExtension.class)
class SignatureFilterTest {
@Mock
private SignatureService signatureService;
@Mock
private GatewayFilterChain chain;
@InjectMocks
private SignatureFilter signatureFilter;
private static final String TEST_SECRET = "TestSecretKey123";
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(signatureFilter, "signatureEnabled", true);
ReflectionTestUtils.setField(signatureFilter, "signatureSecret", TEST_SECRET);
ReflectionTestUtils.setField(signatureFilter, "whitelistPaths", "/actuator/health,/actuator/info");
}
@Test
void testFilter_WhenSignatureDisabled_ShouldContinueChain() {
ReflectionTestUtils.setField(signatureFilter, "signatureEnabled", false);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(signatureFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
verify(signatureService, never()).verifySignature(any(), any());
}
@Test
void testFilter_WhenPathIsWhitelisted_ShouldContinueChain() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/actuator/health")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(signatureFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
verify(signatureService, never()).verifySignature(any(), any());
}
@Test
void testFilter_WhenSignatureValid_ShouldContinueChain() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.header("X-Signature", "valid-signature")
.header("X-Timestamp", String.valueOf(System.currentTimeMillis()))
.header("X-Nonce", "test-nonce")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(signatureService.verifySignature(any(), any())).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(signatureFilter.filter(exchange, chain))
.verifyComplete();
verify(signatureService).verifySignature(request, TEST_SECRET);
verify(chain).filter(exchange);
}
@Test
void testFilter_WhenSignatureInvalid_ShouldReturnUnauthorized() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.header("X-Signature", "invalid-signature")
.header("X-Timestamp", String.valueOf(System.currentTimeMillis()))
.header("X-Nonce", "test-nonce")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(signatureService.verifySignature(any(), any())).thenReturn(false);
StepVerifier.create(signatureFilter.filter(exchange, chain))
.verifyComplete();
verify(signatureService).verifySignature(request, TEST_SECRET);
verify(chain, never()).filter(any());
assertEquals(HttpStatus.UNAUTHORIZED, exchange.getResponse().getStatusCode());
}
@Test
void testFilter_WhenMissingSignatureHeaders_ShouldReturnUnauthorized() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(signatureService.verifySignature(any(), any())).thenReturn(false);
StepVerifier.create(signatureFilter.filter(exchange, chain))
.verifyComplete();
verify(signatureService).verifySignature(request, TEST_SECRET);
verify(chain, never()).filter(any());
assertEquals(HttpStatus.UNAUTHORIZED, exchange.getResponse().getStatusCode());
}
@Test
void testFilter_WhenMultipleWhitelistPaths_ShouldMatchAny() {
MockServerHttpRequest request1 = MockServerHttpRequest
.method(HttpMethod.GET, "/actuator/health")
.build();
MockServerHttpRequest request2 = MockServerHttpRequest
.method(HttpMethod.GET, "/actuator/info")
.build();
MockServerWebExchange exchange1 = MockServerWebExchange.builder(request1).build();
MockServerWebExchange exchange2 = MockServerWebExchange.builder(request2).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(signatureFilter.filter(exchange1, chain))
.verifyComplete();
StepVerifier.create(signatureFilter.filter(exchange2, chain))
.verifyComplete();
verify(chain, times(2)).filter(any());
verify(signatureService, never()).verifySignature(any(), any());
}
@Test
void testFilter_WhenPathStartsWithWhitelist_ShouldMatch() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/actuator/health/details")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(signatureFilter.filter(exchange, chain))
.verifyComplete();
verify(chain).filter(exchange);
verify(signatureService, never()).verifySignature(any(), any());
}
@Test
void testGetOrder_ShouldReturnCorrectOrder() {
int order = signatureFilter.getOrder();
assertEquals(-2147483498, order);
}
@Test
void testFilter_WhenSignatureEnabled_ShouldVerifySignature() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "/api/users")
.header("X-Signature", "test-signature")
.header("X-Timestamp", String.valueOf(System.currentTimeMillis()))
.header("X-Nonce", "test-nonce")
.build();
MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();
when(signatureService.verifySignature(any(), any())).thenReturn(true);
when(chain.filter(any())).thenReturn(Mono.empty());
StepVerifier.create(signatureFilter.filter(exchange, chain))
.verifyComplete();
verify(signatureService).verifySignature(request, TEST_SECRET);
}
}
@@ -0,0 +1,84 @@
package cn.novalon.manage.gateway.health;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
/**
* GatewayHealthIndicator单元测试
*
* 文件定义:测试网关健康检查指示器的核心功能
* 涉及业务:断路器健康检查、限流器健康检查
*
* @author 张翔
* @date 2026-03-26
*/
@ExtendWith(MockitoExtension.class)
class GatewayHealthIndicatorTest {
private CircuitBreakerRegistry circuitBreakerRegistry;
private RateLimiterRegistry rateLimiterRegistry;
private GatewayHealthIndicator healthIndicator;
@BeforeEach
void setUp() {
CircuitBreakerConfig cbConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.slidingWindowSize(100)
.minimumNumberOfCalls(10)
.waitDurationInOpenState(Duration.ofSeconds(10))
.build();
RateLimiterConfig rlConfig = RateLimiterConfig.custom()
.limitForPeriod(100)
.limitRefreshPeriod(Duration.ofSeconds(1))
.build();
circuitBreakerRegistry = CircuitBreakerRegistry.of(cbConfig);
rateLimiterRegistry = RateLimiterRegistry.of(rlConfig);
healthIndicator = new GatewayHealthIndicator(circuitBreakerRegistry, rateLimiterRegistry);
}
@Test
void testHealth_WhenAllComponentsHealthy_ShouldReturnUp() {
circuitBreakerRegistry.circuitBreaker("test-cb");
rateLimiterRegistry.rateLimiter("test-rl");
Health health = healthIndicator.health();
assertEquals(Status.UP, health.getStatus());
assertTrue(health.getDetails().containsKey("circuitBreakers"));
assertTrue(health.getDetails().containsKey("rateLimiters"));
}
@Test
void testHealth_WhenNoComponents_ShouldReturnUp() {
Health health = healthIndicator.health();
assertEquals(Status.UP, health.getStatus());
}
@Test
void testHealth_ShouldIncludeComponentDetails() {
circuitBreakerRegistry.circuitBreaker("gateway");
rateLimiterRegistry.rateLimiter("gateway");
Health health = healthIndicator.health();
assertTrue(health.getDetails().containsKey("circuitBreakers"));
assertTrue(health.getDetails().containsKey("rateLimiters"));
}
}
@@ -0,0 +1,260 @@
package cn.novalon.manage.gateway.integration;
import cn.novalon.manage.gateway.filter.RbacAuthorizationFilter;
import cn.novalon.manage.gateway.model.Permission;
import cn.novalon.manage.gateway.model.Role;
import cn.novalon.manage.gateway.model.User;
import cn.novalon.manage.gateway.service.PermissionService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RbacIntegrationTest {
@Mock
private PermissionService permissionService;
@Mock
private GatewayFilterChain chain;
private RbacAuthorizationFilter filter;
@BeforeEach
void setUp() {
filter = new RbacAuthorizationFilter(permissionService);
}
@Test
void testEndToEnd_AdminUserFullAccess() {
Long adminUserId = 1L;
String adminPath = "/api/admin/users";
String adminMethod = "GET";
when(permissionService.hasPermission(eq(adminUserId), eq(adminPath), eq(adminMethod))).thenReturn(true);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest.get(adminPath)
.header("X-User-Id", adminUserId.toString())
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
assert exchange.getResponse().getStatusCode() == null || exchange.getResponse().getStatusCode() == HttpStatus.OK;
}
@Test
void testEndToEnd_RegularUserLimitedAccess() {
Long regularUserId = 2L;
String adminPath = "/api/admin/users";
String userPath = "/api/users/profile";
when(permissionService.hasPermission(eq(regularUserId), eq(adminPath), eq("GET"))).thenReturn(false);
when(permissionService.hasPermission(eq(regularUserId), eq(userPath), eq("GET"))).thenReturn(true);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
MockServerHttpRequest adminRequest = MockServerHttpRequest.get(adminPath)
.header("X-User-Id", regularUserId.toString())
.build();
ServerWebExchange adminExchange = MockServerWebExchange.from(adminRequest);
Mono<Void> adminResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(adminExchange, chain);
StepVerifier.create(adminResult)
.verifyComplete();
assert adminExchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;
MockServerHttpRequest userRequest = MockServerHttpRequest.get(userPath)
.header("X-User-Id", regularUserId.toString())
.build();
ServerWebExchange userExchange = MockServerWebExchange.from(userRequest);
Mono<Void> userResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(userExchange, chain);
StepVerifier.create(userResult)
.verifyComplete();
assert userExchange.getResponse().getStatusCode() == null || userExchange.getResponse().getStatusCode() == HttpStatus.OK;
}
@Test
void testEndToEnd_MultipleHttpMethods() {
Long userId = 3L;
String basePath = "/api/users";
when(permissionService.hasPermission(eq(userId), eq(basePath), eq("GET"))).thenReturn(true);
when(permissionService.hasPermission(eq(userId), eq(basePath), eq("POST"))).thenReturn(true);
when(permissionService.hasPermission(eq(userId), eq(basePath), eq("PUT"))).thenReturn(false);
when(permissionService.hasPermission(eq(userId), eq(basePath), eq("DELETE"))).thenReturn(false);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
MockServerHttpRequest getRequest = MockServerHttpRequest.get(basePath)
.header("X-User-Id", userId.toString())
.build();
ServerWebExchange getExchange = MockServerWebExchange.from(getRequest);
Mono<Void> getResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(getExchange, chain);
StepVerifier.create(getResult)
.verifyComplete();
assert getExchange.getResponse().getStatusCode() == null || getExchange.getResponse().getStatusCode() == HttpStatus.OK;
MockServerHttpRequest postRequest = MockServerHttpRequest.post(basePath)
.header("X-User-Id", userId.toString())
.build();
ServerWebExchange postExchange = MockServerWebExchange.from(postRequest);
Mono<Void> postResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(postExchange, chain);
StepVerifier.create(postResult)
.verifyComplete();
assert postExchange.getResponse().getStatusCode() == null || postExchange.getResponse().getStatusCode() == HttpStatus.OK;
MockServerHttpRequest putRequest = MockServerHttpRequest.put(basePath)
.header("X-User-Id", userId.toString())
.build();
ServerWebExchange putExchange = MockServerWebExchange.from(putRequest);
Mono<Void> putResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(putExchange, chain);
StepVerifier.create(putResult)
.verifyComplete();
assert putExchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;
MockServerHttpRequest deleteRequest = MockServerHttpRequest.delete(basePath)
.header("X-User-Id", userId.toString())
.build();
ServerWebExchange deleteExchange = MockServerWebExchange.from(deleteRequest);
Mono<Void> deleteResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(deleteExchange, chain);
StepVerifier.create(deleteResult)
.verifyComplete();
assert deleteExchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;
}
@Test
void testEndToEnd_PathMatchingScenarios() {
Long userId = 4L;
when(permissionService.hasPermission(eq(userId), eq("/api/users"), eq("GET"))).thenReturn(true);
when(permissionService.hasPermission(eq(userId), eq("/api/users/123"), eq("GET"))).thenReturn(true);
when(permissionService.hasPermission(eq(userId), eq("/api/users/123/profile"), eq("GET"))).thenReturn(true);
when(permissionService.hasPermission(eq(userId), eq("/api/admin"), eq("GET"))).thenReturn(false);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
String[] allowedPaths = {"/api/users", "/api/users/123", "/api/users/123/profile"};
for (String path : allowedPaths) {
MockServerHttpRequest request = MockServerHttpRequest.get(path)
.header("X-User-Id", userId.toString())
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
assert exchange.getResponse().getStatusCode() == null || exchange.getResponse().getStatusCode() == HttpStatus.OK;
}
MockServerHttpRequest adminRequest = MockServerHttpRequest.get("/api/admin")
.header("X-User-Id", userId.toString())
.build();
ServerWebExchange adminExchange = MockServerWebExchange.from(adminRequest);
Mono<Void> adminResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(adminExchange, chain);
StepVerifier.create(adminResult)
.verifyComplete();
assert adminExchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;
}
@Test
void testEndToEnd_PublicPathsBypass() {
String[] publicPaths = {
"/api/auth/login",
"/api/auth/register",
"/actuator/health",
"/actuator/info"
};
for (String path : publicPaths) {
MockServerHttpRequest request = MockServerHttpRequest.get(path).build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
assert exchange.getResponse().getStatusCode() == null || exchange.getResponse().getStatusCode() == HttpStatus.OK;
}
}
@Test
void testEndToEnd_ErrorScenarios() {
MockServerHttpRequest noHeaderRequest = MockServerHttpRequest.get("/api/users").build();
ServerWebExchange noHeaderExchange = MockServerWebExchange.from(noHeaderRequest);
Mono<Void> noHeaderResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(noHeaderExchange, chain);
StepVerifier.create(noHeaderResult)
.verifyComplete();
assert noHeaderExchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
MockServerHttpRequest invalidIdRequest = MockServerHttpRequest.get("/api/users")
.header("X-User-Id", "invalid")
.build();
ServerWebExchange invalidIdExchange = MockServerWebExchange.from(invalidIdRequest);
Mono<Void> invalidIdResult = filter.apply(new RbacAuthorizationFilter.Config())
.filter(invalidIdExchange, chain);
StepVerifier.create(invalidIdResult)
.verifyComplete();
assert invalidIdExchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
}
}
@@ -0,0 +1,141 @@
package cn.novalon.manage.gateway.loadbalancer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* CustomLoadBalancer单元测试
*
* 文件定义:测试自定义负载均衡器的核心功能
* 涉及业务:轮询、随机、加权轮询、最少连接策略
*
* @author 张翔
* @date 2026-03-26
*/
class CustomLoadBalancerTest {
private CustomLoadBalancer loadBalancer;
private List<ServiceInstance> instances;
@BeforeEach
void setUp() {
loadBalancer = new CustomLoadBalancer();
instances = Arrays.asList(
createInstance("host1", 8080),
createInstance("host2", 8080),
createInstance("host3", 8080)
);
}
@Test
void testSelectByRoundRobin() {
ServiceInstance instance1 = loadBalancer.selectInstance(
instances,
CustomLoadBalancer.LoadBalanceStrategy.ROUND_ROBIN);
ServiceInstance instance2 = loadBalancer.selectInstance(
instances,
CustomLoadBalancer.LoadBalanceStrategy.ROUND_ROBIN);
assertNotNull(instance1);
assertNotNull(instance2);
assertNotSame(instance1, instance2);
}
@Test
void testSelectByRandom() {
ServiceInstance instance = loadBalancer.selectInstance(
instances,
CustomLoadBalancer.LoadBalanceStrategy.RANDOM);
assertNotNull(instance);
assertTrue(instances.contains(instance));
}
@Test
void testSelectByWeightedRoundRobin() {
ServiceInstance instance = loadBalancer.selectInstance(
instances,
CustomLoadBalancer.LoadBalanceStrategy.WEIGHTED_ROUND_ROBIN);
assertNotNull(instance);
assertTrue(instances.contains(instance));
}
@Test
void testSelectByLeastConnections() {
ServiceInstance instance = loadBalancer.selectInstance(
instances,
CustomLoadBalancer.LoadBalanceStrategy.LEAST_CONNECTIONS);
assertNotNull(instance);
assertTrue(instances.contains(instance));
}
@Test
void testSelectInstance_EmptyList() {
ServiceInstance instance = loadBalancer.selectInstance(
Collections.emptyList(),
CustomLoadBalancer.LoadBalanceStrategy.ROUND_ROBIN);
assertNull(instance);
}
@Test
void testSelectInstance_NullList() {
ServiceInstance instance = loadBalancer.selectInstance(
null,
CustomLoadBalancer.LoadBalanceStrategy.ROUND_ROBIN);
assertNull(instance);
}
@Test
void testSetWeight() {
ServiceInstance instance = instances.get(0);
loadBalancer.setWeight(instance, 5);
assertNotNull(instance);
}
@Test
void testIncrementConnection() {
ServiceInstance instance = instances.get(0);
loadBalancer.incrementConnection(instance);
loadBalancer.incrementConnection(instance);
assertNotNull(instance);
}
@Test
void testDecrementConnection() {
ServiceInstance instance = instances.get(0);
loadBalancer.incrementConnection(instance);
loadBalancer.incrementConnection(instance);
loadBalancer.decrementConnection(instance);
assertNotNull(instance);
}
private ServiceInstance createInstance(String host, int port) {
return new DefaultServiceInstance(
"service-" + host + "-" + port,
"test-service",
host,
port,
false
);
}
}
@@ -0,0 +1,85 @@
package cn.novalon.manage.gateway.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
/**
* GatewayMetrics单元测试
*
* 文件定义:测试网关指标收集器的核心功能
* 涉及业务:请求统计、性能监控、活跃连接数统计
*
* @author 张翔
* @date 2026-03-26
*/
class GatewayMetricsTest {
private MeterRegistry meterRegistry;
private GatewayMetrics gatewayMetrics;
@BeforeEach
void setUp() {
meterRegistry = new SimpleMeterRegistry();
gatewayMetrics = new GatewayMetrics(meterRegistry);
}
@Test
void testIncrementTotalRequests() {
gatewayMetrics.incrementTotalRequests();
assertEquals(1, gatewayMetrics.getTotalRequests());
}
@Test
void testIncrementSuccessRequests() {
gatewayMetrics.incrementSuccessRequests();
assertEquals(1, gatewayMetrics.getSuccessRequests());
}
@Test
void testIncrementFailedRequests() {
gatewayMetrics.incrementFailedRequests();
assertEquals(1, gatewayMetrics.getFailedRequests());
}
@Test
void testIncrementActiveConnections() {
gatewayMetrics.incrementActiveConnections();
assertEquals(1, gatewayMetrics.getActiveConnections());
}
@Test
void testDecrementActiveConnections() {
gatewayMetrics.incrementActiveConnections();
gatewayMetrics.incrementActiveConnections();
gatewayMetrics.decrementActiveConnections();
assertEquals(1, gatewayMetrics.getActiveConnections());
}
@Test
void testRecordRequestDuration() {
gatewayMetrics.recordRequestDuration("/api/users", Duration.ofMillis(100));
assertNotNull(meterRegistry.find("gateway.request.duration").timer());
}
@Test
void testMultipleIncrements() {
gatewayMetrics.incrementTotalRequests();
gatewayMetrics.incrementTotalRequests();
gatewayMetrics.incrementTotalRequests();
assertEquals(3, gatewayMetrics.getTotalRequests());
}
}
@@ -0,0 +1,139 @@
package cn.novalon.manage.gateway.monitor;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
class PerformanceMonitorTest {
private PerformanceMonitor performanceMonitor;
private MeterRegistry meterRegistry;
@BeforeEach
void setUp() {
meterRegistry = new SimpleMeterRegistry();
performanceMonitor = new PerformanceMonitor(meterRegistry);
}
@Test
void testRecordRequest() {
performanceMonitor.recordRequest("/api/test", 100);
assertEquals(1, performanceMonitor.getPathStats().size());
assertTrue(performanceMonitor.getAverageProcessingTime() > 0);
}
@Test
void testSlowRequestDetection() {
performanceMonitor.setSlowRequestThresholdMs(50);
performanceMonitor.recordRequest("/api/test", 100);
assertEquals(1, performanceMonitor.getPathStats().size());
}
@Test
void testMultipleRequests() {
performanceMonitor.recordRequest("/api/test1", 100);
performanceMonitor.recordRequest("/api/test2", 200);
performanceMonitor.recordRequest("/api/test1", 150);
Map<String, PerformanceMonitor.PerformanceStats> stats = performanceMonitor.getPathStats();
assertEquals(2, stats.size());
PerformanceMonitor.PerformanceStats test1Stats = stats.get("/api/test1");
assertNotNull(test1Stats);
assertEquals(2, test1Stats.getRequestCount());
assertEquals(125.0, test1Stats.getAverageTime());
assertEquals(150, test1Stats.getMaxTime());
assertEquals(100, test1Stats.getMinTime());
}
@Test
void testMemoryStats() {
Map<String, Object> memoryStats = performanceMonitor.getMemoryStats();
assertNotNull(memoryStats);
assertTrue(memoryStats.containsKey("totalMemory"));
assertTrue(memoryStats.containsKey("freeMemory"));
assertTrue(memoryStats.containsKey("usedMemory"));
assertTrue(memoryStats.containsKey("maxMemory"));
assertTrue(memoryStats.containsKey("memoryUsage"));
}
@Test
void testThreadStats() {
Map<String, Object> threadStats = performanceMonitor.getThreadStats();
assertNotNull(threadStats);
assertTrue(threadStats.containsKey("threadCount"));
assertTrue(threadStats.containsKey("peakThreadCount"));
assertTrue(threadStats.containsKey("daemonThreadCount"));
assertTrue(threadStats.containsKey("totalStartedThreadCount"));
}
@Test
void testMemoryUsage() {
double memoryUsage = performanceMonitor.getMemoryUsage();
assertTrue(memoryUsage >= 0.0);
assertTrue(memoryUsage <= 1.0);
}
@Test
void testAverageProcessingTime_NoRequests() {
assertEquals(0.0, performanceMonitor.getAverageProcessingTime());
}
@Test
void testAverageProcessingTime_WithRequests() {
performanceMonitor.recordRequest("/api/test1", 100);
performanceMonitor.recordRequest("/api/test2", 200);
assertEquals(150.0, performanceMonitor.getAverageProcessingTime());
}
@Test
void testClearStats() {
performanceMonitor.recordRequest("/api/test", 100);
performanceMonitor.clearStats();
assertEquals(0, performanceMonitor.getPathStats().size());
assertEquals(0.0, performanceMonitor.getAverageProcessingTime());
}
@Test
void testSetSlowRequestThreshold() {
performanceMonitor.setSlowRequestThresholdMs(500);
performanceMonitor.recordRequest("/api/test", 600);
assertEquals(1, performanceMonitor.getPathStats().size());
}
@Test
void testSetMemoryWarningThreshold() {
performanceMonitor.setMemoryWarningThreshold(0.9);
performanceMonitor.recordRequest("/api/test", 100);
assertEquals(1, performanceMonitor.getPathStats().size());
}
@Test
void testPerformanceStats() {
PerformanceMonitor.PerformanceStats stats = new PerformanceMonitor.PerformanceStats();
stats.recordRequest(100);
stats.recordRequest(200);
stats.recordRequest(150);
assertEquals(3, stats.getRequestCount());
assertEquals(150.0, stats.getAverageTime());
assertEquals(200, stats.getMaxTime());
assertEquals(100, stats.getMinTime());
}
}
@@ -0,0 +1,147 @@
package cn.novalon.manage.gateway.route;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
/**
* DynamicRouteService单元测试
*
* 文件定义:测试动态路由服务的核心功能
* 涉及业务:路由增删改查、路由刷新
*
* @author 张翔
* @date 2026-03-26
*/
@ExtendWith(MockitoExtension.class)
class DynamicRouteServiceTest {
@Mock
private RouteDefinitionWriter routeDefinitionWriter;
@Mock
private RouteDefinitionLocator routeDefinitionLocator;
@Mock
private ApplicationEventPublisher publisher;
private DynamicRouteService dynamicRouteService;
@BeforeEach
void setUp() {
when(routeDefinitionLocator.getRouteDefinitions())
.thenReturn(Flux.empty());
dynamicRouteService = new DynamicRouteService(
routeDefinitionWriter,
routeDefinitionLocator,
publisher);
}
@Test
void testAddRoute_Success() {
RouteDefinition routeDefinition = createRouteDefinition("test-route");
when(routeDefinitionWriter.save(any())).thenReturn(Mono.empty());
StepVerifier.create(dynamicRouteService.addRoute(routeDefinition))
.expectNext(true)
.verifyComplete();
verify(routeDefinitionWriter).save(any());
verify(publisher).publishEvent(any(RefreshRoutesEvent.class));
}
@Test
void testAddRoute_NullRoute() {
StepVerifier.create(dynamicRouteService.addRoute(null))
.expectNext(false)
.verifyComplete();
verify(routeDefinitionWriter, never()).save(any());
}
@Test
void testDeleteRoute_Success() {
String routeId = "test-route";
when(routeDefinitionWriter.delete(any())).thenReturn(Mono.empty());
StepVerifier.create(dynamicRouteService.deleteRoute(routeId))
.expectNext(true)
.verifyComplete();
verify(routeDefinitionWriter).delete(any());
verify(publisher).publishEvent(any(RefreshRoutesEvent.class));
}
@Test
void testDeleteRoute_NullId() {
StepVerifier.create(dynamicRouteService.deleteRoute(null))
.expectNext(false)
.verifyComplete();
verify(routeDefinitionWriter, never()).delete(any());
}
@Test
void testGetAllRoutes() {
RouteDefinition route1 = createRouteDefinition("route1");
RouteDefinition route2 = createRouteDefinition("route2");
when(routeDefinitionWriter.save(any())).thenReturn(Mono.empty());
StepVerifier.create(dynamicRouteService.addRoute(route1))
.expectNext(true)
.verifyComplete();
StepVerifier.create(dynamicRouteService.addRoute(route2))
.expectNext(true)
.verifyComplete();
StepVerifier.create(dynamicRouteService.getAllRoutes().collectList())
.assertNext(routes -> {
assertNotNull(routes);
assertTrue(routes.size() >= 2);
})
.verifyComplete();
}
@Test
void testGetRouteCount() {
RouteDefinition route = createRouteDefinition("test-route");
when(routeDefinitionWriter.save(any())).thenReturn(Mono.empty());
StepVerifier.create(dynamicRouteService.addRoute(route))
.expectNext(true)
.verifyComplete();
assertTrue(dynamicRouteService.getRouteCount() >= 1);
}
private RouteDefinition createRouteDefinition(String id) {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(id);
routeDefinition.setUri(java.net.URI.create("http://localhost:8080"));
return routeDefinition;
}
}
@@ -0,0 +1,188 @@
package cn.novalon.manage.gateway.service.impl;
import cn.novalon.manage.gateway.service.JwtKeyService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import javax.crypto.SecretKey;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class JwtKeyServiceImplTest {
@InjectMocks
private JwtKeyServiceImpl jwtKeyService;
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(jwtKeyService, "configuredSecret", null);
ReflectionTestUtils.setField(jwtKeyService, "encryptionPassword", "testEncryptionPassword");
ReflectionTestUtils.setField(jwtKeyService, "rotationEnabled", true);
}
@Test
void testInitializeKeys_GeneratesNewKey() {
jwtKeyService.initializeKeys();
String version = jwtKeyService.getCurrentKeyVersion();
SecretKey key = jwtKeyService.getCurrentSigningKey();
assertNotNull(version);
assertNotNull(key);
assertEquals("v1", version);
assertEquals("AES", key.getAlgorithm());
}
@Test
void testGenerateSecureKey_GeneratesValidKey() {
String key = jwtKeyService.generateSecureKey();
assertNotNull(key);
assertFalse(key.isEmpty());
assertTrue(jwtKeyService.validateKeyStrength(key));
}
@Test
void testValidateKeyStrength_ValidKey() {
String validKey = "StrongPassword123ABC!@#XYZabcdefg";
assertTrue(jwtKeyService.validateKeyStrength(validKey));
}
@Test
void testValidateKeyStrength_WeakKey() {
String weakKey = "weak";
assertFalse(jwtKeyService.validateKeyStrength(weakKey));
}
@Test
void testValidateKeyStrength_NullKey() {
assertFalse(jwtKeyService.validateKeyStrength(null));
}
@Test
void testValidateKeyStrength_ShortKey() {
String shortKey = "Short1!";
assertFalse(jwtKeyService.validateKeyStrength(shortKey));
}
@Test
void testEncryptKey_WithPassword() {
String originalKey = "MySecretKey123!";
String encryptedKey = jwtKeyService.encryptKey(originalKey);
assertNotNull(encryptedKey);
assertNotEquals(originalKey, encryptedKey);
assertTrue(encryptedKey.length() > originalKey.length());
}
@Test
void testEncryptDecryptKey_RoundTrip() {
String originalKey = "MySecretKey123!";
String encryptedKey = jwtKeyService.encryptKey(originalKey);
String decryptedKey = jwtKeyService.decryptKey(encryptedKey);
assertNotNull(encryptedKey);
assertNotNull(decryptedKey);
assertEquals(originalKey, decryptedKey);
}
@Test
void testRotateKey_CreatesNewVersion() {
jwtKeyService.initializeKeys();
String oldVersion = jwtKeyService.getCurrentKeyVersion();
SecretKey oldKey = jwtKeyService.getCurrentSigningKey();
jwtKeyService.rotateKey();
String newVersion = jwtKeyService.getCurrentKeyVersion();
SecretKey newKey = jwtKeyService.getCurrentSigningKey();
assertNotEquals(oldVersion, newVersion);
assertEquals("v2", newVersion);
assertNotNull(newKey);
assertEquals("AES", newKey.getAlgorithm());
}
@Test
void testGetSigningKeyByVersion_ReturnsCorrectKey() {
jwtKeyService.initializeKeys();
SecretKey v1Key = jwtKeyService.getSigningKeyByVersion("v1");
assertNotNull(v1Key);
assertEquals("AES", v1Key.getAlgorithm());
}
@Test
void testGetSigningKeyByVersion_InvalidVersion() {
jwtKeyService.initializeKeys();
SecretKey invalidKey = jwtKeyService.getSigningKeyByVersion("v999");
assertNull(invalidKey);
}
@Test
void testRotateKey_Disabled() {
ReflectionTestUtils.setField(jwtKeyService, "rotationEnabled", false);
jwtKeyService.initializeKeys();
String oldVersion = jwtKeyService.getCurrentKeyVersion();
jwtKeyService.rotateKey();
String newVersion = jwtKeyService.getCurrentKeyVersion();
assertEquals(oldVersion, newVersion);
}
@Test
void testShouldRotateKey_NewKey() {
jwtKeyService.initializeKeys();
String currentVersion = jwtKeyService.getCurrentKeyVersion();
SecretKey currentKey = jwtKeyService.getCurrentSigningKey();
assertNotNull(currentVersion, "Current version should not be null");
assertNotNull(currentKey, "Current signing key should not be null");
}
@Test
void testMultipleRotations_CreatesMultipleVersions() {
jwtKeyService.initializeKeys();
jwtKeyService.rotateKey();
assertEquals("v2", jwtKeyService.getCurrentKeyVersion());
jwtKeyService.rotateKey();
assertEquals("v3", jwtKeyService.getCurrentKeyVersion());
jwtKeyService.rotateKey();
assertEquals("v4", jwtKeyService.getCurrentKeyVersion());
assertNotNull(jwtKeyService.getSigningKeyByVersion("v1"));
assertNotNull(jwtKeyService.getSigningKeyByVersion("v2"));
assertNotNull(jwtKeyService.getSigningKeyByVersion("v3"));
assertNotNull(jwtKeyService.getSigningKeyByVersion("v4"));
}
@Test
void testEncryptKey_WithoutPassword() {
ReflectionTestUtils.setField(jwtKeyService, "encryptionPassword", "");
String originalKey = "MySecretKey123!";
String encryptedKey = jwtKeyService.encryptKey(originalKey);
assertEquals(originalKey, encryptedKey);
}
@Test
void testDecryptKey_WithoutPassword() {
ReflectionTestUtils.setField(jwtKeyService, "encryptionPassword", "");
String originalKey = "MySecretKey123!";
String decryptedKey = jwtKeyService.decryptKey(originalKey);
assertEquals(originalKey, decryptedKey);
}
}
@@ -0,0 +1,245 @@
package cn.novalon.manage.gateway.service.impl;
import cn.novalon.manage.gateway.model.Permission;
import cn.novalon.manage.gateway.model.Role;
import cn.novalon.manage.gateway.model.User;
import cn.novalon.manage.gateway.service.PermissionService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersUriSpec;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
import org.springframework.web.reactive.function.client.WebClient.ResponseSpec;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PermissionServiceImplTest {
@Mock
private WebClient.Builder webClientBuilder;
@Mock
private WebClient webClient;
@Mock
private RequestHeadersUriSpec requestHeadersUriSpec;
@Mock
private RequestHeadersSpec requestHeadersSpec;
@Mock
private ResponseSpec responseSpec;
private PermissionService permissionService;
@BeforeEach
void setUp() {
when(webClientBuilder.build()).thenReturn(webClient);
permissionService = new PermissionServiceImpl(webClientBuilder, "http://localhost:8084");
}
@Test
void testGetUserById_Success() {
User expectedUser = new User(1L, "testuser", "test@example.com", "1234567890", 1, System.currentTimeMillis(), System.currentTimeMillis());
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(eq(User.class))).thenReturn(Mono.just(expectedUser));
User user = permissionService.getUserById(1L);
assertNotNull(user);
assertEquals("testuser", user.getUsername());
verify(webClient).get();
}
@Test
void testGetUserById_NullUserId() {
User user = permissionService.getUserById(null);
assertNull(user);
verify(webClient, never()).get();
}
@Test
void testGetUserRoles_Success() {
List<Role> expectedRoles = Arrays.asList(
new Role(1L, "ADMIN", "Administrator", "Admin role", 1, System.currentTimeMillis(), System.currentTimeMillis()),
new Role(2L, "USER", "User", "User role", 1, System.currentTimeMillis(), System.currentTimeMillis())
);
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(eq(Role[].class))).thenReturn(Mono.just(expectedRoles.toArray(new Role[0])));
List<Role> roles = permissionService.getUserRoles(1L);
assertNotNull(roles);
assertEquals(2, roles.size());
verify(webClient).get();
}
@Test
void testGetUserRoles_NullUserId() {
List<Role> roles = permissionService.getUserRoles(null);
assertNotNull(roles);
assertTrue(roles.isEmpty());
verify(webClient, never()).get();
}
@Test
void testGetUserPermissions_Success() {
Set<Permission> expectedPermissions = new HashSet<>(Arrays.asList(
new Permission(1L, "user:read", "Read User", "API", "/api/users/**", "GET", "Read user permissions", 1, System.currentTimeMillis(), System.currentTimeMillis()),
new Permission(2L, "user:write", "Write User", "API", "/api/users/**", "POST", "Write user permissions", 1, System.currentTimeMillis(), System.currentTimeMillis())
));
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(eq(Permission[].class))).thenReturn(Mono.just(expectedPermissions.toArray(new Permission[0])));
Set<Permission> permissions = permissionService.getUserPermissions(1L);
assertNotNull(permissions);
assertEquals(2, permissions.size());
verify(webClient).get();
}
@Test
void testGetUserPermissions_NullUserId() {
Set<Permission> permissions = permissionService.getUserPermissions(null);
assertNotNull(permissions);
assertTrue(permissions.isEmpty());
verify(webClient, never()).get();
}
@Test
void testHasPermission_True() {
Set<Permission> permissions = new HashSet<>(Arrays.asList(
new Permission(1L, "user:read", "Read User", "API", "/api/users/**", "GET", "Read user permissions", 1, System.currentTimeMillis(), System.currentTimeMillis())
));
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(eq(Permission[].class))).thenReturn(Mono.just(permissions.toArray(new Permission[0])));
boolean hasPermission = permissionService.hasPermission(1L, "/api/users/123", "GET");
assertTrue(hasPermission);
}
@Test
void testHasPermission_False() {
Set<Permission> permissions = new HashSet<>(Arrays.asList(
new Permission(1L, "user:read", "Read User", "API", "/api/users/**", "GET", "Read user permissions", 1, System.currentTimeMillis(), System.currentTimeMillis())
));
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(eq(Permission[].class))).thenReturn(Mono.just(permissions.toArray(new Permission[0])));
boolean hasPermission = permissionService.hasPermission(1L, "/api/users/123", "POST");
assertFalse(hasPermission);
}
@Test
void testHasPermission_NullUserId() {
boolean hasPermission = permissionService.hasPermission(null, "/api/users/123", "GET");
assertFalse(hasPermission);
verify(webClient, never()).get();
}
@Test
void testGetPermissionPaths_Success() {
Set<Permission> permissions = new HashSet<>(Arrays.asList(
new Permission(1L, "user:read", "Read User", "API", "/api/users/**", "GET", "Read user permissions", 1, System.currentTimeMillis(), System.currentTimeMillis()),
new Permission(2L, "user:write", "Write User", "API", "/api/users/**", "POST", "Write user permissions", 1, System.currentTimeMillis(), System.currentTimeMillis())
));
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(eq(Permission[].class))).thenReturn(Mono.just(permissions.toArray(new Permission[0])));
Set<String> paths = permissionService.getPermissionPaths(1L, "GET");
assertNotNull(paths);
assertEquals(1, paths.size());
assertTrue(paths.contains("/api/users/**"));
}
@Test
void testClearCache_Success() {
User user = new User(1L, "testuser", "test@example.com", "1234567890", 1, System.currentTimeMillis(), System.currentTimeMillis());
List<Role> roles = Arrays.asList(new Role(1L, "ADMIN", "Administrator", "Admin role", 1, System.currentTimeMillis(), System.currentTimeMillis()));
Set<Permission> permissions = new HashSet<>(Arrays.asList(
new Permission(1L, "user:read", "Read User", "API", "/api/users/**", "GET", "Read user permissions", 1, System.currentTimeMillis(), System.currentTimeMillis())
));
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(eq(User.class))).thenReturn(Mono.just(user));
when(responseSpec.bodyToMono(eq(Role[].class))).thenReturn(Mono.just(roles.toArray(new Role[0])));
when(responseSpec.bodyToMono(eq(Permission[].class))).thenReturn(Mono.just(permissions.toArray(new Permission[0])));
permissionService.getUserById(1L);
permissionService.getUserRoles(1L);
permissionService.getUserPermissions(1L);
permissionService.clearCache(1L);
verify(webClient, times(3)).get();
}
@Test
void testClearAllCache_Success() {
User user = new User(1L, "testuser", "test@example.com", "1234567890", 1, System.currentTimeMillis(), System.currentTimeMillis());
List<Role> roles = Arrays.asList(new Role(1L, "ADMIN", "Administrator", "Admin role", 1, System.currentTimeMillis(), System.currentTimeMillis()));
Set<Permission> permissions = new HashSet<>(Arrays.asList(
new Permission(1L, "user:read", "Read User", "API", "/api/users/**", "GET", "Read user permissions", 1, System.currentTimeMillis(), System.currentTimeMillis())
));
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(eq(User.class))).thenReturn(Mono.just(user));
when(responseSpec.bodyToMono(eq(Role[].class))).thenReturn(Mono.just(roles.toArray(new Role[0])));
when(responseSpec.bodyToMono(eq(Permission[].class))).thenReturn(Mono.just(permissions.toArray(new Permission[0])));
permissionService.getUserById(1L);
permissionService.getUserRoles(1L);
permissionService.getUserPermissions(1L);
permissionService.clearAllCache();
permissionService.getUserById(1L);
permissionService.getUserRoles(1L);
permissionService.getUserPermissions(1L);
verify(webClient, times(6)).get();
}
}
@@ -0,0 +1,248 @@
package cn.novalon.manage.gateway.service.impl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpMethod;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
/**
* SignatureServiceImpl单元测试
*
* 文件定义:测试签名服务的核心功能
* 涉及业务:签名生成、签名验证、时间戳验证、nonce防重放
*
* @author 张翔
* @date 2026-03-26
*/
@ExtendWith(MockitoExtension.class)
class SignatureServiceImplTest {
@InjectMocks
private SignatureServiceImpl signatureService;
private static final String TEST_SECRET = "TestSecretKey123";
private static final String TEST_METHOD = "GET";
private static final String TEST_PATH = "/api/users";
private static final String TEST_QUERY = "page=1&size=10";
private static final String TEST_BODY = "";
private static final String TEST_NONCE = "test-nonce-12345";
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(signatureService, "signatureEnabled", true);
ReflectionTestUtils.setField(signatureService, "maxAgeMinutes", 5);
ReflectionTestUtils.setField(signatureService, "nonceCacheSize", 10000);
}
@Test
void testGenerateSignature_ShouldGenerateValidSignature() {
long timestamp = System.currentTimeMillis();
String signature = signatureService.generateSignature(
TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
assertNotNull(signature);
assertFalse(signature.isEmpty());
assertTrue(signature.length() > 0);
}
@Test
void testGenerateSignature_ShouldGenerateSameSignatureForSameInput() {
long timestamp = System.currentTimeMillis();
String signature1 = signatureService.generateSignature(
TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
String signature2 = signatureService.generateSignature(
TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
assertEquals(signature1, signature2);
}
@Test
void testGenerateSignature_ShouldGenerateDifferentSignatureForDifferentInput() {
long timestamp = System.currentTimeMillis();
String signature1 = signatureService.generateSignature(
TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
String signature2 = signatureService.generateSignature(
"POST", TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
assertNotEquals(signature1, signature2);
}
@Test
void testVerifySignature_WithValidSignature_ShouldReturnTrue() {
long timestamp = System.currentTimeMillis();
String signature = signatureService.generateSignature(
TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, TEST_PATH + "?" + TEST_QUERY)
.header("X-Signature", signature)
.header("X-Timestamp", String.valueOf(timestamp))
.header("X-Nonce", TEST_NONCE)
.build();
boolean isValid = signatureService.verifySignature(request, TEST_SECRET);
assertTrue(isValid);
}
@Test
void testVerifySignature_WithInvalidSignature_ShouldReturnFalse() {
long timestamp = System.currentTimeMillis();
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, TEST_PATH)
.header("X-Signature", "invalid-signature")
.header("X-Timestamp", String.valueOf(timestamp))
.header("X-Nonce", TEST_NONCE)
.build();
boolean isValid = signatureService.verifySignature(request, TEST_SECRET);
assertFalse(isValid);
}
@Test
void testVerifySignature_WithMissingHeaders_ShouldReturnFalse() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, TEST_PATH)
.build();
boolean isValid = signatureService.verifySignature(request, TEST_SECRET);
assertFalse(isValid);
}
@Test
void testVerifySignature_WithExpiredTimestamp_ShouldReturnFalse() {
long expiredTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10);
String signature = signatureService.generateSignature(
TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, expiredTimestamp, TEST_NONCE, TEST_SECRET);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, TEST_PATH)
.header("X-Signature", signature)
.header("X-Timestamp", String.valueOf(expiredTimestamp))
.header("X-Nonce", TEST_NONCE)
.build();
boolean isValid = signatureService.verifySignature(request, TEST_SECRET);
assertFalse(isValid);
}
@Test
void testVerifySignature_WithUsedNonce_ShouldReturnFalse() {
long timestamp = System.currentTimeMillis();
String signature = signatureService.generateSignature(
TEST_METHOD, TEST_PATH, TEST_QUERY, TEST_BODY, timestamp, TEST_NONCE, TEST_SECRET);
signatureService.recordNonce(TEST_NONCE);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, TEST_PATH)
.header("X-Signature", signature)
.header("X-Timestamp", String.valueOf(timestamp))
.header("X-Nonce", TEST_NONCE)
.build();
boolean isValid = signatureService.verifySignature(request, TEST_SECRET);
assertFalse(isValid);
}
@Test
void testIsTimestampValid_WithValidTimestamp_ShouldReturnTrue() {
long validTimestamp = System.currentTimeMillis();
boolean isValid = signatureService.isTimestampValid(validTimestamp, 5);
assertTrue(isValid);
}
@Test
void testIsTimestampValid_WithExpiredTimestamp_ShouldReturnFalse() {
long expiredTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10);
boolean isValid = signatureService.isTimestampValid(expiredTimestamp, 5);
assertFalse(isValid);
}
@Test
void testIsTimestampValid_WithFutureTimestamp_ShouldReturnFalse() {
long futureTimestamp = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10);
boolean isValid = signatureService.isTimestampValid(futureTimestamp, 5);
assertFalse(isValid);
}
@Test
void testIsNonceUsed_WithNewNonce_ShouldReturnFalse() {
boolean isUsed = signatureService.isNonceUsed("new-nonce-123");
assertFalse(isUsed);
}
@Test
void testIsNonceUsed_WithUsedNonce_ShouldReturnTrue() {
String nonce = "used-nonce-123";
signatureService.recordNonce(nonce);
boolean isUsed = signatureService.isNonceUsed(nonce);
assertTrue(isUsed);
}
@Test
void testRecordNonce_ShouldIncreaseCacheSize() {
int initialSize = signatureService.getNonceCacheSize();
signatureService.recordNonce("test-nonce-1");
signatureService.recordNonce("test-nonce-2");
signatureService.recordNonce("test-nonce-3");
int finalSize = signatureService.getNonceCacheSize();
assertEquals(initialSize + 3, finalSize);
}
@Test
void testCleanupExpiredNonces_ShouldRemoveExpiredEntries() {
ReflectionTestUtils.setField(signatureService, "nonceCacheSize", 5);
signatureService.recordNonce("nonce-1");
signatureService.recordNonce("nonce-2");
signatureService.recordNonce("nonce-3");
signatureService.recordNonce("nonce-4");
signatureService.recordNonce("nonce-5");
signatureService.recordNonce("nonce-6");
int cacheSize = signatureService.getNonceCacheSize();
assertTrue(cacheSize <= 6);
}
@Test
void testVerifySignature_WhenDisabled_ShouldReturnTrue() {
ReflectionTestUtils.setField(signatureService, "signatureEnabled", false);
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, TEST_PATH)
.build();
boolean isValid = signatureService.verifySignature(request, TEST_SECRET);
assertTrue(isValid);
}
}
@@ -0,0 +1,32 @@
spring:
application:
name: manage-gateway
cloud:
gateway:
routes:
- id: user-service
uri: http://localhost:8084
predicates:
- Path=/api/users/**
- id: auth-service
uri: http://localhost:8083
predicates:
- Path=/api/auth/**
user:
service:
url: http://localhost:8084
permission:
cache:
expiry:
minutes: 5
logging:
level:
cn.novalon.manage.gateway: DEBUG
org.springframework.cloud.gateway: DEBUG
org.springframework.web.reactive: DEBUG
server:
port: 8080