Merge branch 'master' into xsj_branch

This commit is contained in:
xuesijing 2025-03-07 10:32:44 +08:00
commit 6fc79a75a4
39 changed files with 1084 additions and 232 deletions

28
pom.xml
View File

@ -80,6 +80,11 @@
<artifactId>sqlite-jdbc</artifactId>
<version>3.49.0.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
@ -140,6 +145,16 @@
<artifactId>gzs-common-oss</artifactId>
<version>0.1.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>8.0</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.9.0</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -176,8 +191,9 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
@ -245,6 +261,14 @@
<groupId>com.cmcc.hy</groupId>
<artifactId>gzs-common-oss</artifactId>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>

View File

@ -47,9 +47,12 @@ public class CircularQueue<T> {
if (isFull()) {
dequeue();
}
data[tail] = element; // 将元素插入尾部
tail = (tail + 1) % capacity; // 更新尾指针循环利用空间
size++; // 增加队列元素数量
// 将元素插入尾部
data[tail] = element;
// 更新尾指针循环利用空间
tail = (tail + 1) % capacity;
// 增加队列元素数量
size++;
return true;
}

View File

@ -64,6 +64,16 @@ public class CommonConfigure {
*/
public static String PROJECT_PREFIX_URL;
/**
* 本机服务IP地址
*/
public static String LOCAL_IP_ADDR;
/**
* 本机服务端口
*/
public static Short LOCAL_PORT;
/**
* 服务配置属性
* <p>
@ -127,10 +137,14 @@ public class CommonConfigure {
*
* @param prefixUrl 项目上下文路径Context Path
* @param baseUrl 项目的基础 URL包括协议主机地址和端口号
* @param ipAddr 本机服务IP地址
* @param port 本机服务端口号
*/
private static void setGlobalVars(String prefixUrl, String baseUrl) {
private static void setGlobalVars(String prefixUrl, String baseUrl, String ipAddr, Integer port) {
PROJECT_PREFIX_URL = prefixUrl;
BASEURL = baseUrl;
LOCAL_IP_ADDR = ipAddr;
LOCAL_PORT = port.shortValue();
}
/**
@ -176,7 +190,9 @@ public class CommonConfigure {
log.error("Unable get local ip address: {}", e.getMessage());
} finally {
setGlobalVars(serverProperties.getServlet().getContextPath(),
"http://" + addr + ":" + serverProperties.getPort() + serverProperties.getServlet().getContextPath());
"http://" + addr + ":" + serverProperties.getPort() + serverProperties.getServlet().getContextPath(),
addr,
serverProperties.getPort());
log.info("baseUrl: {}", BASEURL);
}

View File

@ -0,0 +1,60 @@
package com.cmcc.magent.config;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.h2.tools.Server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* H2 数据库配置类用于在 Spring Boot 应用中启动 H2 数据库的 TCP 服务器
* 该类通过条件注解 {@code @ConditionalOnProperty} 控制是否启用 H2 数据库服务器
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-02-27
*/
@Configuration
@ConditionalOnProperty(name = "spring.h2.server.enable", havingValue = "true")
@Slf4j
public class H2DataBaseConfig {
/**
* 通用配置类用于获取本地 IP 地址等信息
*/
@Resource
private CommonConfigure commonConfigure;
/**
* H2 数据库 TCP 服务器的端口号通过配置文件 {@code spring.h2.server.port} 注入
*/
@Value("${spring.h2.server.port}")
private Integer tcpPort;
/**
* 数据源连接字符串通过配置文件 {@code spring.datasource.url} 注入
*/
@Value("${spring.datasource.url}")
private String connectString;
/**
* 启动 H2 数据库 TCP 服务器并生成远程连接 URL
* 该方法通过 {@code CommandLineRunner} 接口在应用启动后执行
*
* @return {@code CommandLineRunner} 实例用于启动 H2 数据库服务器
*/
@Bean
public CommandLineRunner startH2DatabaseServer() {
return args -> {
if (commonConfigure != null) {
// 启动 H2 数据库 TCP 服务器
Server.createTcpServer("-tcp", "-tcpPort", String.valueOf(tcpPort), "-tcpAllowOthers").start();
// 生成远程连接 URL
String remoteBase = "jdbc:h2:tcp://" + CommonConfigure.LOCAL_IP_ADDR + ":" + tcpPort;
String connStr = connectString.replace("jdbc:h2:file:", remoteBase);
// 记录远程连接 URL 到日志
log.info("H2 Database server remote connect url [{}]", connStr);
}
};
}
}

View File

@ -33,8 +33,8 @@ import java.util.concurrent.TimeoutException;
*/
@Slf4j
public class ExecutingCommand {
final static int BASH_SPLIT_LEN = 3;
static Map<String, COMMAND_TYPE> osInternalCommand = new HashMap<>(1024);
final static int BASH_SPLIT_LEN = 3;
final static Map<String, COMMAND_TYPE> OS_INTERNAL_COMMAN = new HashMap<>(1024);
/**
* 私有化构造方法以防止实例化
@ -79,7 +79,7 @@ public class ExecutingCommand {
for (String line : lines) {
String[] value = line.replaceAll("\\s+", " ").trim().split(" ");
if (value.length == 2) {
osInternalCommand.put(value[1].toUpperCase(), COMMAND_TYPE.TYPE_BASH_LINUX);
OS_INTERNAL_COMMAN.put(value[1].toUpperCase(), COMMAND_TYPE.TYPE_BASH_LINUX);
log.debug("Bash Command: {} --> {}", value[1], value[0]);
}
}
@ -97,12 +97,12 @@ public class ExecutingCommand {
*/
private static COMMAND_TYPE getCommandTypeOfLinux(String cmdName) {
if (osInternalCommand.isEmpty()) {
if (OS_INTERNAL_COMMAN.isEmpty()) {
getAllInternalOfLinux();
}
if (osInternalCommand.containsKey(cmdName.toUpperCase())) {
return osInternalCommand.get(cmdName.toUpperCase());
if (OS_INTERNAL_COMMAN.containsKey(cmdName.toUpperCase())) {
return OS_INTERNAL_COMMAN.get(cmdName.toUpperCase());
}
if (extenAppIsExistsOfLinux(cmdName)) {

View File

@ -201,6 +201,7 @@ public class HelperUtils {
* @throws NoSuchFieldException 如果指定的字段不存在
* @throws IllegalAccessException 如果没有访问权限
*/
@SuppressWarnings("unchecked")
public static <T> T getField(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);

View File

@ -8,7 +8,7 @@ import com.mybatisflex.annotation.Table;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.sql.Date;
import java.time.LocalDateTime;
/**
* MiddlewareData 类表示中间件管理的数据模型
@ -68,10 +68,10 @@ public class MiddlewareData {
private String uninstall;
@Schema(description = "中间件记录的创建时间")
@Column(value = "created_time", onInsertValue = "DATETIME('now', 'localtime')")
private Date createdTime;
@Column(value = "created_time", onInsertValue = "CURRENT_TIMESTAMP(6)")
private LocalDateTime createdTime;
@Schema(description = "中间件记录的更新时间")
@Column(value = "upgrade_time", onUpdateValue = "DATETIME('now', 'localtime')")
private Date upgradeTime;
@Column(value = "upgrade_time", onUpdateValue = "CURRENT_TIMESTAMP(6)")
private LocalDateTime upgradeTime;
}

View File

@ -223,8 +223,7 @@ public class MiddlewareManagerServiceImpl implements MiddlewareManagerService {
for (RemoteFileDetails file : configFiles) {
try {
String saveName = Paths.get(workDir + File.separator + file.getFileName()).toString();
// FileDownloader dl = new FileDownloader(file.getUrl(), saveName);
// dl.downloadFile();
/* Http Download Usage: new FileDownloader(file.getUrl(), saveName).downloadFile(); */
OssService service = ossFactory.getOssService();
service.download(new URL(file.getUrl()), new File(saveName));

View File

@ -113,16 +113,13 @@ public class ProtocolSecurityServiceImpl implements ProtocolSecurityService {
log.error("AES256 decode message error: {}", base64Decode);
throw new SecurityProtocolException(ErrorCode.ERR_DECRYPT_AES256);
}
} else if (Objects.equals(proReq.getCryptoType(), ProtoCryptoType.CRYPTO_DES.getValue())) {
} else {
try {
decryptContent = CryptoHelper.desDecryption(base64Decode, protocolConfigure.getCryptoKey());
} catch (Exception e) {
log.error("DES decode message error: {}", base64Decode);
throw new SecurityProtocolException(ErrorCode.ERR_DECRYPT_3DES);
}
} else {
log.error("Unknown protocol security type: {}, {}", proReq.getCryptoType(), ciphertext);
throw new SecurityProtocolException(ErrorCode.ERR_DECRYPT_UNKNOWN);
}
String decodeMsg = new String(decryptContent, StandardCharsets.UTF_8);

View File

@ -5,12 +5,36 @@ server.compression.enabled=true
server.compression.mime-types=application/json
server.compression.min-response-size=1KB
#server.port=8443
server.ssl.key-store=classpath:mydomain.jks
server.ssl.key-store-password=101010
server.ssl.key-password=101010
server.ssl.key-alias=mydomain
server.undertow.buffer-size=1024
server.undertow.max-http-post-size=10MB
# Spring Web Configuration
spring.web.resources.add-mappings=false
# Spring DataSource Configuration
spring.datasource.url=jdbc:sqlite:src/main/resources/db/agent.db
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.datasource.url=jdbc:h2:file:./src/main/resources/db/agent;MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# enable H2 console
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# server config
spring.h2.server.enable=true
spring.h2.server.port=18090
# auto init
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:sql/schema.sql
# Mybatis Configuration
#mybatis.mapper-locations=classpath:mappers/*.xml

View File

@ -6,10 +6,26 @@
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%c{0}][%M\(%L\)][%t]: %m%n</pattern>
<pattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%C{1}][%M\(%L\)]: %m%n</pattern>
</encoder>
</appender>
<appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender">
<syslogHost>localhost</syslogHost>
<facility>LOCAL0</facility>
<suffixPattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%c{0}][%M\(%L\)][%t]: %msg%n</suffixPattern>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%msg</pattern>
</layout>
</appender>
<appender name="KAFKA" class="net.logstash.logback.appender.KafkaAppender">
<topic>your-topic-name</topic>
<deliveryTimeoutMs>5000</deliveryTimeoutMs>
<producerConfig>bootstrap.servers=172.21.152.244:9092</producerConfig>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<appender name="BIZ"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/biz.log</file>
@ -29,7 +45,7 @@
</fileNamePattern>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%c{0}][%M\(%L\)][%t]: %m%n</pattern>
<pattern>[%d{yy-MM-dd HH:mm:ss:SSS}][%-5p][%c][%M\(%L\)][%t]: %m%n</pattern>
</encoder>
</appender>
@ -45,57 +61,20 @@
</encoder>
</appender>
<logger name="com.cmcc.gds.mapper" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.mybatis" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.apache.ibatis" level="${LOG_LEVEL}"
additivity="false">
<appender-ref ref="DATA"/>
</logger>
<logger name="org.mybatis.spring" level="${LOG_LEVEL}"
additivity="false">
<appender-ref ref="DATA"/>
</logger>
<logger name="org.springframework.jdbc" level="${LOG_LEVEL}"
additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.springframework.orm" level="${LOG_LEVEL}"
additivity="false">
<appender-ref ref="DATA"/>
</logger>
<logger name="com.mysql" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="java.sql" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="javax.sql" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
</logger>
<logger name="org.springframework.security" level="${LOG_LEVEL}" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="org.casbin.jcasbin" level="error" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>
</logger>
<logger name="com.ulisesbocchio.jasyptspringboot" level="error" additivity="false">
<appender-ref ref="DATA"/>
<appender-ref ref="CONSOLE"/>

Binary file not shown.

View File

@ -1,23 +1,21 @@
PRAGMA foreign_keys = false;
SET REFERENTIAL_INTEGRITY FALSE;
DROP TABLE IF EXISTS middleware_manager;
-- ----------------------------
-- Table structure for middleware_manager
-- ----------------------------
CREATE TABLE IF NOT EXISTS middleware_manager ( -- 部署命令
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, -- id
"uid" TEXT(48) NOT NULL, -- 部署命令UUID
"middleware" TEXT(256) NOT NULL, -- 操作模块
"middleware_ver" TEXT(256), -- 操作模块
"work_directory" TEXT(4096) NOT NULL, -- 操作类型
"deployment" TEXT, -- 部署命令
"config" TEXT, -- 配置命令
"start" TEXT, -- 启动命令
"stop" TEXT, -- 停止命令
"restart" TEXT, -- 重启命令
"uninstall" TEXT, -- 卸载命令
"created_time" DATE DEFAULT (DATETIME('now', 'localtime')), -- 创建时间
"upgrade_time" DATE DEFAULT (DATETIME('now', 'localtime')) -- 更新时间
CREATE TABLE IF NOT EXISTS middleware_manager (
"id" BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'id',
"uid" VARCHAR(48) NOT NULL UNIQUE COMMENT '部署命令UUID',
"middleware" VARCHAR(256) NOT NULL COMMENT '中间件名称',
"middleware_ver" VARCHAR(256) COMMENT '中间件版本',
"work_directory" VARCHAR(4096) NOT NULL COMMENT '部署工作目录',
"deployment" VARCHAR COMMENT '部署命令',
"config" VARCHAR COMMENT '配置命令',
"start" VARCHAR COMMENT '启动命令',
"stop" VARCHAR COMMENT '停止命令',
"restart" VARCHAR COMMENT '重启命令',
"uninstall" VARCHAR COMMENT '卸载命令',
"created_time" DATE DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
"upgrade_time" DATE DEFAULT CURRENT_TIMESTAMP(6) COMMENT '更新时间'
);
PRAGMA foreign_keys = true;
SET REFERENTIAL_INTEGRITY TRUE;

View File

@ -14,6 +14,8 @@ import com.cmcc.magent.crypto.RequestProtocolSecurityTest;
import com.cmcc.magent.crypto.arithmetic.CryptoHelperTest;
import com.cmcc.magent.exception.CustomExceptionTest;
import com.cmcc.magent.interceptor.RequestBodyCacheWrapperTest;
import com.cmcc.magent.interceptor.RequestBodyFilterTest;
import com.cmcc.magent.interceptor.RestfulLogAspectTest;
import com.cmcc.magent.mapper.MiddlewareDataMapperTest;
import com.cmcc.magent.misc.ApiContextUtilsTest;
import com.cmcc.magent.misc.CircularQueueSerializerTest;
@ -30,6 +32,7 @@ import com.cmcc.magent.misc.SpringBootBeanUtilsTest;
import com.cmcc.magent.pojo.mapper.IObjectConvertImplTest;
import com.cmcc.magent.pojo.vo.ProtocolRespTest;
import com.cmcc.magent.service.impl.MiddlewareManagerServiceImplTest;
import com.cmcc.magent.service.impl.OperationLogServiceImplTest;
import com.cmcc.magent.service.impl.PlatformApiServiceImplTest;
import com.cmcc.magent.service.impl.ProtocolSecurityServiceImplTest;
import com.cmcc.magent.service.impl.ResourceUsageServiceImplTest;
@ -52,7 +55,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
* <p>
* 测试类包括
* <ul>
* <li>{@link CommonFrameworkApiTest}</li>
* <li>{@link CommonFrameworkApiTest}, {@link OperationLogServiceImplTest}</li>
* <li>{@link EnumCoverTest}</li>
* <li>{@link HelperUtilsTest}</li>
* <li>{@link PlatformApiServiceImplTest}</li>
@ -77,8 +80,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
* <li>{@link ProtocolConfigureTest}</li>
* <li>{@link ProtocolRespTest}</li>
* <li>{@link ApiContextUtilsTest}</li>
* <li>{@link HttpUtilsTest}</li>
* <li>{@link JsonUtilsTest}</li>
* <li>{@link HttpUtilsTest}, {@link RequestBodyFilterTest}</li>
* <li>{@link JsonUtilsTest}, {@link RestfulLogAspectTest}</li>
* <li>{@link MessageUtilTest}, {@link MiddlewareManagerApiTest}</li>
* <li>{@link IObjectConvertImplTest}, {@link MiddlewareManagerServiceImplTest}</li>
* <li>{@link ExecutingCommandTest}, {@link AutoExternStringJsonProcessTest}</li>
@ -101,7 +104,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
MessageUtilTest.class, IObjectConvertImplTest.class, ExecutingCommandTest.class, HttpClientUtilsTest.class,
FileDownloaderTest.class, CustomLifecycleTest.class, MiddlewareDataMapperTest.class, MiddlewareAgentApplicationTest.class,
ValidFixValuesImplTest.class, AutoExternStringJsonProcessTest.class, MiddlewareManagerServiceImplTest.class,
MiddlewareManagerApiTest.class})
MiddlewareManagerApiTest.class, RestfulLogAspectTest.class, RequestBodyFilterTest.class, OperationLogServiceImplTest.class})
@DisplayName("接口集成测试")
@Suite
@EnableScheduling

View File

@ -109,9 +109,6 @@ public class EnumCoverTest {
@DisplayName("遍历枚举")
void testEnumCover() {
for (ErrorCode e : ErrorCode.values()) {
// System.out.println(e.getStringValue() + "|" + e.getValue() + "|" + e.getDescription());
// System.out.println(e.getValue());
// System.out.println(e.getDescription());
assertThat(e).isNotNull();
}
}

View File

@ -42,6 +42,7 @@ import static org.mockito.Mockito.when;
* @since 2025-01-20
*/
@DisplayName("RequestProtocolSecurity 测试类")
@SuppressWarnings("unchecked")
public class RequestProtocolSecurityTest {
@InjectMocks
@ -190,16 +191,16 @@ public class RequestProtocolSecurityTest {
*/
@Test
@DisplayName("afterBodyRead 应返回相同对象")
@SuppressWarnings("unchecked")
public void afterBodyRead_ShouldReturnSameObject() {
// Arrange
Object decryptedObject = new Object();
HttpInputMessage httpInputMessage = mock(HttpInputMessage.class);
MethodParameter methodParameter = mock(MethodParameter.class);
Type type = mock(Type.class);
Class<? extends HttpMessageConverter<?>> converterType = aClass;
// Act
Object result = requestProtocolSecurity.afterBodyRead(decryptedObject, httpInputMessage, methodParameter, type, converterType);
Object result = requestProtocolSecurity.afterBodyRead(decryptedObject, httpInputMessage, methodParameter, type, aClass);
// Assert
assertEquals(decryptedObject, result, "The method should return the same object that was passed in.");

View File

@ -3,22 +3,32 @@ package com.cmcc.magent.interceptor;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import sun.misc.Unsafe;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@ -45,9 +55,15 @@ import static org.mockito.Mockito.when;
@DisplayName("HTTP请求数据处理")
public class RequestBodyCacheWrapperTest {
private RequestBodyCacheWrapper wrapper;
private RequestBodyCacheWrapper requestWrapper;
private MockHttpServletRequest mockRequest;
@InjectMocks
private RequestBodyCacheWrapper requestBodyCacheWrapper;
@Mock
private HttpServletRequest httpRequest;
private Logger logger;
private ServletInputStream servletInputStream;
/**
* 初始化方法在每个测试方法执行之前运行
@ -58,9 +74,29 @@ public class RequestBodyCacheWrapperTest {
* @throws IOException 如果发生 I/O 异常
*/
@BeforeEach
public void setUp() throws IOException {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
ServletInputStream servletInputStream = new ServletInputStream() {
public void setUp() throws Exception {
MockitoAnnotations.openMocks(this);
logger = mock(Logger.class);
// 获取 MyService Class 对象
Class<?> clazz = RequestBodyCacheWrapper.class;
// 获取 log 字段
Field logField = clazz.getDeclaredField("log");
// 设置字段可访问
logField.setAccessible(true);
// 获取 Unsafe 实例
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
// 获取 log 字段的偏移量
long offset = unsafe.staticFieldOffset(logField);
// 获取 log 字段所属的类对象
Object base = unsafe.staticFieldBase(logField);
// 使用 Unsafe 修改 log 字段的值
unsafe.putObject(base, offset, logger);
servletInputStream = new ServletInputStream() {
private final InputStream inputStream = new ByteArrayInputStream("testBody".getBytes(StandardCharsets.UTF_8));
@Override
@ -88,24 +124,18 @@ public class RequestBodyCacheWrapperTest {
return inputStream.read();
}
};
when(request.getInputStream()).thenReturn(servletInputStream);
wrapper = new RequestBodyCacheWrapper(request);
mockRequest = new MockHttpServletRequest();
requestWrapper = new RequestBodyCacheWrapper(mockRequest);
when(httpRequest.getInputStream()).thenReturn(servletInputStream);
}
@AfterEach
void tearDown() throws IOException {
servletInputStream.close();
}
/**
* 测试场景验证从输入流中读取缓存的请求体是否正确
*
* <p>预期结果返回的内容应与原始请求体一致</p>
*
* @throws IOException 如果发生 I/O 异常
*/
@Test
@DisplayName("验证从输入流中读取缓存的请求体是否正确")
@DisplayName("读取缓存的请求体")
public void getInputStream_ReadsCachedBody() throws IOException {
ServletInputStream servletInputStream = wrapper.getInputStream();
ServletInputStream servletInputStream = requestBodyCacheWrapper.getInputStream();
byte[] buffer = new byte[1024];
int bytesRead = servletInputStream.read(buffer);
// 确保 bytesRead 不为负数
@ -117,55 +147,118 @@ public class RequestBodyCacheWrapperTest {
}
}
/**
* 测试场景验证输入流的 {@code isFinished()} 方法是否始终返回 false
*
* <p>预期结果方法返回 false</p>
*/
@Test
@DisplayName("验证输入流的 isFinished() 方法是否始终返回 false")
@DisplayName("isFinished()方法返回false")
public void isFinished_AlwaysReturnsFalse() {
ServletInputStream servletInputStream = wrapper.getInputStream();
ServletInputStream servletInputStream = requestBodyCacheWrapper.getInputStream();
assertFalse(servletInputStream.isFinished());
}
/**
* 测试场景验证输入流的 {@code isReady()} 方法是否始终返回 false
*
* <p>预期结果方法返回 false</p>
*/
@Test
@DisplayName("验证输入流的 isReady() 方法是否始终返回 false")
@DisplayName("isReady()方法返回false")
public void isReady_AlwaysReturnsFalse() {
ServletInputStream servletInputStream = wrapper.getInputStream();
ServletInputStream servletInputStream = requestBodyCacheWrapper.getInputStream();
assertFalse(servletInputStream.isReady());
}
/**
* 测试场景验证设置读取监听器时是否正确执行
*
* <p>预期结果调用 {@code setReadListener()} 方法时不抛出异常</p>
*/
@Test
@DisplayName("验证设置读取监听器时是否正确执行")
@DisplayName("设置读取监听器")
public void setReadListener_DoesNothing() {
ServletInputStream servletInputStream = wrapper.getInputStream();
ServletInputStream servletInputStream = requestBodyCacheWrapper.getInputStream();
servletInputStream.setReadListener(null); // 不应抛出异常
}
/**
* 测试场景验证使用 {@code getReader()} 方法获取请求体内容是否正确
*
* <p>预期结果返回的 {@link BufferedReader} 对象不为空</p>
*
*/
@Test
@DisplayName("验证使用 getReader() 方法获取请求体内容是否正确")
public void getReader_ShouldReturnCorrectBody() {
String requestBody = "{\"key\":\"value\"}";
@DisplayName("使用getReader()获取请求体内容")
public void getReader_ShouldReturnCorrectBody() throws IOException {
String requestBody = "{\"key\":\"value\"}";
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
RequestBodyCacheWrapper requestWrapper = new RequestBodyCacheWrapper(mockRequest);
mockRequest.setContent(requestBody.getBytes(StandardCharsets.UTF_8));
BufferedReader reader = requestWrapper.getReader();
assertNotNull(reader);
}
@Test
@DisplayName("空输入流时读取器行为")
public void getReader_EmptyInputStream_ReturnsEmptyString() throws IOException {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
ServletInputStream servletInputStream = new ServletInputStream() {
private final InputStream inputStream = new ByteArrayInputStream(new byte[0]);
@Override
public boolean isFinished() {
try {
return inputStream.available() == 0;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
// 不执行任何操作
}
@Override
public int read() throws IOException {
return inputStream.read();
}
};
when(request.getInputStream()).thenReturn(servletInputStream);
RequestBodyCacheWrapper emptyWrapper = new RequestBodyCacheWrapper(request);
BufferedReader reader = emptyWrapper.getReader();
StringBuilder stringBuilder = new StringBuilder();
char[] charBuffer = new char[128];
int bytesRead;
while ((bytesRead = reader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
assertEquals("", stringBuilder.toString());
}
@Test
@DisplayName("构造函数读写异常")
public void requestBodyCacheWrapper_ReadException() throws IOException {
when(httpRequest.getInputStream()).thenThrow(new IOException("null"));
// 验证 log.info 是否被调用并检查输出内容
new RequestBodyCacheWrapper(httpRequest);
verify(logger).error(eq("RequestWrapper read error :{}"), eq("null"));
}
@Test
@DisplayName("获取请求体JSON内容")
public void getReader_Json_ShouldReturnCorrectBody() throws IOException {
String requestBody = "{\"key\":\"value\"}";
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.setContent(requestBody.getBytes(StandardCharsets.UTF_8));
// 添加单个请求头
mockRequest.addHeader("Authorization", "Bearer token123");
// 设置请求头覆盖已存在的值
mockRequest.addHeader("Content-Type", "application/json");
// 添加多个值的请求头
mockRequest.addHeader("Accept", "application/json");
when(httpRequest.getInputStream()).thenReturn(mockRequest.getInputStream());
RequestBodyCacheWrapper requestWrapper = new RequestBodyCacheWrapper(mockRequest);
BufferedReader reader = requestWrapper.getReader();
assertNotNull(reader);
assertNotNull(requestWrapper.getHeaders());
assertEquals(3, requestWrapper.getHeaders().size());
assertEquals("Bearer token123", requestWrapper.getHeaders().get("Authorization"));
assertEquals("application/json", requestWrapper.getHeaders().get("Content-Type"));
assertEquals("application/json", requestWrapper.getHeaders().get("Accept"));
}
}

View File

@ -0,0 +1,164 @@
package com.cmcc.magent.interceptor;
import com.cmcc.magent.config.ObjectMapperProvider;
import com.cmcc.magent.misc.MessageUtil;
import com.cmcc.magent.misc.ProtocolJsonUtils;
import com.cmcc.magent.pojo.vo.DeploymentMiddlewareResp;
import com.cmcc.magent.pojo.vo.ProtocolResp;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
import org.springframework.mock.web.MockHttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* RequestBodyFilter 测试类用于测试 RequestBodyFilter 过滤器的功能
* 该类通过模拟 HTTP 请求过滤器链请求和响应对象验证过滤器在不同场景下的行为
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-02-26
*/
@DisplayName("协议请求JSON拦截")
public class RequestBodyFilterTest {
@InjectMocks
private RequestBodyFilter requestBodyFilter;
@Mock
private FilterConfig filterConfig;
@Mock
private FilterChain filterChain;
@Mock
private ServletRequest servletRequest;
@Mock
private ServletResponse servletResponse;
private MockedStatic<ProtocolJsonUtils> mockProtocolJsonUtils;
private MockedStatic<ObjectMapperProvider> mockObjectMapperProvider;
private MockedStatic<MessageUtil> mockMessageUtil;
/**
* 在每个测试方法执行前初始化测试环境
* 包括初始化 Mock 对象模拟静态方法等
*/
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
mockMessageUtil = mockStatic(MessageUtil.class);
mockObjectMapperProvider = mockStatic(ObjectMapperProvider.class);
mockProtocolJsonUtils = mockStatic(ProtocolJsonUtils.class);
}
/**
* 在每个测试方法执行后清理测试环境
* 包括关闭模拟的静态方法
*/
@AfterEach
public void tearDown() {
mockProtocolJsonUtils.close();
mockObjectMapperProvider.close();
mockMessageUtil.close();
}
/**
* 测试过滤器初始化方法是否不会抛出异常
* 验证在调用 `init()` 方法时不会抛出任何异常
*/
@Test
@DisplayName("初始化不抛异常")
public void init_ShouldNotThrowException() {
assertDoesNotThrow(() -> requestBodyFilter.init(filterConfig));
}
/**
* 测试当请求是 HttpServletRequest 过滤器是否对请求进行包装
* 验证在请求是 HttpServletRequest 的情况下过滤器会包装请求并调用过滤器链
*
* @throws Exception 如果测试过程中发生异常
*/
@Test
@DisplayName("HttpServletRequest时包装请求")
@SuppressWarnings("unchecked")
public void doFilter_RequestIsHttpServletRequest_WrapsRequest() throws Exception {
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
DeploymentMiddlewareResp resp = DeploymentMiddlewareResp.builder().deploymentId("3dd6175c-ca47-4640-83e0-5231c037c5ac").build();
resp.setStatus(0);
resp.setMessage(new String[]{"success"});
ProtocolResp<DeploymentMiddlewareResp> protocolRespB = new ProtocolResp<>();
protocolRespB.setVer(1);
protocolRespB.setCryptoType(0);
protocolRespB.setTimeStamp(1740564578790L);
protocolRespB.setCode(200);
protocolRespB.setMsgContent(resp);
String requestBody = """
{"ver":1,"cryptoType":0,"timeStamp":1740564578790,"code":200,"msgContent":\
{"deploymentId":"3dd6175c-ca47-4640-83e0-5231c037c5ac","status":0,"message":["success"]}}
""";
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.setContent(requestBody.getBytes(StandardCharsets.UTF_8));
ServletInputStream servletInputStream = new ServletInputStream() {
private final InputStream inputStream = new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8));
@Override
public boolean isFinished() {
try {
return inputStream.available() == 0;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
// 不执行任何操作
}
@Override
public int read() throws IOException {
return inputStream.read();
}
};
when(httpServletRequest.getInputStream()).thenReturn(servletInputStream);
mockProtocolJsonUtils.when(() -> ProtocolJsonUtils.jsonGetObject(anyString(), any(Class.class))).thenReturn(protocolRespB);
mockMessageUtil.when(() -> MessageUtil.get(anyString(), anyString())).thenReturn("");
requestBodyFilter.doFilter(httpServletRequest, servletResponse, filterChain);
verify(filterChain).doFilter(any(RequestBodyCacheWrapper.class), eq(servletResponse));
}
/**
* 测试当请求不是 HttpServletRequest 过滤器是否直接传递原始请求
* 验证在请求不是 HttpServletRequest 的情况下过滤器不会包装请求而是直接传递原始请求
*
* @throws IOException 如果测试过程中发生 IO 异常
* @throws ServletException 如果测试过程中发生 Servlet 异常
*/
@Test
@DisplayName("非HttpServletRequest时传递原始请求")
public void doFilter_RequestIsNotHttpServletRequest_PassesOriginalRequest() throws IOException, ServletException {
servletRequest = mock(ServletRequest.class);
requestBodyFilter.doFilter(servletRequest, servletResponse, filterChain);
verify(filterChain).doFilter(eq(servletRequest), eq(servletResponse));
}
}

View File

@ -0,0 +1,160 @@
package com.cmcc.magent.interceptor;
import com.cmcc.magent.common.ErrorCode;
import com.cmcc.magent.misc.MessageUtil;
import com.cmcc.magent.pojo.base.BaseProtocol;
import com.cmcc.magent.pojo.po.BaseRespStatus;
import com.cmcc.magent.pojo.vo.ProtocolResp;
import com.cmcc.magent.service.OperationLogService;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* RestfulLogAspect 测试类用于测试 RestfulLogAspect 切面的功能
* 该类通过模拟 HTTP 请求请求上下文日志服务等验证切面在不同场景下的行为
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-02-26
*/
@DisplayName("API接口系统日志注入")
public class RestfulLogAspectTest {
private MockMvc mockMvc;
@Mock
private OperationLogService optLogDbService;
@InjectMocks
private RestfulLogAspect restfulLogAspect;
@Mock
private JoinPoint joinPoint;
@Mock
private MethodSignature methodSignature;
@Mock
private HttpServletRequest request;
@Mock
private RequestAttributes requestAttributes;
private MockedStatic<MessageUtil> mockMessageUtil;
private MockedStatic<RequestContextHolder> mockRequestContextHolder;
/**
* 在每个测试方法执行前初始化测试环境
* 包括初始化 Mock 对象模拟静态方法等
*/
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
mockMessageUtil = mockStatic(MessageUtil.class);
mockRequestContextHolder = mockStatic(RequestContextHolder.class);
mockMvc = MockMvcBuilders.standaloneSetup(new Object()).build();
when(requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST)).thenReturn(request);
when(joinPoint.getSignature()).thenReturn(methodSignature);
}
/**
* 在每个测试方法执行后清理测试环境
* 包括关闭模拟的静态方法
*/
@AfterEach
public void tearDown() {
mockMessageUtil.close();
mockRequestContextHolder.close();
}
/**
* 测试 RestfulLogAspect 切面是否能够拦截控制器方法
* 验证切面在拦截到控制器方法后是否调用了日志服务
*
* @throws Exception 如果测试过程中发生异常
*/
@Test
@DisplayName("拦截控制器方法")
public void restfulLog_AspectInterceptsControllerMethod() throws Exception {
doNothing().when(optLogDbService).systemOperationLog(any(), any(), any(), any(), any(), any(), any());
restfulLogAspect.restfulLog();
mockMvc.perform(get("/test")).andExpect(status().isNotFound());
}
/**
* 测试当请求上下文为空时是否不会记录日志
* 验证在请求上下文为空的情况下日志服务未被调用
*/
@Test
@DisplayName("请求上下文为空时不记录日志")
public void saveOperationLog_RequestAttributesIsNull_NoLogging() {
mockRequestContextHolder.when(RequestContextHolder::getRequestAttributes).thenReturn(null);
restfulLogAspect.saveOperationLog(joinPoint, new Object());
verify(optLogDbService, never()).systemOperationLog(anyString(), any(HttpServletRequest.class), any(), any(), any());
}
/**
* 测试当 HTTP 请求对象为空时是否不会记录日志
* 验证在 HTTP 请求对象为空的情况下日志服务未被调用
*/
@Test
@DisplayName("HTTP请求对象为空时不记录日志")
public void saveOperationLog_HttpServletRequestIsNull_NoLogging() {
mockRequestContextHolder.when(RequestContextHolder::getRequestAttributes).thenReturn(requestAttributes);
when(requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST)).thenReturn(null);
restfulLogAspect.saveOperationLog(joinPoint, new Object());
verify(optLogDbService, never()).systemOperationLog(anyString(), any(HttpServletRequest.class), any(), any(), any());
}
/**
* 测试在正常请求和响应的情况下是否能够成功记录日志
* 验证日志服务在正常场景下被正确调用
*/
@Test
@DisplayName("正常请求和响应时记录日志")
public void saveOperationLog_Success() {
mockRequestContextHolder.when(RequestContextHolder::getRequestAttributes).thenReturn(requestAttributes);
when(requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST)).thenReturn(request);
mockMessageUtil.when(() -> MessageUtil.get(anyString(), anyString())).thenReturn("Test");
when(methodSignature.getMethod()).thenReturn(new Object() {
}.getClass().getEnclosingMethod());
ProtocolResp<BaseRespStatus> result = new ProtocolResp<>();
result.setMsgContent(new BaseRespStatus(0, new String[] {"Test"}));
restfulLogAspect.saveOperationLog(joinPoint, result);
verify(optLogDbService).systemOperationLog(anyString(), eq(request), eq(result), isNull(), eq(ErrorCode.ERR_OK));
}
/**
* 测试当响应对象类型不合法时是否记录系统异常日志
* 验证在响应对象类型不合法的情况下日志服务被调用并记录系统异常
*/
@Test
@DisplayName("响应对象类型不合法时记录系统异常日志")
public void saveOperationLog_UnValidProtocolResp_TypeError() {
BaseProtocol<String> baseProtocol = new BaseProtocol<>();
mockRequestContextHolder.when(RequestContextHolder::getRequestAttributes).thenReturn(requestAttributes);
when(requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST)).thenReturn(request);
mockMessageUtil.when(() -> MessageUtil.get(anyString(), anyString())).thenReturn("Test");
when(methodSignature.getMethod()).thenReturn(new Object() {
}.getClass().getEnclosingMethod());
restfulLogAspect.saveOperationLog(joinPoint, baseProtocol);
verify(optLogDbService).systemOperationLog(anyString(), eq(request), eq(baseProtocol), isNull(), eq(ErrorCode.ERR_SYSTEMEXCEPTION));
}
}

View File

@ -15,7 +15,6 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -50,7 +49,7 @@ public class ExecutingCommandTest {
private static final String[][] TEST_CMD_ARRAY = {{"ping", "-c", "1", "www.baidu.com"}, {"echo", "www.baidu.com"}, {"pwd"}};
@BeforeEach
public void setUp() throws IOException {
public void setUp() {
MockitoAnnotations.openMocks(this);
helperUtils = mockStatic(HelperUtils.class, Mockito.CALLS_REAL_METHODS);
}

View File

@ -232,10 +232,8 @@ public class HelperUtilsTest {
// 断言结果为 false
assertFalse(result);
// null 字符串
str = null;
// 断言结果为 false
assertFalse(HelperUtils.stringNotEmptyOrNull(str));
assertFalse(HelperUtils.stringNotEmptyOrNull(null));
}
/**

View File

@ -28,6 +28,9 @@ import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@ -48,6 +51,7 @@ import static org.mockito.Mockito.when;
*/
@SpringBootTest
@DisplayName("HttpClient 测试类")
@SuppressWarnings("unchecked")
public class HttpClientUtilsTest {
private static final String SUCCESS_URL = "https://example.com/success";
@ -62,6 +66,7 @@ public class HttpClientUtilsTest {
private MockedStatic<HttpClientUtils> platformMock;
private MockedStatic<HttpClients> httpClientsMock;
private MockedStatic<HttpAsyncClients> httpAsyncClientsMock;
private MockedStatic<EntityUtils> entityUtilsMock;
// 创建 Mock HttpClient
private CloseableHttpClient mockHttpClient;
@ -73,6 +78,7 @@ public class HttpClientUtilsTest {
private FutureCallback<SimpleHttpResponse> mockCallback;
/**
* 在每个测试前进行设置
* 创建 HttpClient 和响应的 Mock 对象并初始化 Mockito
@ -85,6 +91,7 @@ public class HttpClientUtilsTest {
platformMock = mockStatic(HttpClientUtils.class, Mockito.CALLS_REAL_METHODS);
httpClientsMock = mockStatic(HttpClients.class, Mockito.CALLS_REAL_METHODS);
httpAsyncClientsMock = mockStatic(HttpAsyncClients.class, Mockito.CALLS_REAL_METHODS);
entityUtilsMock = mockStatic(EntityUtils.class);
mockHttpClient = Mockito.mock(CloseableHttpClient.class);
mockResponse = Mockito.mock(ClassicHttpResponse.class);
@ -112,10 +119,10 @@ public class HttpClientUtilsTest {
*/
@AfterEach
public void tearDown() {
httpClientsMock.close();
platformMock.close();
httpAsyncClientsMock.close();
entityUtilsMock.close();
}
/**
@ -140,7 +147,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("GET 成功")
public void get_Success() throws IOException, ParseException {
public void get_Success() throws IOException {
when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenReturn(SUCCESS_RESPONSE);
// 调用 get 方法并验证结果
String response = HttpClientUtils.get(SUCCESS_URL, null, null);
@ -153,9 +160,7 @@ public class HttpClientUtilsTest {
Mockito.when(mockResponse.getCode()).thenReturn(404);
mockResponse.setEntity(new StringEntity(FAILURE_JSON_RESPONSE, StandardCharsets.UTF_8));
// 模拟对于不同的响应处理器返回不同的值
Mockito.when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenAnswer(invocation -> {
return handleResponse(mockResponse);
});
Mockito.when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenAnswer(invocation -> handleResponse(mockResponse));
// 调用 get 方法并验证结果
assertThrows(CommonErrorCodeException.class, () -> HttpClientUtils.get(FAILURE_URL, null, null));
}
@ -172,7 +177,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("GET 带头部成功")
public void get_WithHead_Success() throws IOException, ParseException {
public void get_WithHead_Success() throws IOException {
Map<String, String> headers = new HashMap<>();
headers.put("Custom-Header", "Value");
@ -183,7 +188,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("GET 带超时成功")
public void get_WithTiemout_Success() throws IOException, ParseException {
public void get_WithTiemout_Success() throws IOException {
when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenReturn(SUCCESS_RESPONSE);
String response = HttpClientUtils.get(SUCCESS_URL, null, 10);
assertEquals(SUCCESS_RESPONSE, response);
@ -191,7 +196,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("GET 超时为零或负数成功")
public void get_WithTiemoutLessOrEqueleZero_Success() throws IOException, ParseException {
public void get_WithTiemoutLessOrEqueleZero_Success() throws IOException {
when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenReturn(SUCCESS_RESPONSE);
String response = HttpClientUtils.get(SUCCESS_URL, null, 0);
assertEquals(SUCCESS_RESPONSE, response);
@ -202,7 +207,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("POST 成功")
public void post_Success() throws IOException, ParseException {
public void post_Success() throws IOException {
when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenReturn(SUCCESS_RESPONSE);
String response = HttpClientUtils.post(SUCCESS_URL, null, null, null);
assertEquals(SUCCESS_RESPONSE, response);
@ -210,7 +215,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("POST 带头部成功")
public void post_WithHead_Success() throws IOException, ParseException {
public void post_WithHead_Success() throws IOException {
Map<String, String> headers = new HashMap<>();
headers.put("Custom-Header", "Value");
@ -221,7 +226,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("POST 带参数成功")
public void post_WithParams_Success() throws IOException, ParseException {
public void post_WithParams_Success() throws IOException {
Map<String, String> params = new HashMap<>();
params.put("user", "user");
params.put("pwd", "pwd");
@ -233,7 +238,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("POST 带空参数成功")
public void post_WithEmptyParams_Success() throws IOException, ParseException {
public void post_WithEmptyParams_Success() throws IOException {
Map<String, String> params = new HashMap<>(1);
when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenReturn(SUCCESS_RESPONSE);
@ -243,7 +248,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("POST 带超时成功")
public void post_WithTiemout_Success() throws IOException, ParseException {
public void post_WithTiemout_Success() throws IOException {
when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenReturn(SUCCESS_RESPONSE);
String response = HttpClientUtils.post(SUCCESS_URL, null, null, 10);
assertEquals(SUCCESS_RESPONSE, response);
@ -251,7 +256,7 @@ public class HttpClientUtilsTest {
@Test
@DisplayName("POST 超时为零或负数成功")
public void post_WithTiemoutLessOrEqueleZero_Success() throws IOException, ParseException {
public void post_WithTiemoutLessOrEqueleZero_Success() throws IOException {
when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenReturn(SUCCESS_RESPONSE);
String response = HttpClientUtils.post(SUCCESS_URL, null, null, 0);
assertEquals(SUCCESS_RESPONSE, response);
@ -651,4 +656,70 @@ public class HttpClientUtilsTest {
HttpClientUtils.asyncPostForm(FAILURE_URL, null, form, null, mockCallback);
mockCallback.failed(exception);
}
@Test
@DisplayName("私有handleResponse调用空Entity")
public void get_WithHead_Handle_entityNull() throws IOException {
Mockito.when(mockResponse.getCode()).thenReturn(404);
mockResponse.setEntity(new StringEntity(FAILURE_JSON_RESPONSE, StandardCharsets.UTF_8));
// 模拟对于不同的响应处理器返回不同的值
Mockito.when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenAnswer(invocation -> {
// 获取私有方法 handleResponse
Class<?> clazz = HttpClientUtils.class;
// private static String handleResponse(ClassicHttpResponse response)
Method method = clazz.getDeclaredMethod("handleResponse", ClassicHttpResponse.class);
// 设置方法为可访问
method.setAccessible(true);
// 调用私有方法
return method.invoke(null, mockResponse);
});
// 调用 get 方法并验证结果
assertThrows(InvocationTargetException.class, () -> HttpClientUtils.get(FAILURE_URL, null, null));
}
@Test
@DisplayName("私有handleResponse返回值200")
public void get_WithHead_Handle_isOK() throws IOException {
when(mockResponse.getCode()).thenReturn(200);
when(mockResponse.getEntity()).thenReturn(new StringEntity(FAILURE_JSON_RESPONSE, StandardCharsets.UTF_8));
// mockResponse.setEntity(new StringEntity(FAILURE_JSON_RESPONSE, StandardCharsets.UTF_8));
entityUtilsMock.when(() -> EntityUtils.toString(any(HttpEntity.class), any(Charset.class))).thenReturn("message");
// 模拟对于不同的响应处理器返回不同的值
Mockito.when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenAnswer(invocation -> {
// 获取私有方法 handleResponse
Class<?> clazz = HttpClientUtils.class;
// private static String handleResponse(ClassicHttpResponse response)
Method method = clazz.getDeclaredMethod("handleResponse", ClassicHttpResponse.class);
// 设置方法为可访问
method.setAccessible(true);
// 调用私有方法
return method.invoke(null, mockResponse);
});
// 调用 get 方法并验证结果
assertEquals("message", HttpClientUtils.get(FAILURE_URL, null, null));
}
@Test
@DisplayName("私有handleResponse调用非空Entity")
public void get_WithHead_Handle_entityNotNull() throws IOException {
Mockito.when(mockResponse.getCode()).thenReturn(404);
when(mockResponse.getEntity()).thenReturn(new StringEntity(FAILURE_JSON_RESPONSE, StandardCharsets.UTF_8));
entityUtilsMock.when(() -> EntityUtils.toString(any(HttpEntity.class), any(Charset.class))).thenReturn("message");
// 模拟对于不同的响应处理器返回不同的值
Mockito.when(mockHttpClient.execute(any(ClassicHttpRequest.class), any(HttpClientResponseHandler.class))).thenAnswer(invocation -> {
// 获取私有方法 handleResponse
Class<?> clazz = HttpClientUtils.class;
// private static String handleResponse(ClassicHttpResponse response)
Method method = clazz.getDeclaredMethod("handleResponse", ClassicHttpResponse.class);
// 设置方法为可访问
method.setAccessible(true);
// 调用私有方法
return method.invoke(null, mockResponse);
});
// 调用 get 方法并验证结果
assertThrows(InvocationTargetException.class, () -> HttpClientUtils.get(FAILURE_URL, null, null));
}
}

View File

@ -1,6 +1,7 @@
package com.cmcc.magent.misc;
import org.junit.jupiter.api.BeforeEach;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
@ -9,16 +10,14 @@ import org.mockito.MockitoAnnotations;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import java.net.UnknownHostException;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* <p>
* 测试 {@link HttpUtils} 工具类的功能
@ -44,7 +43,7 @@ public class HttpUtilsTest {
assertNotNull(instance);
});
}
/*
@Test
public void testGetHttpRequestHeaders_nullrequest() throws Exception {
String result = HttpUtils.getHttpRequestHeaders(null);
@ -56,5 +55,21 @@ public class HttpUtilsTest {
public void testGetHttpRequestSrcIp_nullrequest() throws Exception {
String result = HttpUtils.getHttpRequestSrcIp(null);
assertEquals("unknown", result);
*/
@Test
@DisplayName("参数null测试")
public void getHttpRequestHeaders_inputIsNull_returnObject() throws JsonProcessingException, UnknownHostException {
assertEquals("{}", HttpUtils.getHttpRequestHeaders(null));
assertEquals("unknown", HttpUtils.getHttpRequestSrcIp(null));
}
@Test
@DisplayName("输入多个IP")
public void getHttpRequestSrcIp_returnMutileIpSplit() throws UnknownHostException {
String testIp = "172.28.22.21, 172.28.22.22";
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader("x-forwarded-for")).thenReturn(testIp);
assertEquals("172.28.22.21", HttpUtils.getHttpRequestSrcIp(request));
}
}

View File

@ -1,13 +1,25 @@
package com.cmcc.magent.misc;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.context.MessageSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* <p>
@ -22,8 +34,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
* @since 2025-02-07
*/
@DisplayName("国际化消息工具测试")
@Slf4j
public class MessageUtilTest {
private MockedStatic<SpringBootBeanUtils> mockSpringBootBeanUtils;
private MessageSource messageSource;
@BeforeEach
public void setUp() {
messageSource = mock(MessageSource.class);
mockSpringBootBeanUtils = mockStatic(SpringBootBeanUtils.class);
mockSpringBootBeanUtils.when(() -> SpringBootBeanUtils.getBean(MessageSource.class)).thenReturn(messageSource);
}
@AfterEach
public void tearDown() {
mockSpringBootBeanUtils.close();
}
@Test
@DisplayName("禁止类实例化")
public void testPrivateConstructor() {
@ -34,4 +62,40 @@ public class MessageUtilTest {
assertNotNull(instance);
});
}
@Test
@DisplayName("国际化消息为空")
public void get_EmptyMessage_returnKey() {
MockedStatic<HelperUtils> mockHelpUtils = mockStatic(HelperUtils.class);
log.info("message: {}", messageSource);
mockHelpUtils.when(() -> HelperUtils.stringNotEmptyOrNull(anyString())).thenReturn(false);
when(messageSource.getMessage(anyString(), any(Object[].class), any(Locale.class))).thenReturn("message");
assertEquals("key", MessageUtil.get("key", new Object[0], "zh_CN"));
mockHelpUtils.close();
}
@Test
@DisplayName("指定国际化语言")
public void get_Message_returnMessage() {
log.info("message: {}", messageSource);
when(messageSource.getMessage(anyString(), any(Object[].class), any(Locale.class))).thenReturn("message");
assertEquals("key", MessageUtil.get("key", new Object[0], "zh_CN"));
}
@Test
@DisplayName("国际化语言为空字符串")
public void get_EmptyLanguage_returnDefaultLanguage() {
log.info("message: {}", messageSource);
when(messageSource.getMessage(anyString(), any(Object[].class), any(Locale.class))).thenReturn("message");
assertEquals("key", MessageUtil.get("key", new Object[0], ""));
}
@Test
@DisplayName("国际化消息重载方法")
public void get_returnDefaultLanguage() {
log.info("message: {}", messageSource);
when(messageSource.getMessage(anyString(), any(Object[].class), any(Locale.class))).thenReturn("message");
assertEquals("key", MessageUtil.get("key", "zh_CN"));
assertEquals("key", MessageUtil.get("key", ""));
}
}

View File

@ -67,6 +67,7 @@ public class ProtocolJsonUtilsTest {
* <p>此方法初始化需要的测试数据</p>
*/
@BeforeEach
@SuppressWarnings("unchecked")
public void setUp() {
protocolResp = new ProtocolResp();
protocolRespB = new ProtocolResp();
@ -171,6 +172,7 @@ public class ProtocolJsonUtilsTest {
*/
@Test
@DisplayName("不匹配的 JSON 应返回默认对象")
@SuppressWarnings("unchecked")
public void jsonGetObject_MismatchedJson() throws JsonProcessingException {
String mismatchedJson = "{\"protocolId\":12345,\"protocolName\":true,\"protocolType\":null,\"protocolVersion\":1.0," +
"\"protocolStatus\":\"SUCCESS\"}";

View File

@ -417,31 +417,4 @@ public class MiddlewareManagerServiceImplTest {
assertEquals(ErrorCode.ERR_OK, result.getFirstParam());
}
@Test
@DisplayName("异步下载部署脚本异常(部署脚本)")
public void executeTaskAsync_downloadConfigFiles_ThrowException() {
RemoteFileDetails remoteFileDetails = new RemoteFileDetails();
HashMap<MiddlewareManagerCommand, RemoteFileDetails> command = new HashMap<>();
List<RemoteFileDetails> configFiles = new ArrayList<>();
when(ossFactory.getOssService()).thenReturn(service);
// 设置 download 方法的行为不进行真实调用直接返回
doNothing().when(service).download(any(URL.class), any(File.class));
when(middlewareDataMapper.insert(any(MiddlewareData.class))).thenReturn(1);
when(commonConfigure.isAsyncDeployment()).thenReturn(true);
mockCryptoHelper.when(() -> CryptoHelper.md5FileEncryption(anyString())).thenThrow(new NoSuchAlgorithmException());
mockHelpUtils.when(() -> HelperUtils.stringNotEmptyOrNull(anyString())).thenReturn(true);
command.put(MiddlewareManagerCommand.COMMAND_DEPLOYMENT,
RemoteFileDetails.builder().url("http://test").fileName("").checksum("").build());
remoteFileDetails.setFileName("test");
remoteFileDetails.setUrl("http://test");
remoteFileDetails.setChecksum("test");
configFiles.add(remoteFileDetails);
MulReturnType<ErrorCode, String> result = middlewareManagerService.executeTask("Redis", "6.0", command, configFiles);
assertEquals(ErrorCode.ERR_OK, result.getFirstParam());
}
}

View File

@ -0,0 +1,148 @@
package com.cmcc.magent.service.impl;
import com.cmcc.magent.annotation.OperationLogAnnotation;
import com.cmcc.magent.common.ErrorCode;
import com.cmcc.magent.config.CommonConfigure;
import com.cmcc.magent.config.ObjectMapperProvider;
import com.cmcc.magent.misc.ApiContextUtils;
import com.cmcc.magent.misc.HelperUtils;
import com.cmcc.magent.misc.HttpUtils;
import com.cmcc.magent.misc.JsonUtils;
import com.cmcc.magent.pojo.po.ApiContext;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* OperationLogServiceImpl 测试类用于测试 OperationLogServiceImpl 日志服务的功能
* 该类通过模拟 HTTP 请求API 上下文工具类等验证日志服务在不同场景下的行为
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-02-26
*/
@DisplayName("操作日志测试")
public class OperationLogServiceImplTest {
@InjectMocks
private OperationLogServiceImpl operationLogService;
@Mock
private ServerProperties serverProperties;
private MockedStatic<HelperUtils> mockHelperUtils;
private MockedStatic<HttpUtils> mockHttpUtils;
private MockedStatic<JsonUtils> mockJsonUtils;
private MockedStatic<ObjectMapperProvider> mockObjectMapperProvider;
private MockedStatic<ApiContextUtils> mockApiContextUtils;
/**
* 在每个测试方法执行前初始化测试环境
* 包括初始化 Mock 对象模拟静态方法等
*/
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
CommonConfigure.PROJECT_PREFIX_URL = "/api";
mockHelperUtils = mockStatic(HelperUtils.class);
mockHttpUtils = mockStatic(HttpUtils.class);
mockObjectMapperProvider = mockStatic(ObjectMapperProvider.class);
mockJsonUtils = mockStatic(JsonUtils.class);
mockApiContextUtils = mockStatic(ApiContextUtils.class);
}
/**
* 在每个测试方法执行后清理测试环境
* 包括关闭模拟的静态方法
*/
@AfterEach
public void tearDown() {
mockHelperUtils.close();
mockHttpUtils.close();
mockJsonUtils.close();
mockObjectMapperProvider.close();
mockApiContextUtils.close();
}
/**
* 测试日志服务在传入完整参数时是否正常调用
* 验证在传入完整参数的情况下日志服务是否被正确调用
*/
@Test
@DisplayName("完整参数调用")
public void systemOperationLog_fullParamsCall() {
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getRequestURI()).thenReturn("/api/error/someError");
mockHelperUtils.when(() -> HelperUtils.stringNotEmptyOrNull(anyString())).thenReturn(true);
operationLogService.systemOperationLog("testCall", request, new Object(), "testModule", "testType", "testDesc", ErrorCode.ERR_OK);
}
/**
* 测试日志服务在传入简化参数带注解时是否正常调用
* 验证在传入简化参数带注解的情况下日志服务是否被正确调用
*/
@Test
@DisplayName("简单参数调用")
public void systemOperationLog_simpleParamsCall() {
HttpServletRequest request = mock(HttpServletRequest.class);
OperationLogAnnotation annotation = mock(OperationLogAnnotation.class);
when(annotation.OperationModule()).thenReturn("testModule");
when(annotation.OperationType()).thenReturn("testType");
when(annotation.OperationDesc()).thenReturn("testDesc");
when(request.getRequestURI()).thenReturn("/api/error/someError");
mockHelperUtils.when(() -> HelperUtils.stringNotEmptyOrNull(anyString())).thenReturn(true);
operationLogService.systemOperationLog("testCall", request, new Object(), annotation, ErrorCode.ERR_OK);
}
/**
* 测试日志服务在传入简化参数注解为空时是否正常调用
* 验证在传入简化参数注解为空的情况下日志服务是否被正确调用
*/
@Test
@DisplayName("简单参数调用(注解为空)")
public void systemOperationLog_simpleParamsCall_annotationNull() {
HttpServletRequest request = mock(HttpServletRequest.class);
mockHelperUtils.when(() -> HelperUtils.stringNotEmptyOrNull(anyString())).thenReturn(true);
operationLogService.systemOperationLog("testCall", request, new Object(), null, ErrorCode.ERR_OK);
}
/**
* 测试日志服务在有效请求时是否成功记录日志
* 验证在请求有效的情况下日志是否被成功记录
*
*/
@Test
@DisplayName("有效请求")
public void systemOperationLog_ValidRequest_LogsSuccessfully() {
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getRequestURI()).thenReturn("/api/somePath");
when(request.getMethod()).thenReturn("GET");
when(request.getHeaderNames()).thenReturn(new java.util.Vector<String>().elements());
when(request.getHeader(anyString())).thenReturn("someHeaderValue");
ServerProperties.Servlet servlet = mock(ServerProperties.Servlet.class);
when(serverProperties.getServlet()).thenReturn(servlet);
when(servlet.getContextPath()).thenReturn("/api");
ApiContext apiContext = new ApiContext();
apiContext.setRequestBody("{\"timeStamp\": 1609459200000}");
apiContext.setRequestTime(System.currentTimeMillis());
mockHttpUtils.when(() -> HttpUtils.getHttpRequestHeaders(any(HttpServletRequest.class))).thenReturn(
"{\"someHeader\": \"someHeaderValue\"}");
mockHttpUtils.when(() -> HttpUtils.getHttpRequestSrcIp(any(HttpServletRequest.class))).thenReturn("127.0.0.1");
mockHelperUtils.when(() -> HelperUtils.truncateString(anyString(), anyInt())).thenReturn("{\"someHeader\": \"someHeaderValue\"}");
mockApiContextUtils.when(ApiContextUtils::get).thenReturn(apiContext);
mockHelperUtils.when(() -> HelperUtils.stringNotEmptyOrNull(anyString())).thenReturn(true);
operationLogService.systemOperationLog("testCall", request, new Object(), "testModule", "testType", "testDesc", ErrorCode.ERR_OK);
}
}

View File

@ -65,10 +65,9 @@ public class PlatformApiServiceImplTest {
* 在每个测试前进行设置
* 创建 HttpClient 和响应的 Mock 对象并初始化 Mockito
*
* @throws IOException 如果在设置过程中发生 IO 异常
*/
@BeforeEach
public void setUp() throws IOException {
public void setUp() {
MockitoAnnotations.openMocks(this);
platformMock = mockStatic(HttpClientUtils.class, Mockito.CALLS_REAL_METHODS);
jsonMock = mockStatic(JsonUtils.class);

View File

@ -7,7 +7,6 @@ import com.cmcc.magent.config.ProtocolConfigure;
import com.cmcc.magent.crypto.DecryptRequestProtocol;
import com.cmcc.magent.crypto.arithmetic.CryptoHelper;
import com.cmcc.magent.exception.SecurityProtocolException;
import com.cmcc.magent.misc.JsonUtils;
import com.cmcc.magent.pojo.dto.ProtocolReq;
import com.cmcc.magent.pojo.po.BaseRespStatus;
import com.cmcc.magent.pojo.vo.ProtocolResp;
@ -78,14 +77,8 @@ public class ProtocolSecurityServiceImplTest {
@Mock
private ProtocolConfigure protocolConfigure;
@Mock
private JsonUtils jsonUtils;
@Mock
private CryptoHelper cryptoHelper;
@Spy
private ObjectMapper objectMapper = new ObjectMapper();
final private ObjectMapper objectMapper = new ObjectMapper();
private MockedStatic<CryptoHelper> cryptoHelperMockedStatic;
@ -93,6 +86,7 @@ public class ProtocolSecurityServiceImplTest {
public void setUp() {
cryptoHelperMockedStatic = Mockito.mockStatic(CryptoHelper.class);
when(protocolConfigure.getCryptoKey()).thenReturn("testKey");
assertNotNull(objectMapper);
}
@AfterEach
@ -295,7 +289,17 @@ public class ProtocolSecurityServiceImplTest {
}
@Test
@DisplayName("AES-256解密正确解码")
@DisplayName("AES-128解密异常")
public void decryptProtocol_CryptoAes128_DecryptsException() throws Exception {
String json = "{\"cryptoType\":2,\"msgContent\":\"encryptedData\"}";
when(CryptoHelper.base64Decryption("encryptedData")).thenReturn("encryptedData".getBytes());
when(CryptoHelper.aes128Decryption(any(byte[].class), anyString())).thenThrow(new InvalidKeyException());
assertThrows(SecurityProtocolException.class, () -> protocolSecurityService.decryptProtocol(json));
}
@Test
@DisplayName("DES解密正确解码")
public void decryptProtocol_CryptoAes256_DecryptsCorrectly() throws Exception {
String json = "{\"cryptoType\":3,\"msgContent\":\"encryptedData\"}";
@ -308,7 +312,18 @@ public class ProtocolSecurityServiceImplTest {
}
@Test
@DisplayName("DES解密正确解码")
@DisplayName("DES解密异常")
public void decryptProtocol_CryptoAes256_DecryptsException() throws Exception {
String json = "{\"cryptoType\":3,\"msgContent\":\"encryptedData\"}";
when(CryptoHelper.base64Decryption("encryptedData")).thenReturn("encryptedData".getBytes());
when(CryptoHelper.desDecryption(any(byte[].class), anyString())).thenThrow(new InvalidKeyException());
assertThrows(SecurityProtocolException.class, () -> protocolSecurityService.decryptProtocol(json));
}
@Test
@DisplayName("AES-256解密正确解码")
public void decryptProtocol_CryptoDes_DecryptsCorrectly() throws Exception {
String json = "{\"cryptoType\":4,\"msgContent\":\"encryptedData\"}";
@ -320,6 +335,17 @@ public class ProtocolSecurityServiceImplTest {
assertEquals("{\"cryptoType\":4,\"msgContent\":{\"item\":\"test\"}}", result);
}
@Test
@DisplayName("AES-256解密异常")
public void decryptProtocol_CryptoDes_DecryptsException() throws Exception {
String json = "{\"cryptoType\":4,\"msgContent\":\"encryptedData\"}";
when(CryptoHelper.base64Decryption("encryptedData")).thenReturn("encryptedData".getBytes());
when(CryptoHelper.aes256Decryption(any(byte[].class), anyString())).thenThrow(new InvalidKeyException());
assertThrows(SecurityProtocolException.class, () -> protocolSecurityService.decryptProtocol(json));
}
@Test
@DisplayName("无效加密类型:抛出异常")
public void decryptProtocol_InvalidCryptoType_ThrowsException() {

View File

@ -6,7 +6,6 @@ import com.cmcc.magent.pojo.po.MemoryDetails;
import com.cmcc.magent.pojo.po.MemoryInfo;
import com.cmcc.magent.pojo.po.ResourceUsage;
import com.cmcc.magent.service.SystemInfoService;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -67,7 +66,7 @@ public class ResourceUsageServiceImplTest {
@DisplayName("测试执行任务方法,验证在提供有效数据时能正确入队资源使用率")
@Test
public void performTask_ValidData_ShouldEnqueueResourceUsage() throws JsonProcessingException {
public void performTask_ValidData_ShouldEnqueueResourceUsage() {
// 准备
MemoryDetails physicalMemory = new MemoryDetails();
physicalMemory.setFreeMemory(4096L);
@ -94,7 +93,7 @@ public class ResourceUsageServiceImplTest {
@DisplayName("测试执行任务方法,验证在内存使用率为零的情况下能正确处理")
@Test
public void performTask_ZeroMemoryUsage_ShouldHandleGracefully() throws JsonProcessingException {
public void performTask_ZeroMemoryUsage_ShouldHandleGracefully() {
// 准备
MemoryDetails physicalMemory = new MemoryDetails();
physicalMemory.setFreeMemory(0L);
@ -122,7 +121,7 @@ public class ResourceUsageServiceImplTest {
@DisplayName("测试执行任务方法,验证在 CPU 使用率为零的情况下能正确处理")
@Test
public void performTask_ZeroCpuUsage_ShouldHandleGracefully() throws JsonProcessingException {
public void performTask_ZeroCpuUsage_ShouldHandleGracefully() {
// 准备
MemoryDetails physicalMemory = new MemoryDetails();
physicalMemory.setFreeMemory(4096L);

View File

@ -84,7 +84,7 @@ public class SystemInfoServiceImplTest {
// 检查返回值是否符合预期这里只能验证非空因为操作系统名称依赖具体环境
assertNotNull(osName, "操作系统名称不能为空!");
System.out.println("操作系统名称: " + osName);
log.info("操作系统名称: {}", osName);
}
/**
@ -104,7 +104,7 @@ public class SystemInfoServiceImplTest {
// 验证时间戳一般会大于当前时间减去一个合理的范围
assertTrue(bootTimeStamp > 0, "启动时间戳应该是正值!");
System.out.println("操作系统启动时间戳: " + bootTimeStamp);
log.info("操作系统启动时间戳: {}", bootTimeStamp);
}
/**
@ -129,13 +129,10 @@ public class SystemInfoServiceImplTest {
assertTrue(processorInfo.getPhysicalCores() > 0, "处理器核心数量应该大于0");
assertTrue(processorInfo.getLogicalCpu() >= processorInfo.getPhysicalCores(), "线程数量应该不小于核心数量!");
assertTrue(processorInfo.getCpuVendorFreq() > 0, "CPU频率应该大于0");
System.out.println("处理器信息: 核心数量 = " +
processorInfo.getPhysicalCores() +
", 线程数量 = " +
processorInfo.getLogicalCpu() +
", 主频 = " +
processorInfo.getCpuVendorFreq() +
" GHz");
log.info("处理器信息: 核心数量 = {}, 线程数量 = {}, 主频 = {} Hz",
processorInfo.getPhysicalCores(),
processorInfo.getLogicalCpu(),
processorInfo.getCpuVendorFreq());
}
/**
@ -159,7 +156,7 @@ public class SystemInfoServiceImplTest {
SystemInfo si = new SystemInfo();
// 转换为 HwInfo 对象并打印 JSON 格式
HwInfo hw = IObjectConvert.INSTANCE.toHwInfo(si);
System.out.println(JsonUtils.getJson(hw));
log.info(JsonUtils.getJson(hw));
}
/**
@ -194,7 +191,7 @@ public class SystemInfoServiceImplTest {
// NetworkParams np = si.getOperatingSystem().getNetworkParams();
// 转换为 HwInfo 对象并打印 JSON 格式
MemoryInfo m = IObjectConvert.INSTANCE.toMemoryInfo(memory);
System.out.println(JsonUtils.getJson(m));
log.info(JsonUtils.getJson(m));
}
/**
@ -211,9 +208,8 @@ public class SystemInfoServiceImplTest {
double d = proc.getSystemCpuLoadBetweenTicks(oldTicks) * 100;
oldTicks = proc.getSystemCpuLoadTicks();
System.out.println(FormatUtil.roundToInt(d) + " %");
Thread.sleep(1000);
log.info("Current {} Cpu load {}%", System.currentTimeMillis(), FormatUtil.roundToInt(d));
Thread.sleep(100);
}
}
}

View File

@ -37,7 +37,7 @@ import static org.mockito.Mockito.mockStatic;
@DisplayName("程序生命周期测试")
public class CustomLifecycleTest {
@Spy
private CommonConfigure commonConfigure = new CommonConfigure();
final private CommonConfigure commonConfigure = new CommonConfigure();
@InjectMocks
private CustomLifecycle customLifecycle;

View File

@ -2,8 +2,10 @@ package com.cmcc.magent.setup;
import com.cmcc.magent.MiddlewareAgentApplication;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import java.lang.reflect.Method;
@ -15,6 +17,8 @@ import java.lang.reflect.Method;
* @since 2025-02-21
*/
@SpringBootTest(classes = {MiddlewareAgentApplication.class})
@DisplayName("应用程序入口测试")
@TestPropertySource(properties = "spring.h2.server.enable=true")
public class MiddlewareAgentApplicationTest {
/**
@ -22,11 +26,12 @@ public class MiddlewareAgentApplicationTest {
*
*/
@Test
@DisplayName("测试加载SpringBoot上下文")
void contextLoads() {
Assertions.assertNotNull(this.getClass());
try {
Method method = MiddlewareAgentApplication.class.getMethod("main", String[].class);
method.invoke(null, (Object) null); // 静态方法调用的 obj null,参数需强转为 Object
method.invoke(null, (Object) new String[] {""}); // 静态方法调用的 obj null,参数需强转为 Object
} catch (Exception ignored) {
// 忽略异常
}

View File

@ -61,6 +61,7 @@ public class ValidFixValuesImplTest {
* 在每个测试方法执行前初始化测试环境
*/
@BeforeEach
@SuppressWarnings("unchecked")
public void setUp() {
validator = new ValidFixValuesImpl();
MockitoAnnotations.openMocks(this);

View File

@ -45,6 +45,7 @@ public class ValidPageSizeImplTest {
@DisplayName("测试初始化方法")
@Test
@SuppressWarnings("unchecked")
public void initialize_ShouldNotThrowException() {
ValidPageSize constraintAnnotation = new ValidPageSize() {
@Override

View File

@ -70,6 +70,7 @@ public class ValidProtocolTimestampImplTest {
* <p>验证 {@link ValidProtocolTimestampImpl#initialize(ValidProtocolTimestamp)} 方法的正常调用</p>
*/
@DisplayName("测试初始化方法")
@SuppressWarnings("unchecked")
@Test
public void initialize_ShouldInvokeSuperInitialize() {
ValidProtocolTimestamp constraintAnnotation = new ValidProtocolTimestamp() {

View File

@ -6,8 +6,22 @@ server.compression.mime-types=application/json
server.compression.min-response-size=1KB
# Spring DataSource Configuration
spring.datasource.url=jdbc:sqlite:src/main/resources/db/agent.db
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# enable H2 console
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# server config
spring.h2.server.enable=false
spring.h2.server.port=18090
# auto init
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:sql/schema.sql
# Mybatis Configuration
#mybatis.mapper-locations=classpath:mappers/*.xml

View File

@ -0,0 +1,21 @@
SET REFERENTIAL_INTEGRITY FALSE;
-- ----------------------------
-- Table structure for middleware_manager
-- ----------------------------
CREATE TABLE IF NOT EXISTS middleware_manager (
"id" BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'id',
"uid" VARCHAR(48) NOT NULL UNIQUE COMMENT '部署命令UUID',
"middleware" VARCHAR(256) NOT NULL COMMENT '中间件名称',
"middleware_ver" VARCHAR(256) COMMENT '中间件版本',
"work_directory" VARCHAR(4096) NOT NULL COMMENT '部署工作目录',
"deployment" VARCHAR COMMENT '部署命令',
"config" VARCHAR COMMENT '配置命令',
"start" VARCHAR COMMENT '启动命令',
"stop" VARCHAR COMMENT '停止命令',
"restart" VARCHAR COMMENT '重启命令',
"uninstall" VARCHAR COMMENT '卸载命令',
"created_time" DATE DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
"upgrade_time" DATE DEFAULT CURRENT_TIMESTAMP(6) COMMENT '更新时间'
);
SET REFERENTIAL_INTEGRITY TRUE;