101 lines
3.6 KiB
Java
101 lines
3.6 KiB
Java
package com.accounting.service;
|
||
|
||
import org.junit.jupiter.api.DisplayName;
|
||
import org.junit.jupiter.api.Test;
|
||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||
|
||
/**
|
||
* 脱敏器的单测 —— 这是整个 AI 链路里最不能出错的一个类,
|
||
* 用例按「用户笔记里真实出现过的形态」来写。
|
||
*/
|
||
class SensitiveDataMaskerTest {
|
||
|
||
private final SensitiveDataMasker masker = new SensitiveDataMasker();
|
||
|
||
@Test
|
||
@DisplayName("IPv4 地址被脱敏")
|
||
void masksIpv4() {
|
||
String out = masker.mask("服务器 IP 是 155.103.159.109,SSH 端口 31022");
|
||
assertFalse(out.contains("155.103.159.109"));
|
||
assertTrue(out.contains("[REDACTED_IP_"));
|
||
assertTrue(out.contains("31022")); // 端口不算敏感,保留
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("password 键值对:键名保留,值被替换")
|
||
void masksPasswordKv() {
|
||
String out = masker.mask("MySQL root 密码:mysql_jP65Fc,登录后记得改");
|
||
assertFalse(out.contains("mysql_jP65Fc"));
|
||
assertTrue(out.contains("密码:[REDACTED_SECRET_"));
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("password= 等号形式同样生效")
|
||
void masksPasswordWithEquals() {
|
||
String out = masker.mask("jdbc:url?user=root&password=mySecret123");
|
||
assertFalse(out.contains("mySecret123"));
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("PEM 私钥整块被脱敏")
|
||
void masksPrivateKeyBlock() {
|
||
String key = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
||
+ "b3BlbnNzaC1rZXktdjEAAAAA\n"
|
||
+ "b3BlbnNzaC1rZXktdjEAAAAA\n"
|
||
+ "-----END OPENSSH PRIVATE KEY-----";
|
||
String out = masker.mask("私钥如下:\n" + key + "\n记得保管好");
|
||
assertFalse(out.contains("b3BlbnNzaC1rZXktdjEAAAAA"));
|
||
assertTrue(out.contains("[REDACTED_KEY_1]"));
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("无键名的 AK/SK 长随机串被脱敏")
|
||
void masksBareAccessToken() {
|
||
String out = masker.mask("access key 是 LTAI5tDCJuB9YgLx4KeJwc9C 备用");
|
||
assertFalse(out.contains("LTAI5tDCJuB9YgLx4KeJwc9C"));
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("同一个值在多次出现时映射到同一个占位符")
|
||
void keepsPlaceholderConsistent() {
|
||
String out = masker.mask("密码:abc12345,重复一下密码:abc12345");
|
||
String first = out.substring(out.indexOf("[REDACTED_"));
|
||
assertEquals(1, countOccurrences(out, "[REDACTED_SECRET_1]"), "同一值应得到同一占位符: " + out);
|
||
assertFalse(out.contains(first + "["));
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("普通内容不误伤:中文、短数字、文件名都保留")
|
||
void keepsNormalContent() {
|
||
String text = "笔记在 migration_add_note.sql,共 3 篇,端口 31022,金额 ¥134.00";
|
||
assertEquals(text, masker.mask(text));
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("「密码:无」这种非秘密不被误伤")
|
||
void keepsShortNonSecretValues() {
|
||
String out = masker.mask("密码:无");
|
||
assertTrue(out.contains("密码:无"));
|
||
}
|
||
|
||
@Test
|
||
@DisplayName("null 和空串原样返回")
|
||
void handlesNullAndEmpty() {
|
||
assertEquals(null, masker.mask(null));
|
||
assertEquals("", masker.mask(""));
|
||
}
|
||
|
||
private int countOccurrences(String text, String target) {
|
||
int count = 0;
|
||
int idx = 0;
|
||
while ((idx = text.indexOf(target, idx)) >= 0) {
|
||
count++;
|
||
idx += target.length();
|
||
}
|
||
return count;
|
||
}
|
||
}
|