Files
lpt-be/src/test/java/com/guo/learningprogresstracker/service/impl/ReviewServiceImplTest.java
T
cat-shark 32b247526b feat(复习):标准思维导图生成与回忆对比
- 新增 review_standard_mind_maps / review_recall_records 数据表
- BuiltinMindMapGenerator:从报告/残片/应用场景规则生成标准导图
- MindMapAiClient 接口 + RemoteAiMindMapClient 预留
- StandardMindMapService:生成/编辑/重新生成/回忆对比
- 对比算法:标题归一化 + Bigram Jaccard 模糊匹配
- ReviewController 新增 6 个端点
- MindMapTreeTool / MindMapNode / CompareResult 工具类
- 完整单元测试(10 个,全部通过)
- 更新 docs/review-module-design.md
2026-07-03 08:53:51 +08:00

189 lines
7.7 KiB
Java

package com.guo.learningprogresstracker.service.impl;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.guo.learningprogresstracker.dto.ReviewFeedItem;
import com.guo.learningprogresstracker.dto.request.UpsertReviewMindMapRequest;
import com.guo.learningprogresstracker.entity.ReviewMindMapEntity;
import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity;
import com.guo.learningprogresstracker.entity.StudyReportsEntity;
import com.guo.learningprogresstracker.entity.StudySessionsEntity;
import com.guo.learningprogresstracker.entity.TaskEntity;
import com.guo.learningprogresstracker.enums.ReviewMindMapFileFormatEnum;
import com.guo.learningprogresstracker.enums.ReviewMindMapParseStatusEnum;
import com.guo.learningprogresstracker.mapper.ReviewMindMapMapper;
import com.guo.learningprogresstracker.mapper.StudyReportFragmentsMapper;
import com.guo.learningprogresstracker.mapper.StudyReportsMapper;
import com.guo.learningprogresstracker.mapper.StudySessionsMapper;
import com.guo.learningprogresstracker.mapper.TasksMapper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
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.mock.web.MockMultipartFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ReviewServiceImplTest {
@BeforeAll
static void initTableInfo() {
MybatisConfiguration configuration = new MybatisConfiguration();
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), StudyReportsEntity.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), StudyReportFragmentsEntity.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), StudySessionsEntity.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), TaskEntity.class);
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(configuration, ""), ReviewMindMapEntity.class);
}
@InjectMocks
private ReviewServiceImpl reviewService;
@Mock
private StudyReportsMapper studyReportsMapper;
@Mock
private StudyReportFragmentsMapper studyReportFragmentsMapper;
@Mock
private StudySessionsMapper studySessionsMapper;
@Mock
private TasksMapper tasksMapper;
@Mock
private ReviewMindMapMapper reviewMindMapMapper;
@Test
void getReviewFeed_shouldApplyFinalLimitAfterMerge() {
StudyReportsEntity report = new StudyReportsEntity();
report.setId(1);
report.setSessionNum("S1");
report.setContent("report");
report.setCreatedTime(LocalDateTime.now());
StudyReportFragmentsEntity fragment = new StudyReportFragmentsEntity();
fragment.setId(2);
fragment.setSessionNum("S1");
fragment.setContent("fragment");
fragment.setCreatedTime(LocalDateTime.now().minusMinutes(1));
StudySessionsEntity session = new StudySessionsEntity();
session.setSessionNum("S1");
session.setTaskNum("T1");
TaskEntity task = new TaskEntity();
task.setTaskNum("T1");
task.setTaskName("Task");
when(studyReportsMapper.selectList(any())).thenReturn(List.of(report));
when(studyReportFragmentsMapper.selectList(any())).thenReturn(List.of(fragment));
when(studySessionsMapper.selectList(any())).thenReturn(List.of(session));
when(tasksMapper.selectList(any())).thenReturn(List.of(task));
List<ReviewFeedItem> items = reviewService.getReviewFeed(1, "recent");
assertEquals(1, items.size());
assertEquals("REPORT", items.get(0).getSourceType());
assertEquals("Task", items.get(0).getTaskName());
}
@Test
void upsertMindMap_shouldCreateWhenMissing() throws Exception {
when(tasksMapper.exists(any())).thenReturn(true);
when(reviewMindMapMapper.selectOne(any())).thenReturn(null);
when(reviewMindMapMapper.insert(any())).thenAnswer(invocation -> {
ReviewMindMapEntity entity = invocation.getArgument(0);
entity.setId(3);
return 1;
});
UpsertReviewMindMapRequest request = new UpsertReviewMindMapRequest();
request.setTitle("Map");
request.setContent("root");
request.setContentFormat("mermaid");
ReviewMindMapEntity entity = reviewService.upsertMindMap("T1", request);
assertNotNull(entity.getId());
assertEquals("T1", entity.getTaskNum());
assertEquals("MERMAID", entity.getContentFormat());
verify(reviewMindMapMapper).insert(any(ReviewMindMapEntity.class));
}
@Test
void upsertMindMap_shouldUpdateExisting() throws Exception {
ReviewMindMapEntity existing = new ReviewMindMapEntity();
existing.setId(5);
existing.setTaskNum("T1");
existing.setTitle("Old");
existing.setContent("old");
existing.setContentFormat("TEXT");
when(tasksMapper.exists(any())).thenReturn(true);
when(reviewMindMapMapper.selectOne(any())).thenReturn(existing);
UpsertReviewMindMapRequest request = new UpsertReviewMindMapRequest();
request.setTitle("New");
request.setContent("new");
request.setContentFormat("json");
ReviewMindMapEntity entity = reviewService.upsertMindMap("T1", request);
assertEquals(5, entity.getId());
assertEquals("New", entity.getTitle());
assertEquals("JSON", entity.getContentFormat());
verify(reviewMindMapMapper).updateById(existing);
}
@Test
void uploadMindMap_shouldParseMarkdownFile() throws Exception {
when(tasksMapper.exists(any())).thenReturn(true);
when(reviewMindMapMapper.selectOne(any())).thenReturn(null);
when(reviewMindMapMapper.insert(any())).thenAnswer(invocation -> {
ReviewMindMapEntity entity = invocation.getArgument(0);
entity.setId(9);
return 1;
});
MockMultipartFile file = new MockMultipartFile(
"file",
"java-review.md",
"text/markdown",
"# Java 并发\n## 线程基础\n- RUNNABLE\n".getBytes());
ReviewMindMapEntity entity = reviewService.uploadMindMap("T1", file);
try {
assertEquals(9, entity.getId());
assertEquals("FILE", entity.getSourceType());
assertEquals(ReviewMindMapFileFormatEnum.MARKDOWN.getCode(), entity.getFileFormat());
assertEquals(ReviewMindMapParseStatusEnum.SUCCESS.getCode(), entity.getParseStatus());
assertEquals("JSON", entity.getContentFormat());
assertNotNull(entity.getParsedContent());
assertEquals(true, entity.getParsedContent().contains("线程基础"));
assertEquals(true, entity.getContent().contains("RUNNABLE"));
} finally {
if (entity.getFilePath() != null) {
Path storedFile = Path.of(entity.getFilePath());
Files.deleteIfExists(storedFile);
Files.deleteIfExists(storedFile.getParent());
}
}
verify(reviewMindMapMapper).insert(any(ReviewMindMapEntity.class));
}
}