package com.superbiz.agent.tool; import com.superbiz.agent.dto.KnowledgeEntry; import com.superbiz.agent.dto.LookupResult; import com.superbiz.agent.service.KnowledgeIndexService; import com.superbiz.agent.service.VectorSearchService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** * LookupKnowledgeTool 单元测试 */ class LookupKnowledgeToolTest { @Mock private KnowledgeIndexService knowledgeIndexService; @Mock private VectorSearchService vectorSearchService; @InjectMocks private LookupKnowledgeTool tool; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } @Test void testLookup_uniqueMatch_highConfidence() { // 准备 L0 唯一匹配 KnowledgeEntry entry = KnowledgeEntry.builder() .filePath("test.md") .title("Test Doc") .keywords(List.of("ERR_TIMEOUT")) .summary("Test summary") .build(); when(knowledgeIndexService.exactMatch("ERR_TIMEOUT")) .thenReturn(List.of(entry)); when(knowledgeIndexService.readDocument("test.md", 2000)) .thenReturn("Test content"); // 执行查询 LookupResult result = tool.lookupKnowledge("ERR_TIMEOUT"); // 验证结果 assertTrue(result.isFound()); assertNotNull(result.getPrimary()); assertEquals("high", result.getPrimary().getConfidence()); assertEquals("exact_L0", result.getPrimary().getMatchType()); assertEquals("Test content", result.getPrimary().getContent()); assertNull(result.getSupplement()); // 高置信度不调用 L1 // 验证 L1 未被调用 verify(vectorSearchService, never()).searchSimilarDocuments(anyString(), anyInt(), any()); } @Test void testLookup_multipleMatches_lowConfidence() { // 准备 L0 多个匹配 KnowledgeEntry entry1 = KnowledgeEntry.builder() .filePath("doc1.md") .keywords(List.of("超时")) .build(); KnowledgeEntry entry2 = KnowledgeEntry.builder() .filePath("doc2.md") .keywords(List.of("超时")) .build(); when(knowledgeIndexService.exactMatch("超时")) .thenReturn(List.of(entry1, entry2)); when(knowledgeIndexService.readDocument("doc1.md", 2000)) .thenReturn("Content 1"); // 准备 L1 结果 VectorSearchService.SearchResult l1Result = new VectorSearchService.SearchResult(); l1Result.setContent("L1 content"); l1Result.setMetadata("l1-source"); when(vectorSearchService.searchSimilarDocuments("超时", 3, null)) .thenReturn(List.of(l1Result)); // 执行查询 LookupResult result = tool.lookupKnowledge("超时"); // 验证结果 assertTrue(result.isFound()); assertNotNull(result.getPrimary()); assertEquals("low", result.getPrimary().getConfidence()); // 多个匹配 = 低置信度 assertEquals("Content 1", result.getPrimary().getContent()); assertNotNull(result.getSupplement()); // 低置信度调用 L1 assertEquals("L1 content", result.getSupplement().getContent()); assertEquals("semantic_L1", result.getSupplement().getMatchType()); // 验证 L1 被调用 verify(vectorSearchService).searchSimilarDocuments("超时", 3, null); } @Test void testLookup_noL0Match_onlyL1() { // L0 未匹配 when(knowledgeIndexService.exactMatch("性能优化")) .thenReturn(Collections.emptyList()); // 准备 L1 结果 VectorSearchService.SearchResult l1Result = new VectorSearchService.SearchResult(); l1Result.setContent("L1 semantic result"); l1Result.setMetadata("l1-doc"); when(vectorSearchService.searchSimilarDocuments("性能优化", 3, null)) .thenReturn(List.of(l1Result)); // 执行查询 LookupResult result = tool.lookupKnowledge("性能优化"); // 验证结果 assertTrue(result.isFound()); assertNull(result.getPrimary()); // L0 未命中 assertNotNull(result.getSupplement()); // 只有 L1 结果 assertEquals("L1 semantic result", result.getSupplement().getContent()); verify(vectorSearchService).searchSimilarDocuments("性能优化", 3, null); } @Test void testLookup_noMatch() { // L0 和 L1 都未匹配 when(knowledgeIndexService.exactMatch("不存在的内容")) .thenReturn(Collections.emptyList()); when(vectorSearchService.searchSimilarDocuments("不存在的内容", 3, null)) .thenReturn(Collections.emptyList()); // 执行查询 LookupResult result = tool.lookupKnowledge("不存在的内容"); // 验证结果 assertFalse(result.isFound()); assertNull(result.getPrimary()); assertNull(result.getSupplement()); } @Test void testLookup_l0MatchButReadFails() { // L0 匹配但文件读取失败 KnowledgeEntry entry = KnowledgeEntry.builder() .filePath("nonexistent.md") .keywords(List.of("test")) .build(); when(knowledgeIndexService.exactMatch("test")) .thenReturn(List.of(entry)); when(knowledgeIndexService.readDocument("nonexistent.md", 2000)) .thenReturn(null); // 读取失败 // L0 唯一匹配不会调用 L1,所以没有补充结果 // 执行查询 LookupResult result = tool.lookupKnowledge("test"); // 验证:found 为 false,因为无法读取内容且无 L1 补充 assertFalse(result.isFound()); assertNull(result.getPrimary()); assertNull(result.getSupplement()); // 唯一匹配不调用 L1 // 验证 L1 未被调用(因为是唯一匹配 = 高置信度) verify(vectorSearchService, never()).searchSimilarDocuments(anyString(), anyInt(), any()); } @Test void testLookup_l1ReturnsNull() { // L0 未匹配,L1 返回 null when(knowledgeIndexService.exactMatch("query")) .thenReturn(Collections.emptyList()); when(vectorSearchService.searchSimilarDocuments("query", 3, null)) .thenReturn(null); // 执行查询 LookupResult result = tool.lookupKnowledge("query"); // 验证 assertFalse(result.isFound()); } @Test void testLookup_availableSectionsIsNull() { // 验证 availableSections 字段为 null(MVP 预留字段) KnowledgeEntry entry = KnowledgeEntry.builder() .filePath("test.md") .keywords(List.of("test")) .build(); when(knowledgeIndexService.exactMatch("test")) .thenReturn(List.of(entry)); when(knowledgeIndexService.readDocument("test.md", 2000)) .thenReturn("Content"); LookupResult result = tool.lookupKnowledge("test"); assertNotNull(result.getPrimary()); assertNull(result.getPrimary().getAvailableSections()); // MVP 返回 null } }