复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
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.redisson.api.RRateLimiter;
import org.redisson.api.RedissonClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.context.RetryContextSupport;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
/**
* 飞书通知处理器单元测试
*/
@ExtendWith(MockitoExtension.class)
class FeiShuNotificationHandlerTest {
@Mock
private RestTemplate restTemplate;
@Mock
private ObjectMapper objectMapper;
@Mock
private RetryTemplate retryTemplate;
@Mock
private RRateLimiterService rRateLimiterService;
@Mock
private RedissonClient redisson;
@Mock
private RRateLimiter secondRateLimiter;
@Mock
private RRateLimiter minuteRateLimiter;
@InjectMocks
private FeiShuNotificationHandler feiShuNotificationHandler;
private Map<String, Object> validParams;
private String testUrl = "https://open.feishu.cn/open-apis/bot/v2/hook/test";
private String testSecret = "testSecret";
private String testText = "测试消息";
@BeforeEach
void setUp() {
validParams = new HashMap<>();
validParams.put("url", testUrl);
validParams.put("secret", testSecret);
validParams.put("text", testText);
}
/**
* 测试getNotifyMethod方法
*/
@Test
void testGetNotifyMethod() {
// 执行方法
NotifyMethodEnum result = feiShuNotificationHandler.getNotifyMethod();
// 验证结果
assertEquals(NotifyMethodEnum.FEISHU, result);
}
/**
* 测试notify方法成功场景
*/
@Test
void testNotifySuccess() throws Exception {
// 准备测试数据
String successResponse = "{\"code\":0,\"msg\":\"success\"}";
FeiShuResponse feiShuResponse = new FeiShuResponse();
feiShuResponse.setCode(0);
feiShuResponse.setMsg("success");
// 模拟RestTemplate和ObjectMapper的行为
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
.thenReturn(new ResponseEntity<>(successResponse, HttpStatus.OK));
when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
.thenReturn(feiShuResponse);
// 执行方法
boolean result = feiShuNotificationHandler.notify(validParams);
// 验证结果
assertTrue(result);
}
/**
* 测试notify方法成功场景 - msg为success
*/
@Test
void testNotifySuccessWithMsg() throws Exception {
// 准备测试数据
String successResponse = "{\"code\":1,\"msg\":\"success\"}";
FeiShuResponse feiShuResponse = new FeiShuResponse();
feiShuResponse.setCode(1);
feiShuResponse.setMsg("success");
// 模拟RestTemplate和ObjectMapper的行为
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
.thenReturn(new ResponseEntity<>(successResponse, HttpStatus.OK));
when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
.thenReturn(feiShuResponse);
// 执行方法
boolean result = feiShuNotificationHandler.notify(validParams);
// 验证结果
assertTrue(result);
}
/**
* 测试notify方法失败场景 - HTTP状态码非2xx
*/
@Test
void testNotifyHttpError() {
// 模拟RestTemplate返回非2xx状态码
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
.thenReturn(new ResponseEntity<>("Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR));
// 执行方法
boolean result = feiShuNotificationHandler.notify(validParams);
// 验证结果
assertFalse(result);
}
/**
* 测试notify方法失败场景 - 飞书API返回错误
*/
@Test
void testNotifyApiError() throws Exception {
// 准备测试数据
String errorResponse = "{\"code\":10001,\"msg\":\"参数错误\"}";
FeiShuResponse feiShuResponse = new FeiShuResponse();
feiShuResponse.setCode(10001);
feiShuResponse.setMsg("参数错误");
// 模拟RestTemplate和ObjectMapper的行为
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
.thenReturn(new ResponseEntity<>(errorResponse, HttpStatus.OK));
when(objectMapper.readValue(eq(errorResponse), eq(FeiShuResponse.class)))
.thenReturn(feiShuResponse);
// 执行方法
boolean result = feiShuNotificationHandler.notify(validParams);
// 验证结果
assertFalse(result);
}
/**
* 测试notify方法异常场景 - 参数为空
*/
@Test
void testNotifyWithEmptyParams() {
// 准备空参数
Map<String, Object> emptyParams = new HashMap<>();
// 执行方法并验证异常
assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(emptyParams));
}
/**
* 测试notify方法异常场景 - url为空
*/
@Test
void testNotifyWithEmptyUrl() {
// 准备缺少url的参数
Map<String, Object> paramsWithoutUrl = new HashMap<>();
paramsWithoutUrl.put("secret", testSecret);
paramsWithoutUrl.put("text", testText);
// 执行方法并验证异常
assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(paramsWithoutUrl));
}
/**
* 测试notify方法异常场景 - secret为空
*/
@Test
void testNotifyWithEmptySecret() {
// 准备缺少secret的参数
Map<String, Object> paramsWithoutSecret = new HashMap<>();
paramsWithoutSecret.put("url", testUrl);
paramsWithoutSecret.put("text", testText);
// 执行方法并验证异常
assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(paramsWithoutSecret));
}
/**
* 测试notify方法异常场景 - text为空
*/
@Test
void testNotifyWithEmptyText() {
// 准备缺少text的参数
Map<String, Object> paramsWithoutText = new HashMap<>();
paramsWithoutText.put("url", testUrl);
paramsWithoutText.put("secret", testSecret);
// 执行方法并验证异常
assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(paramsWithoutText));
}
/**
* 测试notify方法异常场景 - RestTemplate抛出异常
*/
@Test
void testNotifyWithRestTemplateException() {
// 模拟RestTemplate抛出异常
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
.thenThrow(new AlarmException(CommonResultEnum.REMOTE_FAIL));
// 执行方法并验证异常
assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(validParams));
}
/**
* 测试notify方法异常场景 - ObjectMapper抛出异常
*/
@Test
void testNotifyWithObjectMapperException() throws Exception {
// 模拟RestTemplate正常返回,但ObjectMapper解析异常
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
.thenReturn(new ResponseEntity<>("Invalid JSON", HttpStatus.OK));
when(objectMapper.readValue(eq("Invalid JSON"), eq(FeiShuResponse.class)))
.thenThrow(new RuntimeException("JSON parse error"));
// 执行方法并验证异常
assertThrows(AlarmException.class, () -> feiShuNotificationHandler.notify(validParams));
}
/**
* 测试makeSign方法
*/
@Test
void testMakeSign() throws Exception {
// 使用反射调用私有方法
String sign = (String) ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "makeSign", testSecret, 1638360000L);
// 验证签名不为空
assertNotNull(sign);
assertFalse(sign.isEmpty());
// 验证相同输入产生相同签名
String sign2 = (String) ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "makeSign", testSecret, 1638360000L);
assertEquals(sign, sign2);
// 验证不同输入产生不同签名
String sign3 = (String) ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "makeSign", "differentSecret", 1638360000L);
assertNotEquals(sign, sign3);
String sign4 = (String) ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "makeSign", testSecret, 1638360001L);
assertNotEquals(sign, sign4);
}
/**
* 测试FeiShuResponse的isSuccess方法
*/
@Test
void testFeiShuResponseIsSuccess() {
// 测试code为0的情况
FeiShuResponse response1 = new FeiShuResponse();
response1.setCode(0);
assertTrue(response1.isSuccess());
// 测试msg为success的情况
FeiShuResponse response2 = new FeiShuResponse();
response2.setMsg("success");
assertTrue(response2.isSuccess());
// 测试code为0且msg为success的情况
FeiShuResponse response3 = new FeiShuResponse();
response3.setCode(0);
response3.setMsg("success");
assertTrue(response3.isSuccess());
// 测试code不为0且msg不为success的情况
FeiShuResponse response4 = new FeiShuResponse();
response4.setCode(1);
response4.setMsg("error");
assertFalse(response4.isSuccess());
// 测试code为null但msg为success的情况
FeiShuResponse response5 = new FeiShuResponse();
response5.setCode(null);
response5.setMsg("success");
assertTrue(response5.isSuccess());
// 测试code为0但msg为null的情况
FeiShuResponse response6 = new FeiShuResponse();
response6.setCode(0);
response6.setMsg(null);
assertTrue(response6.isSuccess());
}
/**
* 测试重试机制 - 第一次尝试成功
*/
@Test
void testRetryMechanismFirstAttemptSuccess() throws Exception {
// 准备测试数据
String successResponse = "{\"code\":0,\"msg\":\"success\"}";
FeiShuResponse feiShuResponse = new FeiShuResponse();
feiShuResponse.setCode(0);
feiShuResponse.setMsg("success");
// 模拟RestTemplate的行为
ResponseEntity<String> responseEntity = new ResponseEntity<>(successResponse, HttpStatus.OK);
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
.thenReturn(responseEntity);
// 模拟ObjectMapper的行为
when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
.thenReturn(feiShuResponse);
// 模拟RetryTemplate的行为
when(retryTemplate.execute(any())).thenAnswer(invocation -> {
// 获取回调函数
RetryCallback<ResponseEntity<String>, Exception> callback =
invocation.getArgument(0);
// 创建模拟的RetryContext,传入parent上下文而不是null
RetryContext parentContext = new RetryContextSupport(null);
RetryContext context = new RetryContextSupport(parentContext);
// 执行回调,模拟第一次尝试成功
try {
return callback.doWithRetry(context);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// 执行方法
boolean result = feiShuNotificationHandler.notify(validParams);
// 验证结果
assertTrue(result);
// 验证RestTemplate只被调用了一次
// 注意:由于使用了RetryTemplate,我们无法直接验证调用次数,但可以通过日志或其他方式验证
}
/**
* 测试重试机制 - 第二次尝试成功
*/
@Test
void testRetryMechanismSecondAttemptSuccess() throws Exception {
// 准备测试数据
String successResponse = "{\"code\":0,\"msg\":\"success\"}";
FeiShuResponse feiShuResponse = new FeiShuResponse();
feiShuResponse.setCode(0);
feiShuResponse.setMsg("success");
// 记录RestTemplate调用次数
final int[] restTemplateCallCount = {0};
// 模拟RestTemplate的行为 - 第一次调用失败,第二次调用成功
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class))).thenAnswer(invocation -> {
restTemplateCallCount[0]++;
if (restTemplateCallCount[0] == 1) {
// 第一次调用失败
System.out.println("First attempt failed");
throw new RuntimeException("First attempt failed");
} else {
// 第二次调用成功
System.out.println("Second attempt success");
return new ResponseEntity<>(successResponse, HttpStatus.OK);
}
});
when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
.thenReturn(feiShuResponse);
// 模拟RetryTemplate的行为 - 执行重试逻辑
when(retryTemplate.execute(any())).thenAnswer(invocation -> {
// 获取回调函数
RetryCallback<ResponseEntity<String>, Exception> callback = invocation.getArgument(0);
// 创建模拟的RetryContext
RetryContext parentContext = new RetryContextSupport(null);
RetryContext context = new RetryContextSupport(parentContext);
try {
// 第一次尝试
System.out.println("First attempt");
return callback.doWithRetry(context);
} catch (Exception e) {
// 第一次失败,进行重试
try {
// 第二次尝试
System.out.println("Second attempt");
return callback.doWithRetry(context);
} catch (Exception retryException) {
// 重试也失败,抛出异常
throw new RuntimeException(retryException);
}
}
});
// 执行方法
boolean result = feiShuNotificationHandler.notify(validParams);
// 验证结果
assertTrue(result);
// 验证RestTemplate被调用了两次
assertEquals(2, restTemplateCallCount[0]);
}
/**
* 测试initRateLimiters方法
*/
@Test
void testInitRateLimiters() {
// 使用反射调用私有方法
ReflectionTestUtils.invokeMethod(feiShuNotificationHandler, "initRateLimiters");
// 验证RRateLimiterService的setRRateLimiter方法被正确调用
// 秒级限流器:5次/秒
org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:second", 5, 1000);
// 分钟级限流器:100次/分钟
org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:minute", 100, 60 * 1000);
}
/**
* 测试acquireRateLimitPermits方法 - 两个限流器都成功获取令牌
*/
@Test
void testAcquireRateLimitPermitsSuccess() {
// 模拟限流器服务返回true
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(true);
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(true);
// 使用反射调用私有方法
Boolean result = ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "acquireRateLimitPermits");
// 验证结果
assertTrue(result);
// 验证限流器服务被正确调用
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
}
/**
* 测试acquireRateLimitPermits方法 - 秒级限流器失败
*/
@Test
void testAcquireRateLimitPermitsSecondLimiterFail() {
// 模拟秒级限流器失败,分钟级限流器成功
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(false);
// 使用反射调用私有方法
Boolean result = ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "acquireRateLimitPermits");
// 验证结果
assertFalse(result);
// 验证秒级限流器被调用
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
// 分钟级限流器不应该被调用,因为秒级已经失败
org.mockito.Mockito.verify(rRateLimiterService, org.mockito.Mockito.never()).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
}
/**
* 测试acquireRateLimitPermits方法 - 分钟级限流器失败
*/
@Test
void testAcquireRateLimitPermitsMinuteLimiterFail() {
// 模拟秒级限流器成功,分钟级限流器失败
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(true);
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(false);
// 使用反射调用私有方法
Boolean result = (Boolean) ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "acquireRateLimitPermits");
// 验证结果
assertFalse(result);
// 验证两个限流器都被调用
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
}
/**
* 测试waitForRateLimitPermits方法
*/
@Test
void testWaitForRateLimitPermits() {
// 模拟Redisson客户端返回RRateLimiter
when(redisson.getRateLimiter("feishu:rate:limiter:second")).thenReturn(secondRateLimiter);
when(redisson.getRateLimiter("feishu:rate:limiter:minute")).thenReturn(minuteRateLimiter);
// 使用反射调用私有方法
ReflectionTestUtils.invokeMethod(feiShuNotificationHandler, "waitForRateLimitPermits");
// 验证Redisson客户端被正确调用
org.mockito.Mockito.verify(redisson).getRateLimiter("feishu:rate:limiter:second");
org.mockito.Mockito.verify(redisson).getRateLimiter("feishu:rate:limiter:minute");
// 验证RRateLimiter的acquire方法被调用
org.mockito.Mockito.verify(secondRateLimiter).acquire();
org.mockito.Mockito.verify(minuteRateLimiter).acquire();
}
/**
* 测试doNotify方法 - 限流器成功获取令牌
*/
@Test
void testDoNotifyRateLimitSuccess() throws Exception {
// 准备测试数据
String successResponse = "{\"code\":0,\"msg\":\"success\"}";
ResponseEntity<String> responseEntity = new ResponseEntity<>(successResponse, HttpStatus.OK);
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(new HashMap<>());
// 模拟限流器服务返回true,表示成功获取令牌
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(true);
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(true);
// 模拟RetryTemplate的行为
when(retryTemplate.execute(any())).thenReturn(responseEntity);
// 使用反射调用私有方法
ResponseEntity<String> result = ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "doNotify", testUrl, testSecret, testText, requestEntity);
// 验证结果
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(successResponse, result.getBody());
// 验证限流器服务被调用
org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:second", 5, 1000);
org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:minute", 100, 60 * 1000);
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
}
/**
* 测试doNotify方法 - 限流器失败后等待获取令牌
*/
@Test
void testDoNotifyRateLimitWaitAndSuccess() throws Exception {
// 准备测试数据
String successResponse = "{\"code\":0,\"msg\":\"success\"}";
ResponseEntity<String> responseEntity = new ResponseEntity<>(successResponse, HttpStatus.OK);
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(new HashMap<>());
// 模拟限流器服务返回false,表示获取令牌失败
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(false);
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(true);
// 模拟Redisson客户端返回RRateLimiter
when(redisson.getRateLimiter("feishu:rate:limiter:second")).thenReturn(secondRateLimiter);
when(redisson.getRateLimiter("feishu:rate:limiter:minute")).thenReturn(minuteRateLimiter);
// 模拟RetryTemplate的行为
when(retryTemplate.execute(any())).thenReturn(responseEntity);
// 使用反射调用私有方法
ResponseEntity<String> result = ReflectionTestUtils.invokeMethod(
feiShuNotificationHandler, "doNotify", testUrl, testSecret, testText, requestEntity);
// 验证结果
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(successResponse, result.getBody());
// 验证限流器服务被调用
org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:second", 5, 1000);
org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:minute", 100, 60 * 1000);
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
// 验证Redisson客户端被调用
org.mockito.Mockito.verify(redisson).getRateLimiter("feishu:rate:limiter:second");
org.mockito.Mockito.verify(redisson).getRateLimiter("feishu:rate:limiter:minute");
// 验证RRateLimiter的acquire方法被调用
org.mockito.Mockito.verify(secondRateLimiter).acquire();
org.mockito.Mockito.verify(minuteRateLimiter).acquire();
}
/**
* 测试notify方法 - 包含限流功能
*/
@Test
void testNotifyWithRateLimit() throws Exception {
// 准备测试数据
String successResponse = "{\"code\":0,\"msg\":\"success\"}";
FeiShuResponse feiShuResponse = new FeiShuResponse();
feiShuResponse.setCode(0);
feiShuResponse.setMsg("success");
// 模拟限流器服务返回true,表示成功获取令牌
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:second", 1000)).thenReturn(true);
when(rRateLimiterService.tryAcquire("feishu:rate:limiter:minute", 60 * 1000)).thenReturn(true);
// 模拟RestTemplate和ObjectMapper的行为
when(restTemplate.postForEntity(eq(testUrl), any(HttpEntity.class), eq(String.class)))
.thenReturn(new ResponseEntity<>(successResponse, HttpStatus.OK));
when(objectMapper.readValue(eq(successResponse), eq(FeiShuResponse.class)))
.thenReturn(feiShuResponse);
// 模拟RetryTemplate的行为
when(retryTemplate.execute(any())).thenAnswer(invocation -> {
// 获取回调函数
RetryCallback<ResponseEntity<String>, Exception> callback =
invocation.getArgument(0);
// 创建模拟的RetryContext
RetryContext parentContext = new RetryContextSupport(null);
RetryContext context = new RetryContextSupport(parentContext);
// 执行回调,模拟第一次尝试成功
try {
return callback.doWithRetry(context);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// 执行方法
boolean result = feiShuNotificationHandler.notify(validParams);
// 验证结果
assertTrue(result);
// 验证限流器服务被调用
org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:second", 5, 1000);
org.mockito.Mockito.verify(rRateLimiterService).setRRateLimiter("feishu:rate:limiter:minute", 100, 60 * 1000);
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:second", 1000);
org.mockito.Mockito.verify(rRateLimiterService).tryAcquire("feishu:rate:limiter:minute", 60 * 1000);
}
}