feat: V1.1 - 问题一单元测试复现
This commit is contained in:
139
src/test/java/com/codechecker/cache/RecordingTodoReplaceScenarioTest.java
vendored
Normal file
139
src/test/java/com/codechecker/cache/RecordingTodoReplaceScenarioTest.java
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
package com.codechecker.cache;
|
||||
|
||||
import com.codechecker.cache.detector.RedisWritePointDetector;
|
||||
import com.codechecker.cache.detector.WritePoint;
|
||||
import com.codechecker.cache.diff.ChangeType;
|
||||
import com.codechecker.cache.diff.SchemaChange;
|
||||
import com.codechecker.cache.report.CheckReport;
|
||||
import com.codechecker.cache.report.KeyStructureChange;
|
||||
import com.codechecker.cache.report.ReportBuilder;
|
||||
import com.codechecker.cache.schema.JavaSchemaExtractor;
|
||||
import com.codechecker.cache.schema.SkeletonJsonRenderer;
|
||||
import com.codechecker.cache.schema.SourceIndex;
|
||||
import com.codechecker.cache.schema.TypeSchema;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* 复现业务仓实跑误报(jnpf-java-cloud / RecordingTodoDomainServiceImpl#replaceText):
|
||||
* <pre>
|
||||
* - Key --> recording:todo:replace:*:*
|
||||
* 类型: <未解析>
|
||||
* value 新增为: “{}”
|
||||
* - Key --> recording:todo:replace:title:*:*
|
||||
* 类型: <未解析>
|
||||
* value 新增为: “{}”
|
||||
* </pre>
|
||||
* 根因:{@code stringRedisTemplate.opsForValue().set(key, String变量)} 被 W04 命中,
|
||||
* String 无法展开 Schema → 空骨架 + WRITE_POINT_ADDED。
|
||||
*/
|
||||
class RecordingTodoReplaceScenarioTest {
|
||||
|
||||
private static final Set<String> PATTERNS = new HashSet<>(Arrays.asList("W01", "W02", "W03", "W04", "W05"));
|
||||
|
||||
@Test
|
||||
void reproducesUnresolvedStringCacheFalsePositiveLikeWeCom() {
|
||||
String constants = TestSupport.fixture("fixtures/recording-todo/RecordingTodoConstants.txt");
|
||||
String service = TestSupport.fixture("fixtures/recording-todo/RecordingTodoDomainServiceImpl.txt");
|
||||
|
||||
SourceIndex index = new SourceIndex();
|
||||
index.addSource(constants);
|
||||
index.addSource(service);
|
||||
|
||||
List<WritePoint> wps = new RedisWritePointDetector(index, PATTERNS).detect("RecordingTodoDomainServiceImpl.java", service);
|
||||
wps = wps.stream().sorted(Comparator.comparing(WritePoint::getLineNumber)).collect(Collectors.toList());
|
||||
|
||||
assertEquals(2, wps.size(), "应检出 content / title 两处 set");
|
||||
|
||||
WritePoint contentWp = wps.get(0);
|
||||
WritePoint titleWp = wps.get(1);
|
||||
|
||||
// --- 检测层:与实跑一致 ---
|
||||
assertEquals("W04", contentWp.getPattern());
|
||||
assertEquals("W04", titleWp.getPattern());
|
||||
assertEquals("recording:todo:replace:*:*", contentWp.getResolvedKeyPattern());
|
||||
assertEquals("recording:todo:replace:title:*:*", titleWp.getResolvedKeyPattern());
|
||||
assertNull(contentWp.getResolvedValueType(), "String 不在本仓 SourceIndex,FQN 应为 null");
|
||||
assertNull(titleWp.getResolvedValueType());
|
||||
assertTrue(contentWp.getEnclosingMethod().contains("replaceText") || "replaceText".equals(contentWp.getEnclosingMethod()));
|
||||
assertTrue(contentWp.getEnclosingClass().endsWith("RecordingTodoDomainServiceImpl"));
|
||||
|
||||
// --- Schema:空结构 → "{}" ---
|
||||
JavaSchemaExtractor extractor = new JavaSchemaExtractor(index, 8);
|
||||
SkeletonJsonRenderer renderer = new SkeletonJsonRenderer();
|
||||
TypeSchema contentSchema = extractor.extract(contentWp.getResolvedValueType(), contentWp.isRootArray());
|
||||
TypeSchema titleSchema = extractor.extract(titleWp.getResolvedValueType(), titleWp.isRootArray());
|
||||
assertTrue(contentSchema.isEmpty());
|
||||
assertTrue(titleSchema.isEmpty());
|
||||
assertEquals("{}", renderer.render(contentSchema));
|
||||
assertEquals("{}", renderer.render(titleSchema));
|
||||
|
||||
// --- 报告层:复现企微「value 新增为 / 类型未解析」 ---
|
||||
CheckReport report = buildAddedWritePointReport(contentWp, titleWp, extractor, renderer);
|
||||
String md = new ReportBuilder("【序列化结构变更】").toMarkdown(report);
|
||||
|
||||
assertTrue(md.contains("recording:todo:replace:*:*") || md.contains("recording:todo:replace:*:*"), "应包含 content key(企微转义后可为全角*),实际:\n" + md);
|
||||
assertTrue(md.contains("recording:todo:replace:title:*:*") || md.contains("recording:todo:replace:title:*:*"), "应包含 title key,实际:\n" + md);
|
||||
assertTrue(md.contains("<未解析>"), "类型应展示为 <未解析>,实际:\n" + md);
|
||||
assertTrue(md.contains("value 新增为"), "应走新增写入文案,实际:\n" + md);
|
||||
assertTrue(md.contains("“{}”") || md.contains("\"{}\"") || md.contains("{}"), "骨架应为空对象 {},实际:\n" + md);
|
||||
assertTrue(md.contains("RecordingTodoDomainServiceImpl#replaceText"), "位置应指向 replaceText,实际:\n" + md);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟 SchemaCheckAnalyzer 对「文件新增写入点」的聚合与展示路径(无 git)。
|
||||
*/
|
||||
private static CheckReport buildAddedWritePointReport(WritePoint contentWp, WritePoint titleWp,
|
||||
JavaSchemaExtractor extractor,
|
||||
SkeletonJsonRenderer renderer) {
|
||||
CheckReport report = new CheckReport();
|
||||
report.setRepository("jnpf-java-cloud");
|
||||
report.setBranch("patrol/dev_patrol_2.0");
|
||||
report.setOldSha("adbb15c6");
|
||||
report.setNewSha("6ddc72dc");
|
||||
report.setModifier("xgl");
|
||||
report.setModifyTime("2026-07-29 17:57:02");
|
||||
|
||||
for (WritePoint wp : Arrays.asList(contentWp, titleWp)) {
|
||||
SchemaChange added = new SchemaChange(ChangeType.WRITE_POINT_ADDED);
|
||||
added.setKeyPattern(wp.getResolvedKeyPattern());
|
||||
added.setKeyExpression(wp.getKeyExpression());
|
||||
added.setWriteLocation(wp.location());
|
||||
added.setValueType(displayType(wp));
|
||||
added.setMessage("新增缓存写入点,value 类型: " + displayType(wp));
|
||||
report.getChanges().add(added);
|
||||
|
||||
TypeSchema schema = extractor.extract(wp.getResolvedValueType(), wp.isRootArray());
|
||||
KeyStructureChange kc = new KeyStructureChange();
|
||||
kc.setKeyPattern(wp.getResolvedKeyPattern());
|
||||
kc.setKeyExpression(wp.getKeyExpression());
|
||||
kc.setWriteLocation(wp.location());
|
||||
kc.setValueType(displayType(wp));
|
||||
kc.setKeyUnresolved(false);
|
||||
kc.setOldSkeletonJson("");
|
||||
kc.setNewSkeletonJson(renderer.render(schema, Collections.emptySet(), SkeletonJsonRenderer.DEFAULT_MAX_LEN));
|
||||
kc.getFieldDetails().add(added);
|
||||
report.getKeyChanges().add(kc);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
private static String displayType(WritePoint wp) {
|
||||
String fqn = wp.getResolvedValueType();
|
||||
if (fqn == null) {
|
||||
return "<未解析>";
|
||||
}
|
||||
return fqn.contains(".") ? fqn.substring(fqn.lastIndexOf('.') + 1) : fqn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.fantaibao.iot.domain.constant;
|
||||
|
||||
/**
|
||||
* 精简自业务仓 RecordingTodoConstants(复现非序列化 String 缓存误报)。
|
||||
*/
|
||||
public final class RecordingTodoConstants {
|
||||
|
||||
private RecordingTodoConstants() {
|
||||
}
|
||||
|
||||
/** 待办内容替换原文缓存的 Redis key 前缀 */
|
||||
public static final String REDIS_KEY_PREFIX = "recording:todo:replace:";
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.fantaibao.iot.infrastructure.service.recording;
|
||||
|
||||
import com.fantaibao.iot.domain.constant.RecordingTodoConstants;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 精简自业务仓 RecordingTodoDomainServiceImpl#replaceText:
|
||||
* 将待办原文 content/title(纯 String)写入 StringRedisTemplate,无 JSON 序列化。
|
||||
* 对应企微误报:类型 <未解析>、value 新增为 “{}”。
|
||||
*/
|
||||
public class RecordingTodoDomainServiceImpl {
|
||||
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public void replaceText(String tenantId, String todoId, String originalContent, String originalTitle) {
|
||||
String contentRedisKey = RecordingTodoConstants.REDIS_KEY_PREFIX + todoId + ":" + tenantId;
|
||||
stringRedisTemplate.opsForValue().set(contentRedisKey, originalContent);
|
||||
|
||||
String titleRedisKey = RecordingTodoConstants.REDIS_KEY_PREFIX + "title:" + todoId + ":" + tenantId;
|
||||
stringRedisTemplate.opsForValue().set(titleRedisKey, originalTitle);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user