OCT REM: 1. 修正代码警告

2. 补充单元测试用例
This commit is contained in:
HuangXin 2025-01-14 17:22:41 +08:00
parent 37b57fa577
commit 19605826f8
27 changed files with 827 additions and 197 deletions

View File

@ -130,6 +130,13 @@
<version>6.1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.12.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -1,10 +1,12 @@
package com.cmhi.magent.annotation.impl;
import com.cmhi.magent.annotation.AutoExternString;
import com.cmhi.magent.common.BaseEnum;
import com.cmhi.magent.common.ErrorCode;
import com.cmhi.magent.common.UtilsFormatType;
import com.cmhi.magent.exception.CommonErrorCodeException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
@ -12,7 +14,6 @@ import org.springframework.boot.jackson.JsonComponent;
import oshi.util.FormatUtil;
import java.io.IOException;
import java.lang.reflect.Method;
/**
* {@code AutoExternStringJsonProcess} 是一个 Jackson 自定义序列化器用于处理 {@link AutoExternString} 注解的字段
@ -148,18 +149,34 @@ public class AutoExternStringJsonProcess extends JsonSerializer<Number> implemen
jsonGenerator.writeString(formattedValue);
} else if (formatType == UtilsFormatType.FORMAT_ENUM) {
try {
// 将字段值转换为枚举实例
Method fromValueMethod = enumClass.getMethod("fromValue", int.class);
Enum<?> enumInstance = (Enum<?>) fromValueMethod.invoke(null, (int) value);
if (!enumClass.isEnum()) {
throw new CommonErrorCodeException(ErrorCode.ERR_PARAMS);
}
int index = (int) value;
if (index < 0 || index >= enumClass.getEnumConstants().length) {
throw new CommonErrorCodeException(ErrorCode.ERR_NOSUCHITEM);
}
boolean isBaseEnumImplemented = false;
for (Class<?> iface : enumClass.getInterfaces()) {
if (iface.equals(BaseEnum.class)) {
isBaseEnumImplemented = true;
break;
}
}
if (isBaseEnumImplemented) {
BaseEnum baseEnum = (BaseEnum) enumClass.getEnumConstants()[index];
jsonGenerator.writeString(baseEnum.getDescription());
} else {
jsonGenerator.writeString(enumClass.getEnumConstants()[index].toString());
}
// 调用枚举实例的描述方法
Method method = enumClass.getMethod("getDescription");
String description = (String) method.invoke(enumInstance);
// 写入描述字符串
jsonGenerator.writeString(description);
} catch (Exception e) {
throw new IOException("Failed to convert enum value to description", e);
throw new CommonErrorCodeException(ErrorCode.ERR_NOSUCHITEM);
}
} else {
jsonGenerator.writeString(numericValue.toString());
@ -188,8 +205,12 @@ public class AutoExternStringJsonProcess extends JsonSerializer<Number> implemen
AutoExternString annotation = property.getAnnotation(AutoExternString.class);
if (annotation != null) {
// 根据注解配置创建新的序列化器
return new AutoExternStringJsonProcess(annotation.prefix(), annotation.suffix(), annotation.format(), annotation.units(),
annotation.enumClass(), true);
return new AutoExternStringJsonProcess(annotation.prefix(),
annotation.suffix(),
annotation.format(),
annotation.units(),
annotation.enumClass(),
true);
}
}
// 如果字段不存在注解返回默认实例

View File

@ -71,8 +71,7 @@ public final class ConstValue {
* <li>简化形式的IPv6地址 {@code ::1} {@code 2001:db8::1}</li>
* </ul>
*/
public static final String IP_ADDRESS_REGEX =
"^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$" +
public static final String IP_ADDRESS_REGEX = "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$" +
"|^([\\da-fA-F]{1,4}:){7}[\\da-fA-F]{1,4}$" +
"|^::([\\da-fA-F]{1,4}:){0,6}[\\da-fA-F]{1,4}$" +
"|^([\\da-fA-F]{1,4}:){1,7}:$" +

View File

@ -148,8 +148,10 @@ public class CommonConfigure {
*/
@PostConstruct
private void initGlobalValue() {
log.info("Current: tokenExpiredOfSeconds = {}, allowPasswdRetryTimes = {}, showSqlCommand = {}", tokenExpiredOfSeconds,
allowPasswdRetryTimes, showSqlCommand);
log.info("Current: tokenExpiredOfSeconds = {}, allowPasswdRetryTimes = {}, showSqlCommand = {}",
tokenExpiredOfSeconds,
allowPasswdRetryTimes,
showSqlCommand);
String addr = "localhost";
try {

View File

@ -109,8 +109,12 @@ public class PlatformServiceConfig {
HttpClient httpClient = HttpClient.create().responseTimeout(Duration.ofSeconds(requestTimeout));
// 构建 WebClient
return builder.baseUrl(platformUrl).defaultHeader("Content-Type", "application/json").clientConnector(
new ReactorClientHttpConnector(httpClient)).filter(logRequest()).filter(logResponse()).build();
return builder.baseUrl(platformUrl)
.defaultHeader("Content-Type", "application/json")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.filter(logRequest())
.filter(logResponse())
.build();
}
/**

View File

@ -93,7 +93,8 @@ public class ProtocolConfigure {
private void initGlobalValue() {
log.info("Current: checkTimestamp = {}, timeoutOfSeconds = {}", checkTimestamp, timeoutOfSeconds);
setGlobalVars(Optional.ofNullable(checkTimestamp).orElse(false), Optional.ofNullable(timeoutOfSeconds).orElse(30),
setGlobalVars(Optional.ofNullable(checkTimestamp).orElse(false),
Optional.ofNullable(timeoutOfSeconds).orElse(30),
Optional.ofNullable(cryptoType).orElse(0));
}

View File

@ -61,14 +61,16 @@ public class CommonFrameworkApi {
OperationType = "SYSLOG_TYPE_READ",
OperationDesc = "SYSLOG_DESC_GET_VERSION")
public ProtocolResp<VersionResp> getVersion() {
return ProtocolResp.result(VersionResp.builder().version(VersionInfo.builder()
return ProtocolResp.result(VersionResp.builder()
.version(VersionInfo.builder()
.commitId(prgVer.getCommitId())
.commitDescribe(prgVer.getCommitDescribe())
.commitTime(prgVer.getCommitTime())
.tagName(prgVer.getTagName())
.buildTime(prgVer.getBuildTime())
.gitBranch(prgVer.getGitBranch())
.build()).build());
.build())
.build());
}
/**
@ -85,13 +87,15 @@ public class CommonFrameworkApi {
OperationType = "SYSLOG_TYPE_READ",
OperationDesc = "SYSLOG_DESC_GET_VERSION")
public ProtocolResp<VersionResp> getVersionV2() {
return ProtocolResp.result(VersionResp.builder().version(VersionInfo.builder()
return ProtocolResp.result(VersionResp.builder()
.version(VersionInfo.builder()
.commitId(prgVer.getCommitId())
.commitDescribe(prgVer.getCommitDescribe())
.commitTime(prgVer.getCommitTime())
.tagName(prgVer.getTagName())
.buildTime(prgVer.getBuildTime())
.gitBranch(prgVer.getGitBranch())
.build()).build());
.build())
.build());
}
}

View File

@ -58,8 +58,8 @@ public class RequestProtocolSecurity implements RequestBodyAdvice {
@NotNull Type type,
@NotNull Class<? extends HttpMessageConverter<?>> aClass) {
// 检查类级别或方法级别上是否存在 @DecryptionProtocol 注解
return methodParameter.getContainingClass().isAnnotationPresent(DecryptionProtocol.class)
|| methodParameter.hasMethodAnnotation(DecryptionProtocol.class);
return methodParameter.getContainingClass().isAnnotationPresent(DecryptionProtocol.class) || methodParameter.hasMethodAnnotation(
DecryptionProtocol.class);
}
/**

View File

@ -63,11 +63,10 @@ public class ResponseProtocolSecurity implements ResponseBodyAdvice<Object> {
* @since 2025-01-07
*/
@Override
public boolean supports(@NotNull MethodParameter methodParameter,
@NotNull Class<? extends HttpMessageConverter<?>> aClass) {
public boolean supports(@NotNull MethodParameter methodParameter, @NotNull Class<? extends HttpMessageConverter<?>> aClass) {
// 检查类级别或方法级别上是否存在 @EncryptionProtocol 注解
return methodParameter.getContainingClass().isAnnotationPresent(EncryptionProtocol.class)
|| methodParameter.hasMethodAnnotation(EncryptionProtocol.class);
return methodParameter.getContainingClass().isAnnotationPresent(EncryptionProtocol.class) || methodParameter.hasMethodAnnotation(
EncryptionProtocol.class);
}
/**

View File

@ -75,8 +75,9 @@ public class RequestBodyFilter implements Filter {
* @throws ServletException 如果发生其他错误
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
// 定义包装后的请求对象
ServletRequest requestWrapper = null;

View File

@ -81,7 +81,7 @@ public class HelperUtils {
if (!stringNotEmptyOrNull(orgString) || orgString.length() <= maxLength) {
return orgString;
} else {
return orgString.substring(0, maxLength - 4) + "...";
return orgString.substring(0, maxLength - 3) + "...";
}
}

View File

@ -130,9 +130,9 @@ public class JsonParameterizedType implements ParameterizedType {
if (this == other) {
return true;
}
return Objects.equals(this.ownerType, other.getOwnerType())
&& Objects.equals(this.rawType, other.getRawType())
&& Arrays.equals(this.actualTypeArguments, other.getActualTypeArguments());
return Objects.equals(this.ownerType, other.getOwnerType()) &&
Objects.equals(this.rawType, other.getRawType()) &&
Arrays.equals(this.actualTypeArguments, other.getActualTypeArguments());
}
return false;
}
@ -144,9 +144,7 @@ public class JsonParameterizedType implements ParameterizedType {
*/
@Override
public int hashCode() {
return Arrays.hashCode(this.actualTypeArguments)
^ Objects.hashCode(this.ownerType)
^ Objects.hashCode(this.rawType);
return Arrays.hashCode(this.actualTypeArguments) ^ Objects.hashCode(this.ownerType) ^ Objects.hashCode(this.rawType);
}
/**

View File

@ -1,6 +1,5 @@
package com.cmhi.magent.misc;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeansException;
@ -101,8 +100,7 @@ public class SpringBootBeanUtils implements ApplicationContextAware {
* @throws BeansException 如果上下文注入失败
*/
@Override
public void setApplicationContext(
@NotNull @org.jetbrains.annotations.NotNull ApplicationContext applicationContext) throws BeansException {
public void setApplicationContext(@org.jetbrains.annotations.NotNull ApplicationContext applicationContext) throws BeansException {
setAppContext(applicationContext);
}
}

View File

@ -30,7 +30,6 @@ import oshi.software.os.FileSystem;
import oshi.software.os.NetworkParams;
import oshi.software.os.OSFileStore;
import java.util.ArrayList;
import java.util.List;
/**
@ -234,16 +233,8 @@ public interface IObjectConvert {
* @return 自定义的 {@link FileStoreInfo} 对象
*/
default FileStoreInfo toFileStoreInfo(FileSystem fs, List<HWDiskStore> d) {
if (fs == null) {
return FileStoreInfo.builder().fileStore(new ArrayList<>()).build();
}
List<OSFileStore> o = fs.getFileStores(true);
if (o == null || o.isEmpty()) {
return FileStoreInfo.builder().fileStore(new ArrayList<>()).build();
}
FileStoreInfo fileStoreInfo = FileStoreInfo.builder().build();
fileStoreInfo.setFileStore(toFileStoreDetailsList(o));
fileStoreInfo.setHwDisk(toHwDisk(d));

View File

@ -1,4 +1,3 @@
package com.cmhi.magent.pojo.po;
import com.cmhi.magent.annotation.AutoExternString;

View File

@ -69,7 +69,7 @@ public class IfInterfaceInfo {
*/
@AutoExternString(format = UtilsFormatType.FORMAT_ENUM, enumClass = NetworkIF.IfOperStatus.class)
@Schema(description = "网络接口的运行状态枚举类型UP, DOWN, UNKNOWN 等", example = "1")
private int ifOperStatus;
private Integer ifOperStatus;
/**
* 网络接口的最大传输单元MTU

View File

@ -155,8 +155,7 @@ public class OperationLog {
* 记录 HTTP 请求的头部信息通常以 JSON 字符串形式存储
* </p>
*/
@Schema(description = "HTTP 请求头信息(以 JSON 格式存储)。",
example = "{ 'Content-Type': 'application/json' }")
@Schema(description = "HTTP 请求头信息(以 JSON 格式存储)。", example = "{ 'Content-Type': 'application/json' }")
private String requestHeaders;
/**

View File

@ -68,8 +68,7 @@ public class PageResults<T> {
* 表示当前页返回的具体数据项类型由泛型参数 {@code T} 决定
* </p>
*/
@Schema(description = "当前页的具体数据项列表。",
example = "[{ 'id': 1, 'name': 'Item 1' }, { 'id': 2, 'name': 'Item 2' }]")
@Schema(description = "当前页的具体数据项列表。", example = "[{ 'id': 1, 'name': 'Item 1' }, { 'id': 2, 'name': 'Item 2' }]")
private List<T> items;
/**

View File

@ -71,8 +71,7 @@ public class ProcessorInfo {
* 表示处理器的完整型号名称例如 "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz"
* </p>
*/
@Schema(description = "处理器的完整型号名称。",
example = "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz")
@Schema(description = "处理器的完整型号名称。", example = "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz")
private String cpuName;
/**

View File

@ -79,7 +79,8 @@ public class BasePageResultResp<T> extends BaseRespStatus {
*/
@Schema(description = "分页数据,包含分页元信息(如当前页码、总页数、总行数等)以及具体数据项。",
example = "{ \"pageNumber\": 1, \"pageSize\": 10, \"totalPage\": 5, \"totalRow\": 50, " +
"\"items\": [ {\"id\": 1, \"name\": " + "\"Item 1\"}, {\"id\": 2, \"name\": " +
"\"items\": [ {\"id\": 1, \"name\": " +
"\"Item 1\"}, {\"id\": 2, \"name\": " +
"\"Item 2\"} ] }")
private PageResults<T> items;
}

View File

@ -76,7 +76,12 @@ public class OperationLogServiceImpl implements OperationLogService {
ErrorCode errorCode) {
if (annotation != null) {
// 提取注解中的元数据并记录日志
systemOperationLog(call, request, result, annotation.OperationModule(), annotation.OperationType(), annotation.OperationDesc(),
systemOperationLog(call,
request,
result,
annotation.OperationModule(),
annotation.OperationType(),
annotation.OperationDesc(),
errorCode);
} else {
// 如果注解为空使用默认值记录日志

View File

@ -74,10 +74,7 @@ public class ValidHttpMethodImpl implements ConstraintValidator<ValidHttpMethod,
// 遍历 HTTP 方法列表并校验
strings.forEach(k -> {
if (RequestMethod.resolve(k.toUpperCase()) == null) {
errMsg.append("[")
.append(k)
.append("]: ")
.append(MessageUtil.get("http.request_invalid", ApiContextUtils.getLanguage()));
errMsg.append("[").append(k).append("]: ").append(MessageUtil.get("http.request_invalid", ApiContextUtils.getLanguage()));
}
});

View File

@ -0,0 +1,111 @@
package com.cmhi.magent.common;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
/**
* NoneEnumTest {@link NoneEnum} 的单元测试类
* <p>
* 使用 JUnit 5 AssertJ NoneEnum 的各个方法功能进行验证确保枚举方法返回的值符合预期
* 主要测试以下方法
* </p>
* <ul>
* <li>{@link NoneEnum#getStringValue()} - 验证获取字符串值功能</li>
* <li>{@link NoneEnum#getValue()} - 验证获取整数值功能</li>
* <li>{@link NoneEnum#getDescription()} - 验证获取描述功能</li>
* </ul>
*
* <p>依赖注入</p>
* <ul>
* <li>使用 {@code @SpringBootTest} 确保 Spring Boot 环境正常加载</li>
* </ul>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-13
*/
@SpringBootTest
class EnumCoverTest {
/**
* 测试 {@link NoneEnum#getStringValue()} 方法
* <p>
* 验证是否能正确返回枚举的字符串值
* </p>
*
* <p>测试逻辑</p>
* <ul>
* <li>调用 {@link NoneEnum#NONE_ENUM#getStringValue()}</li>
* <li>断言返回的字符串值是否等于预期值 {@code ""}空字符串</li>
* </ul>
*/
@Test
void testGetStringValue() {
// Setup
// Run the test
final String result = NoneEnum.NONE_ENUM.getStringValue();
// Verify the results
assertThat(result).isEqualTo("");
final String var = UtilsFormatType.FORMAT_NONE.getStringValue();
assertThat(var).isEqualTo("不进行格式化");
assertThat(UtilsFormatType.FORMAT_NONE.getValue()).isEqualTo(4);
assertThat(UtilsFormatType.FORMAT_NONE.getDescription().length()).isGreaterThan(0);
assertThat(ProtoCryptoType.CRYPTO_NONE.getValue()).isEqualTo(0);
assertThat(ProtoCryptoType.CRYPTO_NONE.getDescription().length()).isGreaterThan(0);
assertThat(ProtoCryptoType.CRYPTO_NONE.getStringValue().length()).isGreaterThan(0);
assertThat(CommonStatus.NORMAL.getValue()).isEqualTo(0);
assertThat(CommonStatus.NORMAL.getDescription().length()).isGreaterThan(0);
assertThat(CommonStatus.NORMAL.getStringValue().length()).isGreaterThan(0);
}
/**
* 测试 {@link NoneEnum#getValue()} 方法
* <p>
* 验证是否能正确返回枚举的整数值
* </p>
*
* <p>测试逻辑</p>
* <ul>
* <li>调用 {@link NoneEnum#NONE_ENUM#getValue()}</li>
* <li>断言返回的整数值是否等于预期值 {@code 0}</li>
* </ul>
*/
@Test
void testGetValue() {
// Setup
// Run the test
final Integer result = NoneEnum.NONE_ENUM.getValue();
// Verify the results
assertThat(result).isEqualTo(0);
}
/**
* 测试 {@link NoneEnum#getDescription()} 方法
* <p>
* 验证是否能正确返回枚举的描述信息
* </p>
*
* <p>测试逻辑</p>
* <ul>
* <li>调用 {@link NoneEnum#NONE_ENUM#getDescription()}</li>
* <li>断言返回的描述信息是否等于预期值 {@code ""}空字符串</li>
* </ul>
*/
@Test
void testGetDescription() {
// Setup
// Run the test
final String result = NoneEnum.NONE_ENUM.getDescription();
// Verify the results
assertThat(result).isEqualTo("");
}
}

View File

@ -0,0 +1,260 @@
package com.cmhi.magent.misc;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
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.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* HelperUtilsTest HelperUtils 工具类的单元测试类
* <p>
* 使用 JUnit 5 测试框架对 HelperUtils 提供的方法进行功能验证确保工具方法在各种场景下表现正确
* </p>
*
* <p>主要测试内容</p>
* <ul>
* <li>JSON 序列化功能的测试</li>
* <li>字符串操作的测试</li>
* <li>字节数组与十六进制字符串转换的测试</li>
* <li>字符集编码转换的测试</li>
* <li>输入流与字符串的相互转换测试</li>
* <li>时间和时间戳处理的测试</li>
* <li>字符串非空判断与合并逻辑的测试</li>
* <li>对象验证功能的测试</li>
* </ul>
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-14
*/
@SpringBootTest
@Slf4j
public class HelperUtilsTest {
/**
* 测试 JSON 序列化功能
* <p>
* 验证 {@link HelperUtils#getJson(Object)} 方法是否能正确序列化传入的对象为 JSON 字符串
* </p>
*
* @throws Exception 如果 JSON 序列化失败
*/
@Test
@DisplayName("HelperUtils.getJson")
void testGetJson() throws Exception {
// 创建一个测试对象
Long jsonVal = 123L;
// 调用 getJson 方法
String json = HelperUtils.getJson(jsonVal);
// 断言生成的 JSON 字符串不为空
assertNotNull(json);
assertEquals(new ObjectMapper().writeValueAsString(jsonVal), json);
}
/**
* 测试字符串截断功能
* <p>
* 验证 {@link HelperUtils#truncateString(String, int)} 方法是否能正确截断字符串并追加 "..." 后缀
* 断言截断后的长度内容与期望值是否一致
* </p>
*/
@Test
@DisplayName("HelperUtils.truncateString")
void testTruncateString() {
// 原始字符串
String orgString = "This is a long string";
// 截断长度
int maxLength = 8;
// 调用 truncateString 方法
String truncatedString = HelperUtils.truncateString(orgString, maxLength);
log.info("[{}] --> [{}]", orgString, truncatedString);
// 断言截断后的字符串长度符合预期
assertEquals(maxLength, truncatedString.length());
assertTrue(truncatedString.endsWith("..."));
assertTrue(truncatedString.startsWith(orgString.substring(0, maxLength - 3)));
}
/**
* 测试字节数组转换为十六进制字符串功能
* <p>
* 验证 {@link HelperUtils#bytesToHexString(byte[])} 方法是否能正确将字节数组转换为十六进制字符串
* </p>
*/
@Test
@DisplayName("HelperUtils.bytesToHexString")
void testBytesToHexString() {
// 字节数组
byte[] bArray = {1, 2, 3, 4, 5};
// 调用 bytesToHexString 方法
String hexString = HelperUtils.bytesToHexString(bArray);
// 断言生成的十六进制字符串正确
assertEquals("0102030405", hexString);
}
/**
* 测试字符串转换为 GB2312 编码的字节数组功能
* <p>
* 验证 {@link HelperUtils#convertStringToGb2312(String)} 方法是否能正确转换字符串为 GB2312 字节数组
* </p>
*
* @throws UnsupportedEncodingException 如果指定的编码不受支持
*/
@Test
@DisplayName("HelperUtils.convertStringToGb2312")
void testConvertStringToGb2312() throws UnsupportedEncodingException {
// 原始字符串
String str = "测试字符串";
// 调用 convertStringToGb2312 方法
byte[] gb2312Bytes = HelperUtils.convertStringToGb2312(str);
// 可以进一步验证转换后的字节数组是否符合预期
assertNotNull(gb2312Bytes);
assertTrue(gb2312Bytes.length > 0);
}
/**
* 测试输入流转换为字符串功能
* <p>
* 验证 {@link HelperUtils#inputStream2String(InputStream)} 方法是否能将输入流正确转换为字符串
* </p>
*
* @throws IOException 如果读取输入流失败
*/
@Test
@DisplayName("HelperUtils.inputStream2String")
void testInputStream2String() throws IOException {
// 创建一个输入流
InputStream inputStream = new ByteArrayInputStream("测试输入流".getBytes());
// 调用 inputStream2String 方法
String result = HelperUtils.inputStream2String(inputStream);
// 断言转换后的字符串正确
assertEquals("测试输入流", result);
}
/**
* 测试时间字符串转换为时间戳毫秒功能
* <p>
* 验证 {@link HelperUtils#getTimestampMilliSecond(String)} 方法是否能正确计算时间戳
* </p>
*/
@Test
@DisplayName("HelperUtils.getTimestampMilliSecond")
void testGetTimestampMilliSecond() {
// 时间字符串
String dateTime = "2023-09-15 10:10:10";
// 调用 getTimestampMilliSecond 方法
long timestamp = HelperUtils.getTimestampMilliSecond(dateTime);
// 可以根据具体时间进行断言
assertEquals(1694743810000L, timestamp);
}
/**
* 测试获取当前日期和时间的功能
* <p>
* 验证 {@link HelperUtils#getCurrentDatetime()} 方法是否能返回不为空的当前日期和时间字符串
* </p>
*/
@Test
@DisplayName("HelperUtils.getCurrentDatetime")
void testGetCurrentDatetime() {
// 调用 getCurrentDatetime 方法
String currentDatetime = HelperUtils.getCurrentDatetime();
// 可以根据当前时间进行断言
assertNotNull(currentDatetime);
}
/**
* 测试字符串非空或非 null 检查功能
* <p>
* 验证 {@link HelperUtils#stringNotEmptyOrNull(String)} 方法是否能正确判断字符串是否为空或 null
* </p>
*/
@Test
@DisplayName("HelperUtils.stringNotEmptyOrNull")
void testStringNotEmptyOrNull() {
// 非空字符串
String str = "非空字符串";
// 调用 stringNotEmptyOrNull 方法
boolean result = HelperUtils.stringNotEmptyOrNull(str);
// 断言结果为 true
assertTrue(result);
// 空字符串
str = "";
result = HelperUtils.stringNotEmptyOrNull(str);
// 断言结果为 false
assertFalse(result);
// null 字符串
str = null;
// 断言结果为 false
assertFalse(HelperUtils.stringNotEmptyOrNull(str));
}
/**
* 测试合并数据库字符串值的功能
* <p>
* 验证 {@link HelperUtils#meagreDbStringValue(String)} 方法是否能正确判断数据库字符串值
* <ul>
* <li>对于非空字符串返回原始值</li>
* <li>对于空字符串或 null返回 null</li>
* </ul>
* </p>
*/
@Test
@DisplayName("HelperUtils.meagreDbStringValue")
void testMeagreDbStringValue() {
// 非空字符串
String str = "非空字符串";
// 调用 meagreDbStringValue 方法
String result = HelperUtils.meagreDbStringValue(str);
// 断言结果为原始字符串
assertEquals(str, result);
// 空字符串
str = "";
result = HelperUtils.meagreDbStringValue(str);
// 断言结果为 null
assertNull(result);
// null 字符串
str = null;
result = HelperUtils.meagreDbStringValue(str);
// 断言结果为 null
assertNull(result);
}
}

View File

@ -1,114 +0,0 @@
package com.cmhi.magent.service;
import com.cmhi.magent.misc.HelperUtils;
import com.cmhi.magent.pojo.mapper.IObjectConvert;
import com.cmhi.magent.pojo.po.HwInfo;
import com.cmhi.magent.pojo.po.MemoryDetails;
import com.cmhi.magent.pojo.po.ProcessorInfo;
import com.cmhi.magent.service.impl.SystemInfoServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import oshi.SystemInfo;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HWDiskStore;
import oshi.hardware.NetworkIF;
import oshi.hardware.PhysicalMemory;
import oshi.software.os.FileSystem;
import oshi.software.os.NetworkParams;
import oshi.software.os.OSFileStore;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
class SystemInfoServiceTest {
private SystemInfoServiceImpl systemInfoService; // 实现类对象
@Resource
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
// 实例化实现类
systemInfoService = new SystemInfoServiceImpl();
}
@Test
void testGetOsName() {
// 调用接口方法
String osName = systemInfoService.getOsName();
// 检查返回值是否符合预期这里只能验证非空因为操作系统名称依赖具体环境
assertNotNull(osName, "操作系统名称不能为空!");
System.out.println("操作系统名称: " + osName);
}
@Test
void testGetOsBootTimeStamp() {
// 调用接口方法
long bootTimeStamp = systemInfoService.getOsBootTimeStamp();
// 验证时间戳一般会大于当前时间减去一个合理的范围
assertTrue(bootTimeStamp > 0, "启动时间戳应该是正值!");
System.out.println("操作系统启动时间戳: " + bootTimeStamp);
}
@Test
void testGetProcessInfo() {
// 调用接口方法获取处理器信息
ProcessorInfo processorInfo = systemInfoService.getProcessInfo();
// 检查返回的处理器信息是否合理
assertNotNull(processorInfo, "处理器信息不能为空!");
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");
}
@Test
void testGetHardwareInfo() {
// // 调用接口方法获取硬件信息
// HwInfo hardwareInfo = systemInfoService.getHardwareInfo();
//
// // 检查返回的硬件信息是否合理
// assertNotNull(hardwareInfo, "硬件信息不能为空!");
// assertTrue(hardwareInfo.get() > 0, "内存大小应该大于0");
// assertTrue(hardwareInfo.getDiskCapacity() > 0, "磁盘容量应该大于0");
// System.out.println("硬件信息: 内存大小 = " + hardwareInfo.getMemorySize() +
// " GB, 磁盘容量 = " + hardwareInfo.getDiskCapacity() + " GB");
}
@Test
void testGetMemoryInfo() throws JsonProcessingException {
SystemInfo si = new SystemInfo();
GlobalMemory memory = si.getHardware().getMemory();
List<PhysicalMemory> ph = memory.getPhysicalMemory();
List<HWDiskStore> d = si.getHardware().getDiskStores();
FileSystem fs = si.getOperatingSystem().getFileSystem();
List<OSFileStore> os = fs.getFileStores(true);
List<NetworkIF> nf = si.getHardware().getNetworkIFs(true);
NetworkParams np = si.getOperatingSystem().getNetworkParams();
MemoryDetails md = new MemoryDetails();
md.setFreeMemory(1000L);
md.setTotalMemory(3000L);
HwInfo hw = IObjectConvert.INSTANCE.toHwInfo(si);
System.out.println(HelperUtils.getJson(hw));
// System.out.println(HelperUtils.getJson(d));
// System.out.println(HelperUtils.getJson(fs));
//System.out.println(objectMapper.writeValueAsString(md));
// System.out.println(HelperUtils.getJson(memory));
// System.out.println(HelperUtils.getJson(ph));
}
}

View File

@ -0,0 +1,143 @@
package com.cmhi.magent.service.impl;
import com.cmhi.magent.pojo.po.RegisterAgent;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.annotation.Resource;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 单元测试类 {@code PlatformApiServiceImplTest} 用于测试 {@link PlatformApiServiceImpl} 的功能
* <p>
* 测试的主要目标是确保 {@link PlatformApiServiceImpl#registerAgent(RegisterAgent)} 方法能够正常工作
* 并验证 {@link WebClient} 在调用外部 API 时的行为
* </p>
*
* <p>测试中使用了 {@code MockWebServer} 来模拟 HTTP 请求和响应避免依赖实际的外部服务</p>
*
* <p>主要验证内容包括</p>
* <ul>
* <li>是否正确构造了 HTTP 请求如方法和路径</li>
* <li>是否正确处理了 HTTP 响应并返回预期结果</li>
* </ul>
*
* <p>依赖注入说明</p>
* <ul>
* <li>通过 {@code @Resource} 注入被测试的 {@code PlatformApiServiceImpl}</li>
* <li>通过 {@code @Autowired} 注入 {@code WebClient.Builder}用于生成测试时专用的 {@code WebClient}</li>
* </ul>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-14
*/
@SpringBootTest
class PlatformApiServiceImplTest {
/**
* 被测试的服务类 {@link PlatformApiServiceImpl}
*/
@Resource
private PlatformApiServiceImpl platformApiService;
/**
* 用于创建 {@link WebClient} 实例的 {@link WebClient.Builder}通过它构造指向 {@code MockWebServer} WebClient
*/
@Autowired
private WebClient.Builder webClientBuilder;
/**
* 模拟 HTTP 请求和响应的 MockWebServer
*/
private MockWebServer mockWebServer;
/**
* 初始化 MockWebServer并通过反射注入 MockWebServer 对应的 WebClient PlatformApiService
*
* @throws Exception 如果 MockWebServer WebClient 初始化失败
*/
@BeforeEach
void setUp() throws Exception {
// 初始化 MockWebServer
mockWebServer = new MockWebServer();
mockWebServer.start();
// 使用 MockWebServer URL 配置 WebClient
WebClient webClient = webClientBuilder.baseUrl(mockWebServer.url("/").toString()).build();
// 使用反射注入 WebClient PlatformApiService
injectWebClientIntoApiService(webClient);
}
/**
* 停止 MockWebServer
*
* @throws Exception 如果 MockWebServer 停止失败
*/
@AfterEach
void tearDown() throws Exception {
// 停止 MockWebServer
mockWebServer.shutdown();
}
/**
* 使用反射将测试用的 {@link WebClient} 实例注入到 {@link PlatformApiServiceImpl}
*
* @param webClientToInject 用于注入的 {@link WebClient} 实例
* @throws Exception 如果反射注入过程中出现错误
*/
private void injectWebClientIntoApiService(WebClient webClientToInject) throws Exception {
var webClientField = PlatformApiServiceImpl.class.getDeclaredField("webClient");
webClientField.setAccessible(true);
webClientField.set(platformApiService, webClientToInject);
}
/**
* 测试 {@link PlatformApiServiceImpl#registerAgent(RegisterAgent)} 方法
*
* <p>验证以下内容</p>
* <ul>
* <li>是否正确发送了 HTTP POST 请求到指定路径 {@code /register}</li>
* <li>是否正确处理了 HTTP 响应并返回预期的结果</li>
* </ul>
*
* <p>测试流程</p>
* <ol>
* <li>设置 MockWebServer 的响应内容为 {@code {"message": "Hello, World!"}}</li>
* <li>使用测试数据 {@link RegisterAgent} 对象调用 {@code registerAgent} 方法</li>
* <li>验证返回值是否等于设置的响应内容</li>
* <li>验证发送的请求是否符合预期如方法为 POST路径为 /register</li>
* </ol>
*
* @throws InterruptedException 如果等待 MockWebServer 请求时被中断
* @throws JsonProcessingException 如果 JSON 处理发生错误
*/
@Test
void testFetchData() throws InterruptedException, JsonProcessingException {
// 准备模拟的 HTTP 响应
String mockResponse = "{\"message\": \"Hello, World!\"}";
mockWebServer.enqueue(new MockResponse().setBody(mockResponse).addHeader("Content-Type", "application/json"));
// Setup
final RegisterAgent agent = RegisterAgent.builder().deviceId("123542535").build();
// 调用被测试方法
final String result = platformApiService.registerAgent(agent);
// 验证结果
assertThat(result).isEqualTo(mockResponse);
// 验证请求
var recordedRequest = mockWebServer.takeRequest();
assertThat(recordedRequest.getMethod()).isEqualTo("POST");
assertThat(recordedRequest.getPath()).isEqualTo("/register");
}
}

View File

@ -0,0 +1,206 @@
package com.cmhi.magent.service.impl;
import com.cmhi.magent.misc.HelperUtils;
import com.cmhi.magent.pojo.mapper.IObjectConvert;
import com.cmhi.magent.pojo.po.HwInfo;
import com.cmhi.magent.pojo.po.MemoryDetails;
import com.cmhi.magent.pojo.po.ProcessorInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import oshi.SystemInfo;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HWDiskStore;
import oshi.hardware.NetworkIF;
import oshi.hardware.PhysicalMemory;
import oshi.software.os.FileSystem;
import oshi.software.os.NetworkParams;
import oshi.software.os.OSFileStore;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 单元测试类 {@code SystemInfoServiceImplTest}用于测试 {@link SystemInfoServiceImpl} 的功能
* <p>
* 本测试类通过各种测试用例验证 {@link SystemInfoServiceImpl} 提供的系统信息获取功能是否正常工作
* 主要测试的功能包括
* </p>
* <ul>
* <li>获取操作系统名称</li>
* <li>获取操作系统启动时间戳</li>
* <li>获取 CPU 处理器信息</li>
* <li>获取硬件信息</li>
* <li>获取内存信息</li>
* </ul>
* <p>
* 测试内容涉及对操作系统内存磁盘网络等信息的验证通过对返回结果的合理性检查来确保相关接口的正确性
* </p>
*
* <p>依赖注入说明</p>
* <ul>
* <li>通过实例化的方式创建被测试的 {@link SystemInfoServiceImpl} 实例</li>
* <li>通过 {@code @Resource} 注入 {@link ObjectMapper}用于 JSON 处理</li>
* </ul>
*
* @author huangxin@cmhi.chinamobile.com
* @version 1.0.0
* @since 2025-01-07
*/
@SpringBootTest
class SystemInfoServiceImplTest {
/**
* 被测试的服务类 {@link SystemInfoServiceImpl}
*/
private SystemInfoServiceImpl systemInfoService;
/**
* 用于序列化和反序列化 JSON {@link ObjectMapper} 实例
*/
@Resource
private ObjectMapper objectMapper;
/**
* 在每个测试方法运行之前初始化被测试对象
* <p>
* 在此方法中实例化 {@link SystemInfoServiceImpl}确保每次测试都从干净的环境开始
* </p>
*/
@BeforeEach
void setUp() {
// 实例化实现类
systemInfoService = new SystemInfoServiceImpl();
}
/**
* 测试 {@link SystemInfoServiceImpl#getOsName()} 方法
* <p>
* 该方法用于获取操作系统的名称测试通过以下内容进行验证
* </p>
* <ul>
* <li>返回值不能为空</li>
* <li>打印操作系统名称到控制台以供手动检查</li>
* </ul>
*/
@Test
void testGetOsName() {
// 调用接口方法
String osName = systemInfoService.getOsName();
// 检查返回值是否符合预期这里只能验证非空因为操作系统名称依赖具体环境
assertNotNull(osName, "操作系统名称不能为空!");
System.out.println("操作系统名称: " + osName);
}
/**
* 测试 {@link SystemInfoServiceImpl#getOsBootTimeStamp()} 方法
* <p>
* 该方法用于获取操作系统的启动时间戳测试通过以下内容进行验证
* </p>
* <ul>
* <li>返回的时间戳为正值</li>
* <li>打印启动时间戳到控制台以供手动检查</li>
* </ul>
*/
@Test
void testGetOsBootTimeStamp() {
// 调用接口方法
long bootTimeStamp = systemInfoService.getOsBootTimeStamp();
// 验证时间戳一般会大于当前时间减去一个合理的范围
assertTrue(bootTimeStamp > 0, "启动时间戳应该是正值!");
System.out.println("操作系统启动时间戳: " + bootTimeStamp);
}
/**
* 测试 {@link SystemInfoServiceImpl#getProcessInfo()} 方法
* <p>
* 该方法用于获取处理器的详细信息测试通过以下内容进行验证
* </p>
* <ul>
* <li>返回值不能为空</li>
* <li>处理器核心数量大于 0</li>
* <li>线程数量不小于核心数量</li>
* <li>CPU 主频大于 0</li>
* </ul>
*/
@Test
void testGetProcessInfo() {
// 调用接口方法获取处理器信息
ProcessorInfo processorInfo = systemInfoService.getProcessInfo();
// 检查返回的处理器信息是否合理
assertNotNull(processorInfo, "处理器信息不能为空!");
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");
}
/**
* 测试 {@link SystemInfoServiceImpl#getHardwareInfo()} 方法
* <p>
* 该方法当前被注释掉无法执行实际测试
* </p>
* <p>
* 如果启用此方法测试应包括以下内容
* </p>
* <ul>
* <li>返回的硬件信息不能为空</li>
* <li>内存大小大于 0</li>
* <li>磁盘容量大于 0</li>
* </ul>
*/
@Test
void testGetHardwareInfo() {
// 方法被注释掉当前无法运行测试
}
/**
* 测试系统内存磁盘和网络的详细信息
* <p>
* 测试过程中通过 {@link SystemInfo} 对象获取各种硬件和操作系统信息并转换为 {@link HwInfo} 对象
* 测试内容包括
* </p>
* <ul>
* <li>物理内存信息</li>
* <li>磁盘存储信息</li>
* <li>文件系统信息</li>
* <li>网络接口信息</li>
* <li>网络参数</li>
* </ul>
* <p>
* 本方法主要用于调试和打印相关信息
* </p>
*
* @throws JsonProcessingException 如果 JSON 序列化或反序列化失败
*/
@Test
void testGetMemoryInfo() throws JsonProcessingException {
// 获取系统信息
SystemInfo si = new SystemInfo();
GlobalMemory memory = si.getHardware().getMemory();
List<PhysicalMemory> ph = memory.getPhysicalMemory();
List<HWDiskStore> d = si.getHardware().getDiskStores();
FileSystem fs = si.getOperatingSystem().getFileSystem();
List<OSFileStore> os = fs.getFileStores(true);
List<NetworkIF> nf = si.getHardware().getNetworkIFs(true);
NetworkParams np = si.getOperatingSystem().getNetworkParams();
// 创建 MemoryDetails 对象
MemoryDetails md = new MemoryDetails();
md.setFreeMemory(1000L);
md.setTotalMemory(3000L);
// 转换为 HwInfo 对象并打印 JSON 格式
HwInfo hw = IObjectConvert.INSTANCE.toHwInfo(si);
System.out.println(HelperUtils.getJson(hw));
}
}