Files
crawler-plugin/backend-java/src/test/java/com/nanri/aiimage/config/LogRedactionTest.java
T

125 lines
4.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.nanri.aiimage.config;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* task-190:日志脱敏契约(spec 11 §2)。
* Token 部分脱敏规则单一来源(StructuredLog.maskTokenImageVideo 已委托);结构化字段值
* 消毒防日志注入;重试守卫的 URL(去 query)/异常消息体不入日志,保证敏感原文不落盘。
*/
class LogRedactionTest {
@Test
void shortTokenFullyMasked() {
assertEquals("***", StructuredLog.maskToken("abc123"));
}
@Test
void longTokenPartiallyMaskedNoOriginal() {
String original = "abcdefghij-ABCDEFGHIJ-secret-token-000";
String masked = StructuredLog.maskToken(original);
assertEquals(original.substring(0, 6) + "***" + original.substring(original.length() - 4), masked);
assertFalse(masked.contains(original), "脱敏结果不应包含原始 token");
}
@Test
void nullBlankTokenMaskedEmpty() {
assertEquals("", StructuredLog.maskToken(null));
assertEquals("", StructuredLog.maskToken(" "));
}
@Test
void maskingDeterministic() {
String token = "abcdefghijk-1234567890";
assertEquals(StructuredLog.maskToken(token), StructuredLog.maskToken(token));
}
@Test
void structuredFieldStripsNewlineNoLogInjection() {
String value = StructuredLog.field("正常前缀\r\nInject: stolen; more");
assertFalse(value.contains("\r"), "字段值不应含回车");
assertFalse(value.contains("\n"), "字段值不应含换行,防止日志注入");
}
@Test
void guardStripsTokenFromUrlQueryInLog() throws Exception {
IdempotentRetryGuard guard = new IdempotentRetryGuard(fastProps());
ListAppender<ILoggingEvent> captured = attach();
try {
AtomicInteger n = new AtomicInteger();
guard.execute("POST", true, "brand", "http://example/x?token=SECRET-QUERY-999", () -> {
if (n.incrementAndGet() < 2) {
throw new IOException("连接中断");
}
return "ok";
});
String text = join(captured);
assertFalse(text.contains("SECRET-QUERY-999"), "URL query 中的 token 不应入日志: " + text);
} finally {
detach(captured);
}
}
@Test
void guardExceptionMessageNotLogged() throws Exception {
IdempotentRetryGuard guard = new IdempotentRetryGuard(fastProps());
ListAppender<ILoggingEvent> captured = attach();
try {
AtomicInteger n = new AtomicInteger();
guard.execute("POST", true, "brand", "http://example/x", () -> {
if (n.incrementAndGet() < 2) {
throw new IOException("响应: Bearer secret-token-abc-9999");
}
return "ok";
});
String text = join(captured);
assertFalse(text.contains("secret-token-abc-9999"), "异常消息体不应入日志: " + text);
assertFalse(text.contains("Bearer"), "鉴权字样不应入日志");
} finally {
detach(captured);
}
}
@Test
void normalFieldsKeptWhileSensitiveMasked() {
// 普通字段原样保留;敏感字段走 maskToken 后不再含原文
assertTrue(StructuredLog.format(Map.of("taskId", 7L)).contains("taskId=7"), "普通字段应保留");
String token = "normal-token-value-1234567890";
assertFalse(StructuredLog.maskToken(token).equals(token), "maskToken 不应原样返回 token");
}
private static HttpClientProperties fastProps() {
HttpClientProperties p = new HttpClientProperties();
p.setBaseRetryDelayMillis(1);
return p;
}
private static ListAppender<ILoggingEvent> attach() {
Logger guardLogger = (Logger) LoggerFactory.getLogger(IdempotentRetryGuard.class);
ListAppender<ILoggingEvent> appender = new ListAppender<>();
appender.start();
guardLogger.addAppender(appender);
return appender;
}
private static void detach(ListAppender<ILoggingEvent> appender) {
((Logger) LoggerFactory.getLogger(IdempotentRetryGuard.class)).detachAppender(appender);
}
private static String join(ListAppender<ILoggingEvent> appender) {
return String.join("\n", appender.list.stream().map(ILoggingEvent::getFormattedMessage).toList());
}
}