[update] 单元测试完善
Some checks failed
yexuejc-base package jre11 / package_job (push) Failing after 19s

This commit is contained in:
2025-09-27 12:43:43 +08:00
parent 1c6e424977
commit 78ca6885c5
3 changed files with 757 additions and 0 deletions

View File

@@ -0,0 +1,232 @@
package com.yexuejc.base.encrypt;
import com.yexuejc.base.exception.BaseException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Assertions;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.*;
/**
* AES加密增强测试类 - 验证优化后的安全性和功能
*
* @author optimization
* @date 2025/09/23
*/
@DisplayName("AES加密增强测试")
class AESEnhancedTest {
private AES aes;
@BeforeEach
void setUp() {
aes = AES.builder();
}
@Test
@DisplayName("测试密钥和IV必须设置")
void testKeyAndIvRequired() {
// 测试未设置密钥时抛出异常
IllegalStateException exception = assertThrows(IllegalStateException.class, () -> {
aes.encrypt("test data");
});
assertTrue(exception.getMessage().contains("AES密钥未设置"));
}
@Test
@DisplayName("测试密钥长度验证")
void testKeyLengthValidation() {
// 测试无效密钥长度
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
aes.setKey("short");
});
assertTrue(exception.getMessage().contains("AES密钥长度必须是16、24或32字节"));
// 测试有效密钥长度
assertDoesNotThrow(() -> {
aes.setKey("1234567890123456"); // 16字节
aes.setKey("123456789012345678901234"); // 24字节
aes.setKey("12345678901234567890123456789012"); // 32字节
});
}
@Test
@DisplayName("测试IV长度验证")
void testIvLengthValidation() {
// 设置有效密钥
aes.setKey("1234567890123456");
// 测试无效IV长度
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
aes.setIv("short");
});
assertTrue(exception.getMessage().contains("AES初始向量长度必须是16字节"));
// 测试有效IV长度
assertDoesNotThrow(() -> {
aes.setIv("1234567890123456"); // 16字节
});
}
@Test
@DisplayName("测试输入参数验证")
void testInputValidation() {
aes.setKey("1234567890123456")
.setIv("1234567890123456")
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
// 测试空输入
IllegalArgumentException encryptException = assertThrows(IllegalArgumentException.class, () -> {
aes.encrypt(null);
});
assertTrue(encryptException.getMessage().contains("加密数据不能为空"));
IllegalArgumentException decryptException = assertThrows(IllegalArgumentException.class, () -> {
aes.decrypt(null);
});
assertTrue(decryptException.getMessage().contains("解密数据不能为空"));
}
@Test
@DisplayName("测试字符集设置验证")
void testCharsetValidation() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
aes.setCharset(null);
});
assertTrue(exception.getMessage().contains("字符集不能为空"));
// 测试有效字符集设置
assertDoesNotThrow(() -> {
aes.setCharset(StandardCharsets.UTF_8);
});
}
@Test
@DisplayName("测试ECB模式不需要IV")
void testECBModeWithoutIV() throws BaseException {
String data = "Hello World!";
aes.setKey("1234567890123456")
.setAlgorithm(AES.ALGORITHM.AES_ECB_PKCS5Padding);
// ECB模式不需要IV
String encrypted = aes.encrypt(data);
assertNotNull(encrypted);
assertFalse(encrypted.isEmpty());
String decrypted = aes.decrypt(encrypted);
assertEquals(data, decrypted);
}
@Test
@DisplayName("测试完整的加密解密流程")
void testCompleteEncryptDecryptFlow() throws BaseException {
String originalData = "这是一个测试字符串包含中文!";
aes.setKey("1234567890123456")
.setIv("1234567890123456")
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding)
.setCharset(StandardCharsets.UTF_8);
String encrypted = aes.encrypt(originalData);
assertNotNull(encrypted);
assertNotEquals(originalData, encrypted);
String decrypted = aes.decrypt(encrypted);
assertEquals(originalData, decrypted);
}
@Test
@DisplayName("测试不同AES算法模式")
void testDifferentAlgorithms() throws BaseException {
String data = "Test Data";
String key = "1234567890123456";
String iv = "1234567890123456";
AES.ALGORITHM[] algorithms = {
AES.ALGORITHM.AES_CBC_PKCS5Padding,
AES.ALGORITHM.AES_OFB_PKCS5Padding,
AES.ALGORITHM.AES_ECB_PKCS5Padding
};
for (AES.ALGORITHM algorithm : algorithms) {
AES testAes = AES.builder()
.setKey(key)
.setAlgorithm(algorithm);
if (!algorithm.code.contains("ECB")) {
testAes.setIv(iv);
}
String encrypted = testAes.encrypt(data);
String decrypted = testAes.decrypt(encrypted);
assertEquals(data, decrypted, "Algorithm " + algorithm.code + " failed");
}
}
@Test
@DisplayName("测试建造者模式")
void testBuilderPattern() {
AES aes1 = AES.builder();
AES aes2 = AES.builder();
// 现在返回不同的实例,避免状态共享
assertNotNull(aes1);
assertNotNull(aes2);
assertNotSame(aes1, aes2); // 确认不是单例
}
@Test
@DisplayName("测试解密异常处理")
void testDecryptErrorHandling() {
aes.setKey("1234567890123456")
.setIv("1234567890123456")
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
// 测试无效Base64数据
BaseException exception = assertThrows(BaseException.class, () -> {
aes.decrypt("invalid_base64_data");
});
assertNotNull(exception.getErrorCode());
}
@Test
@DisplayName("测试不同密钥长度")
void testDifferentKeyLengths() throws BaseException {
String data = "Test Data";
String iv = "1234567890123456";
// 测试16字节密钥
AES aes16 = AES.builder()
.setKey("1234567890123456")
.setIv(iv)
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
String encrypted16 = aes16.encrypt(data);
String decrypted16 = aes16.decrypt(encrypted16);
assertEquals(data, decrypted16);
// 测试24字节密钥
AES aes24 = AES.builder()
.setKey("123456789012345678901234")
.setIv(iv)
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
String encrypted24 = aes24.encrypt(data);
String decrypted24 = aes24.decrypt(encrypted24);
assertEquals(data, decrypted24);
// 测试32字节密钥
AES aes32 = AES.builder()
.setKey("12345678901234567890123456789012")
.setIv(iv)
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding);
String encrypted32 = aes32.encrypt(data);
String decrypted32 = aes32.decrypt(encrypted32);
assertEquals(data, decrypted32);
}
}

View File

@@ -0,0 +1,237 @@
package com.yexuejc.base.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* ObjUtil增强测试类 - 验证优化后的异常处理和功能
*
* @author optimization
* @date 2025/09/23
*/
@DisplayName("ObjUtil增强测试")
class ObjUtilEnhancedTest {
@Test
@DisplayName("测试基本字段复制功能")
void testBasicFieldCopy() throws Exception {
SourceObject source = new SourceObject();
source.setName("Test Name");
source.setAge(25);
source.setEmail("test@example.com");
TargetObject target = ObjUtil.copy(source, TargetObject.class, null, null);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
assertEquals(source.getEmail(), target.getEmail());
}
@Test
@DisplayName("测试包含字段过滤")
void testIncludeFieldFiltering() throws Exception {
SourceObject source = new SourceObject();
source.setName("Test Name");
source.setAge(25);
source.setEmail("test@example.com");
List<String> includeFields = Arrays.asList("name", "age");
TargetObject target = ObjUtil.copy(source, TargetObject.class, includeFields, null);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
assertNull(target.getEmail()); // 不在包含列表中
}
@Test
@DisplayName("测试排除字段过滤")
void testExcludeFieldFiltering() throws Exception {
SourceObject source = new SourceObject();
source.setName("Test Name");
source.setAge(25);
source.setEmail("test@example.com");
List<String> excludeFields = Arrays.asList("email");
TargetObject target = ObjUtil.copy(source, TargetObject.class, null, excludeFields);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
assertNull(target.getEmail()); // 被排除
}
@Test
@DisplayName("测试使用Setter方法复制")
void testCopyWithSetter() throws Exception {
SourceObject source = new SourceObject();
source.setName("Test Name");
source.setAge(25);
TargetObjectWithSetter target = ObjUtil.copy(source, TargetObjectWithSetter.class, true);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
}
@Test
@DisplayName("测试字段不匹配的处理")
void testFieldMismatchHandling() throws Exception {
SourceObjectWithExtraField source = new SourceObjectWithExtraField();
source.setName("Test Name");
source.setAge(25);
source.setExtraField("Extra Value");
// 目标类没有extraField字段
TargetObject target = ObjUtil.copy(source, TargetObject.class, null, null);
assertNotNull(target);
assertEquals(source.getName(), target.getName());
assertEquals(source.getAge(), target.getAge());
// extraField应该被忽略不会导致异常
}
@Test
@DisplayName("测试空源对象处理")
void testNullSourceHandling() {
assertThrows(NullPointerException.class, () -> {
ObjUtil.copy(null, TargetObject.class, null, null);
});
}
@Test
@DisplayName("测试获取所有字段功能")
void testGetAllFields() {
List<java.lang.reflect.Field> fields = ObjUtil.getAllFields(ChildObject.class);
assertNotNull(fields);
assertTrue(fields.size() >= 3); // 至少包含child字段和父类的两个字段
// 验证包含父类字段
boolean hasParentField1 = fields.stream().anyMatch(f -> "parentField1".equals(f.getName()));
boolean hasParentField2 = fields.stream().anyMatch(f -> "parentField2".equals(f.getName()));
boolean hasChildField = fields.stream().anyMatch(f -> "childField".equals(f.getName()));
assertTrue(hasParentField1);
assertTrue(hasParentField2);
assertTrue(hasChildField);
}
@Test
@DisplayName("测试获取所有Getter方法功能")
void testGetAllGetterMethods() {
List<java.lang.reflect.Method> methods = ObjUtil.getAllGetterMethods(ChildObject.class, "get");
assertNotNull(methods);
assertTrue(methods.size() >= 3); // 至少包含三个getter方法
// 验证包含父类方法
boolean hasGetParentField1 = methods.stream().anyMatch(m -> "getParentField1".equals(m.getName()));
boolean hasGetParentField2 = methods.stream().anyMatch(m -> "getParentField2".equals(m.getName()));
boolean hasGetChildField = methods.stream().anyMatch(m -> "getChildField".equals(m.getName()));
assertTrue(hasGetParentField1);
assertTrue(hasGetParentField2);
assertTrue(hasGetChildField);
}
@Test
@DisplayName("测试首字母小写工具方法")
void testLowerCaseFirstChar() {
assertEquals("test", ObjUtil.lowerCaseFirstChar("Test"));
assertEquals("tEST", ObjUtil.lowerCaseFirstChar("TEST"));
assertEquals("", ObjUtil.lowerCaseFirstChar(""));
assertNull(ObjUtil.lowerCaseFirstChar(null));
assertEquals("a", ObjUtil.lowerCaseFirstChar("A"));
}
@Test
@DisplayName("测试目标类没有默认构造函数的情况")
void testTargetClassWithoutDefaultConstructor() {
SourceObject source = new SourceObject();
source.setName("Test");
assertThrows(Exception.class, () -> {
ObjUtil.copy(source, ClassWithoutDefaultConstructor.class, null, null);
});
}
// 测试用的内部类
static class SourceObject {
private String name;
private Integer age;
private String email;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
static class SourceObjectWithExtraField extends SourceObject {
private String extraField;
public String getExtraField() { return extraField; }
public void setExtraField(String extraField) { this.extraField = extraField; }
}
static class TargetObject {
private String name;
private Integer age;
private String email;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
static class TargetObjectWithSetter {
private String name;
private Integer age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
static class ParentObject {
private String parentField1;
private String parentField2;
public String getParentField1() { return parentField1; }
public void setParentField1(String parentField1) { this.parentField1 = parentField1; }
public String getParentField2() { return parentField2; }
public void setParentField2(String parentField2) { this.parentField2 = parentField2; }
}
static class ChildObject extends ParentObject {
private String childField;
public String getChildField() { return childField; }
public void setChildField(String childField) { this.childField = childField; }
}
static class ClassWithoutDefaultConstructor {
private String name;
public ClassWithoutDefaultConstructor(String name) {
this.name = name;
}
public String getName() { return name; }
}
}

View File

@@ -0,0 +1,288 @@
package com.yexuejc.base.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.*;
/**
* StrUtil增强测试类 - 验证优化后的性能和线程安全性
*
* @author optimization
* @date 2025/09/23
*/
@DisplayName("StrUtil增强测试")
class StrUtilEnhancedTest {
@Test
@DisplayName("测试MD5计算的一致性")
void testMD5Consistency() {
String input = "Hello World!";
String expected = "ed076287532e86365e841e92bfc50d8c"; // 已知MD5值
String result1 = StrUtil.toMD5(input);
String result2 = StrUtil.toMD5(input);
assertEquals(expected, result1);
assertEquals(result1, result2);
}
@Test
@DisplayName("测试SHA256计算的一致性")
void testSHA256Consistency() {
String input = "Hello World!";
String expected = "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"; // 已知SHA256值
String result1 = StrUtil.toSHA256(input);
String result2 = StrUtil.toSHA256(input);
assertEquals(expected, result1);
assertEquals(result1, result2);
}
@Test
@DisplayName("测试MD5处理null值")
void testMD5WithNull() {
assertNull(StrUtil.toMD5(null));
}
@Test
@DisplayName("测试SHA256处理null值")
void testSHA256WithNull() {
assertNull(StrUtil.toSHA256(null));
}
@Test
@DisplayName("测试不支持的SHA算法")
void testUnsupportedSHAAlgorithm() {
String result = StrUtil.toSHA("test", "INVALID_ALGORITHM");
assertNull(result);
}
@Test
@DisplayName("测试中文字符的MD5计算")
void testMD5WithChinese() {
String input = "你好世界";
String result = StrUtil.toMD5(input);
assertNotNull(result);
assertEquals(32, result.length()); // MD5长度应为32
assertTrue(result.matches("[a-f0-9]+"));
}
@Test
@DisplayName("测试中文字符的SHA256计算")
void testSHA256WithChinese() {
String input = "你好世界";
String result = StrUtil.toSHA256(input);
assertNotNull(result);
assertEquals(64, result.length()); // SHA256长度应为64
assertTrue(result.matches("[a-f0-9]+"));
}
@Test
@DisplayName("测试长字符串的MD5计算")
void testMD5WithLongString() {
StringBuilder longString = new StringBuilder();
for (int i = 0; i < 10000; i++) {
longString.append("abcdefghij");
}
String result = StrUtil.toMD5(longString.toString());
assertNotNull(result);
assertEquals(32, result.length());
}
@RepeatedTest(10)
@DisplayName("测试SecureRandom生成的随机性")
void testCodeIdRandomness() {
String id = "12345678901234567890123456789012"; // 32位ID
String coded1 = StrUtil.codeId(id);
String coded2 = StrUtil.codeId(id);
assertEquals(64, coded1.length());
assertEquals(64, coded2.length());
// 由于使用SecureRandom两次编码结果应该不同
assertNotEquals(coded1, coded2);
// 解码后应该得到原始ID
String decoded1 = StrUtil.decodeId(coded1);
String decoded2 = StrUtil.decodeId(coded2);
assertEquals(id, decoded1);
assertEquals(id, decoded2);
}
@Test
@DisplayName("测试codeId处理无效输入")
void testCodeIdWithInvalidInput() {
// 测试null
assertNull(StrUtil.codeId(null));
// 测试长度不是32的字符串
String shortId = "short";
assertEquals(shortId, StrUtil.codeId(shortId));
String longId = "this_is_a_very_long_string_that_exceeds_32_characters";
assertEquals(longId, StrUtil.codeId(longId));
}
@Test
@DisplayName("测试decodeId处理无效输入")
void testDecodeIdWithInvalidInput() {
// 测试null
assertNull(StrUtil.decodeId(null));
// 测试长度不是64的字符串
String shortCoded = "short";
assertEquals(shortCoded, StrUtil.decodeId(shortCoded));
String longCoded = "this_is_a_very_long_string_that_exceeds_64_characters_and_should_be_returned_as_is";
assertEquals(longCoded, StrUtil.decodeId(longCoded));
}
@Test
@DisplayName("测试线程安全性 - MD5计算")
void testMD5ThreadSafety() throws InterruptedException {
final int threadCount = 20;
final int iterationsPerThread = 100;
final String testString = "Thread Safety Test";
final String expectedMD5 = StrUtil.toMD5(testString);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
AtomicBoolean hasError = new AtomicBoolean(false);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
for (int j = 0; j < iterationsPerThread; j++) {
String result = StrUtil.toMD5(testString);
if (!expectedMD5.equals(result)) {
hasError.set(true);
break;
}
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
assertFalse(hasError.get(), "MD5计算在多线程环境下出现不一致结果");
}
@Test
@DisplayName("测试线程安全性 - SHA256计算")
void testSHA256ThreadSafety() throws InterruptedException {
final int threadCount = 20;
final int iterationsPerThread = 100;
final String testString = "Thread Safety Test";
final String expectedSHA256 = StrUtil.toSHA256(testString);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
AtomicBoolean hasError = new AtomicBoolean(false);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
for (int j = 0; j < iterationsPerThread; j++) {
String result = StrUtil.toSHA256(testString);
if (!expectedSHA256.equals(result)) {
hasError.set(true);
break;
}
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
assertFalse(hasError.get(), "SHA256计算在多线程环境下出现不一致结果");
}
@Test
@DisplayName("测试线程安全性 - codeId生成")
void testCodeIdThreadSafety() throws InterruptedException {
final int threadCount = 10;
final int iterationsPerThread = 50;
final String testId = "12345678901234567890123456789012";
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
Set<String> results = new HashSet<>();
AtomicBoolean hasError = new AtomicBoolean(false);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
for (int j = 0; j < iterationsPerThread; j++) {
String coded = StrUtil.codeId(testId);
String decoded = StrUtil.decodeId(coded);
if (!testId.equals(decoded)) {
hasError.set(true);
break;
}
synchronized (results) {
results.add(coded);
}
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS));
assertFalse(hasError.get(), "codeId在多线程环境下解码失败");
// 验证生成的编码都是唯一的由于使用SecureRandom
assertEquals(threadCount * iterationsPerThread, results.size(),
"生成的编码应该都是唯一的");
}
@Test
@DisplayName("测试性能 - MD5计算")
void testMD5Performance() {
String testString = "Performance test string with some content to hash";
long startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
StrUtil.toMD5(testString);
}
long endTime = System.nanoTime();
long duration = endTime - startTime;
// 确保性能在合理范围内(这里只是确保能完成,具体时间取决于硬件)
assertTrue(duration > 0, "MD5计算应该花费一些时间");
// 可以添加更具体的性能断言,但要考虑不同硬件的差异
System.out.println("MD5 10000次计算耗时: " + duration / 1_000_000 + "ms");
}
@Test
@DisplayName("测试边界值")
void testBoundaryValues() {
// 测试空字符串
String emptyResult = StrUtil.toMD5("");
assertNotNull(emptyResult);
assertEquals(32, emptyResult.length());
// 测试单字符
String singleCharResult = StrUtil.toMD5("a");
assertNotNull(singleCharResult);
assertEquals(32, singleCharResult.length());
// 测试特殊字符
String specialCharResult = StrUtil.toMD5("!@#$%^&*()");
assertNotNull(specialCharResult);
assertEquals(32, specialCharResult.length());
}
}