package com.guo.learningprogresstracker.service.impl; import com.fasterxml.jackson.databind.ObjectMapper; import com.guo.learningprogresstracker.entity.ReviewStandardMindMapEntity; import com.guo.learningprogresstracker.exception.NotFindEntitiesException; import com.guo.learningprogresstracker.mapper.*; import com.guo.learningprogresstracker.service.MindMapAiClient; import com.guo.learningprogresstracker.utils.CompareResult; import com.guo.learningprogresstracker.utils.MindMapNode; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class StandardMindMapServiceImplTest { @InjectMocks private StandardMindMapServiceImpl service; @Mock private ReviewStandardMindMapMapper standardMindMapMapper; @Mock private ReviewRecallRecordMapper recallRecordMapper; @Mock private TasksMapper tasksMapper; @Mock private StudyReportsMapper studyReportsMapper; @Mock private StudyReportFragmentsMapper studyReportFragmentsMapper; @Mock private TaskApplicationMapper taskApplicationMapper; @Mock private StudySessionsMapper studySessionsMapper; @Mock private List aiClients; @Mock private MindMapAiClient mockAiClient; @Mock private AiServiceClient aiServiceClient; private final ObjectMapper objectMapper = new ObjectMapper(); // ============ normalize / fuzzyMatch / bigramSet ============ @Test void normalize_shouldStripPunctuationAndLowercase() { assertEquals("javaconcurrency", StandardMindMapServiceImpl.normalize("Java Concurrency")); assertEquals("测试abc", StandardMindMapServiceImpl.normalize("测试,ABC!?")); assertEquals("helloworld", StandardMindMapServiceImpl.normalize(" Hello, World! ")); assertEquals("", StandardMindMapServiceImpl.normalize(null)); assertEquals("", StandardMindMapServiceImpl.normalize("")); } @Test void normalize_shouldStripCompareMarkers() { assertEquals("thread", StandardMindMapServiceImpl.normalize("MATCHED|Thread")); assertEquals("thread", StandardMindMapServiceImpl.normalize("MISSED|Thread")); } @Test void bigramSet_shouldExtractCharacterPairs() { java.util.Set set = StandardMindMapServiceImpl.bigramSet("hello"); assertTrue(set.contains("he")); assertTrue(set.contains("el")); assertTrue(set.contains("ll")); assertTrue(set.contains("lo")); assertEquals(4, set.size()); } @Test void bigramSet_shouldReturnEmptyForShortStrings() { assertTrue(StandardMindMapServiceImpl.bigramSet("a").isEmpty()); assertTrue(StandardMindMapServiceImpl.bigramSet("").isEmpty()); } // ============ compareTrees ============ @Test void compareTrees_perfectMatch() { service = createService(); MindMapNode standard = new MindMapNode("学习Java"); standard.setChildren(List.of(new MindMapNode("线程基础"), new MindMapNode("锁机制"))); MindMapNode recall = new MindMapNode("学习Java"); recall.setChildren(List.of(new MindMapNode("线程基础"), new MindMapNode("锁机制"))); CompareResult result = service.compareTrees(standard, recall); assertEquals(2, result.getMatchedCount()); assertEquals(0, result.getMissedCount()); assertEquals(0, result.getExtraCount()); assertEquals(1.0, result.getRecallRatio(), 0.001); } @Test void compareTrees_partialMatch() { service = createService(); MindMapNode standard = new MindMapNode("学习Java"); standard.setChildren(List.of( new MindMapNode("线程基础"), new MindMapNode("锁机制"), new MindMapNode("JVM内存模型") )); MindMapNode recall = new MindMapNode("学习Java"); recall.setChildren(List.of( new MindMapNode("线程基础"), new MindMapNode("SpringBoot") )); CompareResult result = service.compareTrees(standard, recall); assertEquals(1, result.getMatchedCount()); assertEquals(2, result.getMissedCount()); assertEquals(1, result.getExtraCount()); } @Test void compareTrees_noMatch() { service = createService(); MindMapNode standard = new MindMapNode("学习Java"); standard.setChildren(List.of(new MindMapNode("线程基础"), new MindMapNode("锁机制"))); MindMapNode recall = new MindMapNode("完全不同"); recall.setChildren(List.of(new MindMapNode("前端开发"))); CompareResult result = service.compareTrees(standard, recall); assertEquals(0, result.getMatchedCount()); assertEquals(2, result.getMissedCount()); assertEquals(1, result.getExtraCount()); assertEquals(0.0, result.getRecallRatio(), 0.001); } // ============ 边缘情况 ============ @Test void compareTrees_emptyStandardLeaves() { service = createService(); MindMapNode standard = new MindMapNode("根"); // 无子节点 MindMapNode recall = new MindMapNode("根"); recall.setChildren(List.of(new MindMapNode("额外节点"))); CompareResult result = service.compareTrees(standard, recall); assertEquals(0, result.getMatchedCount()); assertEquals(0, result.getMissedCount()); assertEquals(1, result.getExtraCount()); assertEquals(0.0, result.getRecallRatio(), 0.001); } @Test void getOrGenerate_shouldThrowWhenTaskNotFound() { when(tasksMapper.exists(any())).thenReturn(false); assertThrows(NotFindEntitiesException.class, () -> service.getOrGenerate("NOT_EXIST")); } @Test void getOrGenerate_shouldReturnExisting() throws Exception { when(tasksMapper.exists(any())).thenReturn(true); ReviewStandardMindMapEntity existing = new ReviewStandardMindMapEntity(); existing.setId(1); existing.setTaskNum("T1"); existing.setTitle("已有导图"); when(standardMindMapMapper.selectOne(any())).thenReturn(existing); ReviewStandardMindMapEntity result = service.getOrGenerate("T1"); assertEquals("已有导图", result.getTitle()); verify(standardMindMapMapper, never()).insert(any()); } // ============ 辅助 ============ private StandardMindMapServiceImpl createService() { StandardMindMapServiceImpl s = new StandardMindMapServiceImpl( standardMindMapMapper, recallRecordMapper, tasksMapper, studyReportsMapper, studyReportFragmentsMapper, taskApplicationMapper, studySessionsMapper, List.of(mockAiClient), objectMapper, aiServiceClient); return s; } }