feat: 一阶段

This commit is contained in:
2026-07-15 16:48:46 +08:00
parent 14f9f1fce7
commit d884aa36ff
16 changed files with 789 additions and 37 deletions

View File

@@ -0,0 +1,123 @@
package com.codechecker.cache;
import com.codechecker.cache.detector.MqWritePointDetector;
import com.codechecker.cache.detector.WritePoint;
import com.codechecker.cache.diff.ChangeType;
import com.codechecker.cache.diff.SchemaChange;
import com.codechecker.cache.diff.SchemaDiffer;
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.SourceIndex;
import com.codechecker.cache.schema.TypeSchema;
import org.junit.jupiter.api.Test;
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.assertTrue;
class MqScenarioTest {
@Test
void rocketMqFieldRemovalProducesTopicDiff() {
String constants = TestSupport.fixture("fixtures/mq/rocket-wallet/CapitalMqConstants.txt");
String oldReq = TestSupport.fixture("fixtures/mq/rocket-wallet/WalletDeductReqOld.txt");
String newReq = TestSupport.fixture("fixtures/mq/rocket-wallet/WalletDeductReqNew.txt");
String producer = TestSupport.fixture("fixtures/mq/rocket-wallet/WalletDeductProducer.txt");
SourceIndex oldIndex = new SourceIndex();
oldIndex.addSource(constants);
oldIndex.addSource(oldReq);
oldIndex.addSource(producer);
SourceIndex newIndex = new SourceIndex();
newIndex.addSource(constants);
newIndex.addSource(newReq);
newIndex.addSource(producer);
Set<String> patterns = new HashSet<>();
patterns.add("MQ01");
WritePoint wp = new MqWritePointDetector(newIndex, patterns)
.detect("WalletDeductProducer.java", producer).get(0);
TypeSchema oldSchema = new JavaSchemaExtractor(oldIndex, 8)
.extract(wp.getResolvedValueType(), wp.isRootArray());
TypeSchema newSchema = new JavaSchemaExtractor(newIndex, 8)
.extract(wp.getResolvedValueType(), wp.isRootArray());
List<SchemaChange> changes = new SchemaDiffer().diff(oldSchema, newSchema);
List<ChangeType> types = changes.stream().map(SchemaChange::getChangeType).collect(Collectors.toList());
assertTrue(types.contains(ChangeType.FIELD_REMOVED), "应删除 remark 字段");
CheckReport report = new CheckReport();
report.setRepository("demo");
report.setBranch("feature/mq");
report.setOldSha("aaa");
report.setNewSha("bbb");
KeyStructureChange key = new KeyStructureChange();
key.setKeyPattern(wp.getResolvedKeyPattern());
key.setChannel(WritePoint.CHANNEL_ROCKETMQ);
key.setWriteLocation(wp.location());
key.setValueType("WalletDeductReq");
key.setOldSkeletonJson("{\"walletId\":\"\",\"amount\":0,\"remark\":\"\"}");
key.setNewSkeletonJson("{\"walletId\":\"\",\"amount\":0}");
key.getFieldDetails().addAll(changes);
report.getKeyChanges().add(key);
report.getChanges().addAll(changes);
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
assertTrue(md.contains("- Topic --> `capital-topic:WALLET_DEDUCT`"), md);
assertTrue(md.contains("> **通道**: RocketMQ"), md);
assertTrue(md.contains("<font color=\"warning\">\"remark\":\"\"</font>")
|| md.contains("remark"), md);
}
@Test
void kafkaListFieldRemovalProducesTopicDiff() {
String oldVo = TestSupport.fixture("fixtures/mq/kafka-patrol/CheckItemDetailVoOld.txt");
String newVo = TestSupport.fixture("fixtures/mq/kafka-patrol/CheckItemDetailVoNew.txt");
String service = TestSupport.fixture("fixtures/mq/kafka-patrol/PatrolService.txt");
SourceIndex oldIndex = new SourceIndex();
oldIndex.addSource(oldVo);
oldIndex.addSource(service);
SourceIndex newIndex = new SourceIndex();
newIndex.addSource(newVo);
newIndex.addSource(service);
Set<String> patterns = new HashSet<>();
patterns.add("MQ-K01");
WritePoint wp = new MqWritePointDetector(newIndex, patterns)
.detect("PatrolService.java", service).get(0);
assertEquals("patrol-store-food-safe:*", wp.getResolvedKeyPattern());
assertTrue(wp.isRootArray());
TypeSchema oldSchema = new JavaSchemaExtractor(oldIndex, 8)
.extract(wp.getResolvedValueType(), true);
TypeSchema newSchema = new JavaSchemaExtractor(newIndex, 8)
.extract(wp.getResolvedValueType(), true);
List<SchemaChange> changes = new SchemaDiffer().diff(oldSchema, newSchema);
assertTrue(changes.stream().anyMatch(c -> c.getChangeType() == ChangeType.FIELD_REMOVED));
CheckReport report = new CheckReport();
report.setRepository("demo");
KeyStructureChange key = new KeyStructureChange();
key.setKeyPattern(wp.getResolvedKeyPattern());
key.setChannel(WritePoint.CHANNEL_KAFKA);
key.setValueType("List<CheckItemDetailVo>");
key.setWriteLocation(wp.location());
key.setOldSkeletonJson("[{\"itemId\":\"\",\"itemName\":\"\",\"score\":0}]");
key.setNewSkeletonJson("[{\"itemId\":\"\",\"itemName\":\"\"}]");
key.getFieldDetails().addAll(changes);
report.getKeyChanges().add(key);
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
assertTrue(md.contains("- Topic --> `patrol-store-food-safe:`"), md);
assertTrue(md.contains("> **通道**: Kafka"), md);
assertTrue(md.contains("> **类型**: List<CheckItemDetailVo>"), md);
}
}

View File

@@ -19,6 +19,9 @@ class ConfigLoaderTest {
assertTrue(config.isEnabled());
assertEquals("notify", config.getMode());
assertTrue(config.getDetection().getPatterns().contains("W01"));
assertTrue(config.getDetection().getMqPatterns().contains("MQ01"));
assertTrue(config.getDetection().getMqPatterns().contains("MQ-K01"));
assertFalse(config.getDetection().isMqReadHintsEnabled());
assertFalse(config.isScanTestSources());
}

View File

@@ -0,0 +1,66 @@
package com.codechecker.cache.detector;
import com.codechecker.cache.TestSupport;
import com.codechecker.cache.schema.SourceIndex;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class MqWritePointDetectorTest {
@Test
void detectsRocketMqSyncSendWithTopicTag() {
String constants = TestSupport.fixture("fixtures/mq/rocket-wallet/CapitalMqConstants.txt");
String req = TestSupport.fixture("fixtures/mq/rocket-wallet/WalletDeductReqOld.txt");
String producer = TestSupport.fixture("fixtures/mq/rocket-wallet/WalletDeductProducer.txt");
SourceIndex index = new SourceIndex();
index.addSource(constants);
index.addSource(req);
index.addSource(producer);
Set<String> patterns = new HashSet<>();
patterns.add("MQ01");
patterns.add("MQ02");
List<WritePoint> wps = new MqWritePointDetector(index, patterns)
.detect("WalletDeductProducer.java", producer);
assertEquals(1, wps.size());
WritePoint wp = wps.get(0);
assertEquals("MQ01", wp.getPattern());
assertEquals(WritePoint.CHANNEL_ROCKETMQ, wp.getChannel());
assertEquals("capital-topic:WALLET_DEDUCT", wp.getResolvedKeyPattern());
assertTrue(wp.getResolvedValueType().endsWith("WalletDeductReq"));
assertFalse(wp.isRootArray());
}
@Test
void detectsKafkaSendListAsRootArray() {
String vo = TestSupport.fixture("fixtures/mq/kafka-patrol/CheckItemDetailVoOld.txt");
String service = TestSupport.fixture("fixtures/mq/kafka-patrol/PatrolService.txt");
SourceIndex index = new SourceIndex();
index.addSource(vo);
index.addSource(service);
Set<String> patterns = new HashSet<>();
patterns.add("MQ-K01");
patterns.add("MQ-K02");
List<WritePoint> wps = new MqWritePointDetector(index, patterns)
.detect("PatrolService.java", service);
assertEquals(1, wps.size());
WritePoint wp = wps.get(0);
assertEquals("MQ-K01", wp.getPattern());
assertEquals(WritePoint.CHANNEL_KAFKA, wp.getChannel());
assertEquals("patrol-store-food-safe:*", wp.getResolvedKeyPattern());
assertTrue(wp.getResolvedValueType().endsWith("CheckItemDetailVo"));
assertTrue(wp.isRootArray());
}
}

View File

@@ -168,6 +168,37 @@ class ReportBuilderTest {
"应包含类型变更摘要行,实际 markdown:\n" + md);
}
@Test
void typeChangeSummaryDedupesSameFieldFromMultipleWrites() {
CheckReport report = baseReport();
KeyStructureChange key = new KeyStructureChange();
key.setKeyPattern("data-analysis-task-topic");
key.setChannel("ROCKETMQ");
key.setWriteLocation("FtbDataAnalysisTaskServiceImpl#createTasks:48");
key.setValueType("DataAnalysisBO");
key.setOldSkeletonJson("{\"test1\":\"\"}");
key.setNewSkeletonJson("{\"test1\":0}");
SchemaChange a = new SchemaChange(ChangeType.TYPE_CHANGED);
a.setFieldPath("test1");
a.setOldValue("String/STRING");
a.setNewValue("Integer/NUMBER");
a.setWriteLocation("FtbDataAnalysisTaskServiceImpl#createTasks:48");
SchemaChange b = new SchemaChange(ChangeType.TYPE_CHANGED);
b.setFieldPath("test1");
b.setOldValue("String/STRING");
b.setNewValue("Integer/NUMBER");
b.setWriteLocation("FtbDataAnalysisTaskServiceImpl#createTasks:52");
key.getFieldDetails().add(a);
key.getFieldDetails().add(b);
report.getKeyChanges().add(key);
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
assertTrue(md.contains("> **类型变更**: test1 <font color=\"warning\">String → Integer</font>"), md);
assertFalse(md.contains("String → Integer</font>test1"),
"同字段类型变更不应重复,实际:\n" + md);
}
@Test
void wrapperAddedHighlightedBeforeNestedMovedFields() {
SchemaChange wrapper = new SchemaChange(ChangeType.WRAPPER_ADDED);