diff --git a/.gitignore b/.gitignore
index 9eda3ce..4b94efc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,6 @@ build/
.vscode/
**/**/.flattened-pom.xml
./.flattened-pom.xml
+
+### Reasonix ###
+.reasonix/
diff --git a/pom.xml b/pom.xml
index 80b120e..2a7c98b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,13 @@
provided
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.2
+ test
+
+
@@ -82,6 +89,12 @@
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+
maven-jar-plugin
${maven.jar.plugin.version}
diff --git a/src/main/java/com/openquartz/javaobjdiff/DiffComparable.java b/src/main/java/com/openquartz/javaobjdiff/DiffComparable.java
index 47eb11e..6360aa3 100644
--- a/src/main/java/com/openquartz/javaobjdiff/DiffComparable.java
+++ b/src/main/java/com/openquartz/javaobjdiff/DiffComparable.java
@@ -8,11 +8,11 @@
public interface DiffComparable {
/**
- * 是否不同
+ * 是否相同
*
* @param source source
* @param target target
- * @return 是否不同,false: 相同, true: 不同
+ * @return 是否相同, true: 相同, false: 不同
*/
boolean diff(Object source, Object target);
}
diff --git a/src/main/java/com/openquartz/javaobjdiff/formatter/DateTimeDiffFormatter.java b/src/main/java/com/openquartz/javaobjdiff/formatter/DateTimeDiffFormatter.java
index abef577..c61cec8 100644
--- a/src/main/java/com/openquartz/javaobjdiff/formatter/DateTimeDiffFormatter.java
+++ b/src/main/java/com/openquartz/javaobjdiff/formatter/DateTimeDiffFormatter.java
@@ -1,7 +1,6 @@
package com.openquartz.javaobjdiff.formatter;
import com.openquartz.javaobjdiff.DiffFormatter;
-import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
@@ -34,7 +33,7 @@ public Object format(Object value, String pattern) {
}
if (value instanceof TemporalAccessor) {
- return DateTimeFormatter.ofPattern(pattern).format((LocalDateTime) value);
+ return DateTimeFormatter.ofPattern(pattern).format((TemporalAccessor) value);
}
throw new IllegalArgumentException("Unsupported type: " + value.getClass());
diff --git a/src/main/java/com/openquartz/javaobjdiff/formatter/datamask/EmailDataMaskFormatter.java b/src/main/java/com/openquartz/javaobjdiff/formatter/datamask/EmailDataMaskFormatter.java
index 2d37867..e12fe2a 100644
--- a/src/main/java/com/openquartz/javaobjdiff/formatter/datamask/EmailDataMaskFormatter.java
+++ b/src/main/java/com/openquartz/javaobjdiff/formatter/datamask/EmailDataMaskFormatter.java
@@ -34,6 +34,10 @@ public static String desensitizeEmail(String email) {
}
int atIndex = email.indexOf("@");
+ if (atIndex == 0) {
+ return email;
+ }
+
String prefix = email.substring(0, atIndex);
int lengthToReplace = prefix.length() - 1;
@@ -41,7 +45,7 @@ public static String desensitizeEmail(String email) {
.limit(lengthToReplace)
.collect(Collectors.joining());
- return replacement + prefix.charAt(0) + "@" + email.substring(atIndex);
+ return replacement + prefix.charAt(0) + email.substring(atIndex);
}
}
diff --git a/src/main/java/com/openquartz/javaobjdiff/formatter/datamask/MobileDataMaskFormatter.java b/src/main/java/com/openquartz/javaobjdiff/formatter/datamask/MobileDataMaskFormatter.java
index 159a87c..96bb4b1 100644
--- a/src/main/java/com/openquartz/javaobjdiff/formatter/datamask/MobileDataMaskFormatter.java
+++ b/src/main/java/com/openquartz/javaobjdiff/formatter/datamask/MobileDataMaskFormatter.java
@@ -31,6 +31,9 @@ public Object format(Object value, String pattern) {
}
public static String desensitizePhoneNumber(String phoneNumber) {
+ if (phoneNumber == null) {
+ return null;
+ }
if (MOBILE_PATTERN.matcher(phoneNumber).matches()) {
// 手机号码脱敏处理
return phoneNumber.substring(0, 3) + "****" + phoneNumber.substring(7);
diff --git a/src/test/java/com/openquartz/javaobjdiff/DiffUtilsTest.java b/src/test/java/com/openquartz/javaobjdiff/DiffUtilsTest.java
new file mode 100644
index 0000000..253e5c8
--- /dev/null
+++ b/src/test/java/com/openquartz/javaobjdiff/DiffUtilsTest.java
@@ -0,0 +1,277 @@
+package com.openquartz.javaobjdiff;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.openquartz.javaobjdiff.test.bean.Address;
+import com.openquartz.javaobjdiff.test.bean.Packet;
+import com.openquartz.javaobjdiff.test.bean.Person;
+import com.openquartz.javaobjdiff.test.bean.Sex;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * DiffUtils 核心功能单元测试
+ *
+ * @author svnee
+ */
+public class DiffUtilsTest {
+
+ private Person person(String name, Integer age, Sex sex, Address address) {
+ Person person = new Person();
+ person.setName(name);
+ person.setAge(age);
+ person.setSex(sex);
+ person.setAddress(address);
+ return person;
+ }
+
+ private Address address(String country, String city) {
+ Address address = new Address();
+ address.setCountry(country);
+ address.setCity(city);
+ return address;
+ }
+
+ @Test
+ public void testSameReferenceNoDiff() {
+ Person person = person("jack", 10, Sex.MAN, null);
+ DiffResult result = DiffUtils.diffResult(person, person);
+ assertFalse(result.isDiff());
+ assertEquals(0, result.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testEqualFieldsNoDiff() {
+ Person p1 = person("jack", 10, Sex.MAN, address("cn", "shanghai"));
+ Person p2 = person("jack", 10, Sex.MAN, address("cn", "shanghai"));
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertFalse(result.isDiff());
+ assertEquals(0, result.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testSimpleFieldDiff() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ Person p2 = person("tom", 10, Sex.MAN, null);
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertTrue(result.isDiff());
+ assertEquals(1, result.getNumberOfDiffs());
+ Diff> diff = result.getDiffs().get(0);
+ assertEquals("name", diff.getFieldName());
+ assertEquals("jack", diff.getLeft());
+ assertEquals("tom", diff.getRight());
+ }
+
+ @Test
+ public void testDiffIgnoreAnnotation() {
+ // age 字段标有 @DiffIgnore,两边 age 不同也不应产生 diff
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ Person p2 = person("jack", 11, Sex.MAN, null);
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertFalse(result.isDiff());
+ assertEquals(0, result.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testExcludeField() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ Person p2 = person("tom", 10, Sex.MAN, null);
+ // 注意:直接传 diffResult(p1, p2, "name") 会因 DiffUtils 的重载歧义无法编译,这里用显式数组
+ DiffResult result = DiffUtils.diffResult(p1, p2, new String[]{"name"});
+ assertEquals(0, result.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testExcludeMultipleFields() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ p1.setMobile("17000000056");
+ p1.setEmail("wangtest1111@126.com");
+ Person p2 = person("tom", 10, Sex.MAN, null);
+ p2.setMobile("17130000056");
+ p2.setEmail("litest2211@126.com");
+ DiffResult result = DiffUtils.diffResult(p1, p2, new String[]{"name", "mobile", "email"});
+ assertEquals(0, result.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testDiffAlias() {
+ Person p1 = person("jack", 10, Sex.MAN, address("tangzhen", "shanghai"));
+ Person p2 = person("jack", 10, Sex.MAN, address("tangzhen", "beijing"));
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertEquals(1, result.getNumberOfDiffs());
+ Diff> diff = result.getDiffs().get(0);
+ // Address.city 标有 @DiffAlias(alias = "城市")
+ assertEquals("城市", diff.getActualName());
+ assertEquals("shanghai", diff.getLeft());
+ assertEquals("beijing", diff.getRight());
+ }
+
+ @Test
+ public void testDateFormat() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ p1.setBirthDate(LocalDateTime.of(2026, 8, 12, 10, 30));
+ Person p2 = person("jack", 10, Sex.MAN, null);
+ p2.setBirthDate(LocalDateTime.of(2026, 8, 2, 10, 30));
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertEquals(1, result.getNumberOfDiffs());
+ Diff> diff = result.getDiffs().get(0);
+ assertEquals("birthDate", diff.getFieldName());
+ // birthDate 标有 @DiffFormat(pattern = "yyyy-MM-dd")
+ assertEquals("2026-08-12", diff.getFormatValue(diff.getLeft()));
+ assertEquals("2026-08-02", diff.getFormatValue(diff.getRight()));
+ }
+
+ @Test
+ public void testCustomEnumComparator() {
+ // sex 字段标有 @DiffCompare(using = SelfEnumComparator.class)
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ Person p2 = person("jack", 10, Sex.WOMAN, null);
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertEquals(1, result.getNumberOfDiffs());
+ assertEquals("sex", result.getDiffs().get(0).getFieldName());
+ }
+
+ @Test
+ public void testCustomEnumComparatorSameValueNoDiff() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ Person p2 = person("jack", 10, Sex.MAN, null);
+ assertEquals(0, DiffUtils.diffResult(p1, p2).getNumberOfDiffs());
+ }
+
+ @Test
+ public void testNestedDiffBean() {
+ Person p1 = person("jack", 10, Sex.MAN, address("tangzhen", "shanghai"));
+ Person p2 = person("jack", 10, Sex.MAN, address("daxing", "shanghai"));
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertEquals(1, result.getNumberOfDiffs());
+ assertEquals("address.country", result.getDiffs().get(0).getFieldName());
+ }
+
+ @Test
+ public void testNestedDiffBeanWithNullSide() {
+ // 一边为 null,应逐字段比较出差异
+ Person p1 = person("jack", 10, Sex.MAN, address("tangzhen", "shanghai"));
+ Person p2 = person("jack", 10, Sex.MAN, null);
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertTrue(result.isDiff());
+ assertTrue(result.getNumberOfDiffs() > 0);
+ }
+
+ @Test
+ public void testBeanCollectionCustomType() {
+ // 集合元素为自定义类型 Packet,元素内 money 使用 BigDecimalEffectiveDiffComparator
+ // 1.0 与 1.00 视为相等,应无差异
+ Packet packet1 = new Packet();
+ packet1.setName("LV");
+ packet1.setMoney(new BigDecimal("1.0"));
+ Packet packet2 = new Packet();
+ packet2.setName("LV");
+ packet2.setMoney(new BigDecimal("1.00"));
+
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ p1.setPacketList(Arrays.asList(packet1));
+ Person p2 = person("jack", 10, Sex.MAN, null);
+ p2.setPacketList(Arrays.asList(packet2));
+
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertEquals(0, result.getNumberOfDiffs());
+ }
+
+ @Test
+ public void testBeanCollectionCustomTypeDiff() {
+ Packet packet1 = new Packet();
+ packet1.setName("LV");
+ packet1.setMoney(new BigDecimal("1.0"));
+ Packet packet2 = new Packet();
+ packet2.setName("PT");
+ packet2.setMoney(new BigDecimal("1.0"));
+
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ p1.setPacketList(Arrays.asList(packet1));
+ Person p2 = person("jack", 10, Sex.MAN, null);
+ p2.setPacketList(Arrays.asList(packet2));
+
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertEquals(1, result.getNumberOfDiffs());
+ assertEquals("packetList[0].name", result.getDiffs().get(0).getFieldName());
+ assertEquals("LV", result.getDiffs().get(0).getLeft());
+ assertEquals("PT", result.getDiffs().get(0).getRight());
+ }
+
+ @Test
+ public void testBeanCollectionCustomTypeIndexAligned() {
+ // 集合长度不同时按索引对齐比较,多出的元素与 null 比较
+ Packet packet1 = new Packet();
+ packet1.setName("LV");
+ Packet packet2 = new Packet();
+ packet2.setName("PT");
+
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ p1.setPacketList(Arrays.asList(packet1, packet2));
+ Person p2 = person("jack", 10, Sex.MAN, null);
+ p2.setPacketList(Arrays.asList(packet1));
+
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertTrue(result.isDiff());
+ assertTrue(result.getDiffs().stream().anyMatch(d -> d.getFieldName().startsWith("packetList[1]")));
+ }
+
+ @Test
+ public void testBeanCollectionJdkTypeSame() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ p1.setFriendIdList(Arrays.asList(1, 2, 3));
+ Person p2 = person("jack", 10, Sex.MAN, null);
+ p2.setFriendIdList(Arrays.asList(1, 2, 3));
+ assertEquals(0, DiffUtils.diffResult(p1, p2).getNumberOfDiffs());
+ }
+
+ @Test
+ public void testBeanCollectionJdkTypeDiff() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ p1.setFriendIdList(Arrays.asList(1, 2, 3));
+ Person p2 = person("jack", 10, Sex.MAN, null);
+ p2.setFriendIdList(Arrays.asList(1, 2, 4));
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+ assertEquals(1, result.getNumberOfDiffs());
+ assertEquals("friendIdList", result.getDiffs().get(0).getFieldName());
+ }
+
+ @Test
+ public void testToStringContainsArrow() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ Person p2 = person("tom", 10, Sex.MAN, null);
+ String diff = DiffUtils.diff(p1, p2);
+ assertTrue(diff.contains("->"));
+ assertTrue(diff.contains("jack"));
+ assertTrue(diff.contains("tom"));
+ }
+
+ @Test
+ public void testDiffResultApi() {
+ Person p1 = person("jack", 10, Sex.MAN, null);
+ Person p2 = person("tom", 10, Sex.MAN, null);
+ DiffResult result = DiffUtils.diffResult(p1, p2);
+
+ assertSame(p1, result.getLeft());
+ assertSame(p2, result.getRight());
+ assertTrue(result.getNumberOfDiffs() > 0);
+ assertTrue(result.isDiff());
+
+ // getDiffs() 返回不可修改列表
+ List> diffs = result.getDiffs();
+ assertThrows(UnsupportedOperationException.class, () -> diffs.add(null));
+
+ // 支持迭代
+ Iterator> iterator = result.iterator();
+ assertTrue(iterator.hasNext());
+ assertTrue(iterator.next().getFieldName() != null);
+ }
+}
diff --git a/src/test/java/com/openquartz/javaobjdiff/comparator/BigDecimalEffectiveDiffComparatorTest.java b/src/test/java/com/openquartz/javaobjdiff/comparator/BigDecimalEffectiveDiffComparatorTest.java
new file mode 100644
index 0000000..343df7f
--- /dev/null
+++ b/src/test/java/com/openquartz/javaobjdiff/comparator/BigDecimalEffectiveDiffComparatorTest.java
@@ -0,0 +1,51 @@
+package com.openquartz.javaobjdiff.comparator;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.math.BigDecimal;
+import org.junit.jupiter.api.Test;
+
+/**
+ * BigDecimalEffectiveDiffComparator 单元测试
+ *
+ * @author svnee
+ */
+public class BigDecimalEffectiveDiffComparatorTest {
+
+ private final BigDecimalEffectiveDiffComparator comparator = new BigDecimalEffectiveDiffComparator();
+
+ @Test
+ public void testSameReference() {
+ BigDecimal value = new BigDecimal("1.0");
+ assertTrue(comparator.diff(value, value));
+ }
+
+ @Test
+ public void testEqualEffectiveValue() {
+ // 忽略尾零后值相等
+ assertTrue(comparator.diff(new BigDecimal("1.0"), new BigDecimal("1.00")));
+ assertTrue(comparator.diff(new BigDecimal("2.000"), new BigDecimal("2")));
+ assertTrue(comparator.diff(new BigDecimal("0"), new BigDecimal("0.000")));
+ }
+
+ @Test
+ public void testDifferentValue() {
+ assertFalse(comparator.diff(new BigDecimal("1.0"), new BigDecimal("2.0")));
+ assertFalse(comparator.diff(new BigDecimal("1.01"), new BigDecimal("1.02")));
+ }
+
+ @Test
+ public void testNullHandling() {
+ assertTrue(comparator.diff(null, null));
+ assertFalse(comparator.diff(null, new BigDecimal("1.0")));
+ assertFalse(comparator.diff(new BigDecimal("1.0"), null));
+ }
+
+ @Test
+ public void testNonBigDecimalType() {
+ assertThrows(IllegalArgumentException.class, () -> comparator.diff("1.0", new BigDecimal("1.0")));
+ assertThrows(IllegalArgumentException.class, () -> comparator.diff(new BigDecimal("1.0"), 1.0d));
+ }
+}
diff --git a/src/test/java/com/openquartz/javaobjdiff/formatter/DateTimeDiffFormatterTest.java b/src/test/java/com/openquartz/javaobjdiff/formatter/DateTimeDiffFormatterTest.java
new file mode 100644
index 0000000..41cad28
--- /dev/null
+++ b/src/test/java/com/openquartz/javaobjdiff/formatter/DateTimeDiffFormatterTest.java
@@ -0,0 +1,71 @@
+package com.openquartz.javaobjdiff.formatter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Calendar;
+import java.util.Date;
+import org.junit.jupiter.api.Test;
+
+/**
+ * DateTimeDiffFormatter 单元测试
+ *
+ * @author svnee
+ */
+public class DateTimeDiffFormatterTest {
+
+ private final DateTimeDiffFormatter formatter = new DateTimeDiffFormatter();
+
+ private Date date(int year, int month, int day, int hour, int minute, int second) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.clear();
+ calendar.set(year, month - 1, day, hour, minute, second);
+ return calendar.getTime();
+ }
+
+ @Test
+ public void testFormatDate() {
+ Date date = date(2026, 8, 12, 10, 30, 45);
+ assertEquals("2026-08-12 10:30:45", formatter.format(date, null));
+ }
+
+ @Test
+ public void testFormatDateWithPattern() {
+ Date date = date(2026, 8, 12, 10, 30, 45);
+ assertEquals("2026-08-12", formatter.format(date, "yyyy-MM-dd"));
+ }
+
+ @Test
+ public void testFormatLocalDateTime() {
+ LocalDateTime value = LocalDateTime.of(2026, 8, 12, 10, 30, 45);
+ assertEquals("2026-08-12 10:30:45", formatter.format(value, null));
+ assertEquals("2026-08-12", formatter.format(value, "yyyy-MM-dd"));
+ }
+
+ @Test
+ public void testFormatLocalDate() {
+ // 回归:修复前 TemporalAccessor 分支强制 cast 为 LocalDateTime,LocalDate 会抛 ClassCastException
+ LocalDate value = LocalDate.of(2026, 8, 12);
+ assertEquals("2026-08-12", formatter.format(value, "yyyy-MM-dd"));
+ }
+
+ @Test
+ public void testFormatLocalTime() {
+ LocalTime value = LocalTime.of(10, 30, 45);
+ assertEquals("10:30:45", formatter.format(value, "HH:mm:ss"));
+ }
+
+ @Test
+ public void testFormatNull() {
+ assertNull(formatter.format(null, "yyyy-MM-dd"));
+ }
+
+ @Test
+ public void testUnsupportedType() {
+ assertThrows(IllegalArgumentException.class, () -> formatter.format(123, "yyyy-MM-dd"));
+ }
+}
diff --git a/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/EmailDataMaskFormatterTest.java b/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/EmailDataMaskFormatterTest.java
new file mode 100644
index 0000000..c6b094f
--- /dev/null
+++ b/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/EmailDataMaskFormatterTest.java
@@ -0,0 +1,56 @@
+package com.openquartz.javaobjdiff.formatter.datamask;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * EmailDataMaskFormatter 单元测试
+ *
+ * @author svnee
+ */
+public class EmailDataMaskFormatterTest {
+
+ private final EmailDataMaskFormatter formatter = new EmailDataMaskFormatter();
+
+ @Test
+ public void testMaskTypicalEmail() {
+ // 回归:修复前会输出重复的 @(***********w@@126.com)
+ // "wangtest1111" 共 12 个字符,保留首字母 w,其余 11 个用 * 替代
+ String masked = EmailDataMaskFormatter.desensitizeEmail("wangtest1111@126.com");
+ assertEquals("***********w@126.com", masked);
+ }
+
+ @Test
+ public void testMaskSingleCharPrefix() {
+ // 单字符前缀无法脱敏,但不应产生重复 @
+ assertEquals("a@126.com", EmailDataMaskFormatter.desensitizeEmail("a@126.com"));
+ }
+
+ @Test
+ public void testMaskKeepsDomain() {
+ String masked = EmailDataMaskFormatter.desensitizeEmail("zhangsan@qq.com");
+ assertEquals("*******z@qq.com", masked);
+ }
+
+ @Test
+ public void testMaskStartsWithAt() {
+ // 回归:修复前 prefix 为空时 Stream.limit(-1) 抛 IllegalArgumentException
+ assertEquals("@126.com", EmailDataMaskFormatter.desensitizeEmail("@126.com"));
+ }
+
+ @Test
+ public void testBlankOrNoAtReturnAsIs() {
+ assertNull(EmailDataMaskFormatter.desensitizeEmail(null));
+ assertEquals("", EmailDataMaskFormatter.desensitizeEmail(""));
+ assertEquals(" ", EmailDataMaskFormatter.desensitizeEmail(" "));
+ assertEquals("plaintext", EmailDataMaskFormatter.desensitizeEmail("plaintext"));
+ }
+
+ @Test
+ public void testFormatUnsupportedType() {
+ assertThrows(IllegalArgumentException.class, () -> formatter.format(123, null));
+ }
+}
diff --git a/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/IdCardDataMaskFormatterTest.java b/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/IdCardDataMaskFormatterTest.java
new file mode 100644
index 0000000..ff33151
--- /dev/null
+++ b/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/IdCardDataMaskFormatterTest.java
@@ -0,0 +1,39 @@
+package com.openquartz.javaobjdiff.formatter.datamask;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * IdCardDataMaskFormatter 单元测试
+ *
+ * @author svnee
+ */
+public class IdCardDataMaskFormatterTest {
+
+ private final IdCardDataMaskFormatter formatter = new IdCardDataMaskFormatter();
+
+ @Test
+ public void testMaskIdCard() {
+ // 保留前 6 位和后 4 位,中间 8 位用 * 替代
+ assertEquals("321556********2110", IdCardDataMaskFormatter.maskIdCard("321556199301152110"));
+ }
+
+ @Test
+ public void testInvalidLengthReturnAsIs() {
+ assertEquals("123456", IdCardDataMaskFormatter.maskIdCard("123456"));
+ assertEquals("1234567890123456789", IdCardDataMaskFormatter.maskIdCard("1234567890123456789"));
+ }
+
+ @Test
+ public void testNull() {
+ assertNull(IdCardDataMaskFormatter.maskIdCard(null));
+ }
+
+ @Test
+ public void testFormatUnsupportedType() {
+ assertThrows(IllegalArgumentException.class, () -> formatter.format(123, null));
+ }
+}
diff --git a/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/MobileDataMaskFormatterTest.java b/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/MobileDataMaskFormatterTest.java
new file mode 100644
index 0000000..dc841cf
--- /dev/null
+++ b/src/test/java/com/openquartz/javaobjdiff/formatter/datamask/MobileDataMaskFormatterTest.java
@@ -0,0 +1,44 @@
+package com.openquartz.javaobjdiff.formatter.datamask;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * MobileDataMaskFormatter 单元测试
+ *
+ * @author svnee
+ */
+public class MobileDataMaskFormatterTest {
+
+ private final MobileDataMaskFormatter formatter = new MobileDataMaskFormatter();
+
+ @Test
+ public void testMaskMobile() {
+ assertEquals("170****0056", MobileDataMaskFormatter.desensitizePhoneNumber("17000000056"));
+ }
+
+ @Test
+ public void testMaskLandline() {
+ assertEquals("010-****5678", MobileDataMaskFormatter.desensitizePhoneNumber("010-12345678"));
+ assertEquals("021-****1234", MobileDataMaskFormatter.desensitizePhoneNumber("021-55661234"));
+ }
+
+ @Test
+ public void testInvalidNumberReturnAsIs() {
+ assertEquals("123", MobileDataMaskFormatter.desensitizePhoneNumber("123"));
+ assertEquals("abcdefg", MobileDataMaskFormatter.desensitizePhoneNumber("abcdefg"));
+ }
+
+ @Test
+ public void testNull() {
+ assertNull(MobileDataMaskFormatter.desensitizePhoneNumber(null));
+ }
+
+ @Test
+ public void testFormatUnsupportedType() {
+ assertThrows(IllegalArgumentException.class, () -> formatter.format(123L, null));
+ }
+}