package com.guo.learningprogresstracker.service.impl; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.guo.learningprogresstracker.entity.StudyReportsEntity; import com.guo.learningprogresstracker.entity.StudySessionsEntity; import com.guo.learningprogresstracker.enums.StudySessionStateEnum; import com.guo.learningprogresstracker.mapper.StudyReportFragmentsMapper; import com.guo.learningprogresstracker.mapper.StudyReportsMapper; import com.guo.learningprogresstracker.mapper.StudySessionsMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.mockito.junit.jupiter.MockitoExtension; import java.time.LocalDateTime; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class StudySessionsServiceImplTest { @Spy @InjectMocks private StudySessionsServiceImpl studySessionsService; @Mock private StudyReportsMapper studyReportsMapper; @Mock private StudyReportFragmentsMapper studyReportFragmentsMapper; @Mock private StudySessionsMapper studySessionsMapper; @BeforeEach void setUp() { // stub updateById,避免调用真实的 getBaseMapper() doReturn(true).when(studySessionsService).updateById(any()); } private StudySessionsEntity createSession(String state, int minutesAgo) { StudySessionsEntity session = new StudySessionsEntity(); session.setSessionNum("SESSION_TEST"); session.setSessionState(state); session.setStartTime(LocalDateTime.now().minusMinutes(minutesAgo)); session.setLastStartTime(LocalDateTime.now().minusMinutes(minutesAgo)); session.setEffectiveTime(0); session.setActualTime(0); return session; } private void mockGetOneOpt(StudySessionsEntity session) { doReturn(Optional.of(session)).when(studySessionsService).getOneOpt(any(Wrapper.class)); } /** * 场景1:ONGOING 状态,有效时间 < 10分钟 → 返回提示消息 */ @Test void endedStudySession_ongoing_lessThan10Min_shouldReturnMessage() throws Exception { StudySessionsEntity session = createSession(StudySessionStateEnum.ONGOING.name(), 5); mockGetOneOpt(session); String message = studySessionsService.endedStudySession("SESSION_TEST", "学习内容"); assertEquals("本次有效学习时间不足10分钟,不计入总学习时间", message); assertEquals(0, session.getEffectiveTime(), "effectiveTime 应被清零"); assertEquals(StudySessionStateEnum.ENDED.name(), session.getSessionState()); verify(studyReportsMapper).insert(any(StudyReportsEntity.class)); } /** * 场景2:ONGOING 状态,有效时间 > 10分钟 → 返回 null(正常结束) */ @Test void endedStudySession_ongoing_moreThan10Min_shouldReturnNull() throws Exception { StudySessionsEntity session = createSession(StudySessionStateEnum.ONGOING.name(), 15); mockGetOneOpt(session); String message = studySessionsService.endedStudySession("SESSION_TEST", "学习内容"); assertNull(message, "有效时间超过10分钟不应返回提示"); assertTrue(session.getEffectiveTime() > 0, "effectiveTime 应保留"); assertEquals(StudySessionStateEnum.ENDED.name(), session.getSessionState()); } /** * 场景3:PAUSED 状态,有效时间 < 10分钟 → 返回 null(PAUSED 不触发提示) */ @Test void endedStudySession_paused_lessThan10Min_shouldReturnNull() throws Exception { StudySessionsEntity session = createSession(StudySessionStateEnum.PAUSED.name(), 5); session.setEffectiveTime(5 * 60); mockGetOneOpt(session); String message = studySessionsService.endedStudySession("SESSION_TEST", "学习内容"); assertNull(message, "PAUSED 状态结束不返回提示(用户已主动暂停)"); assertEquals(0, session.getEffectiveTime(), "effectiveTime 仍应被实体层清零"); } /** * 场景4:PAUSED 状态,有效时间 > 10分钟 → 返回 null,effectiveTime 保留 */ @Test void endedStudySession_paused_moreThan10Min_shouldReturnNull() throws Exception { StudySessionsEntity session = createSession(StudySessionStateEnum.PAUSED.name(), 15); session.setEffectiveTime(15 * 60); mockGetOneOpt(session); String message = studySessionsService.endedStudySession("SESSION_TEST", "学习内容"); assertNull(message); assertTrue(session.getEffectiveTime() > 0, "effectiveTime 应保留"); } /** * 场景5:无论是否触发清零,学习报告都应正常创建 */ @Test void endedStudySession_shouldAlwaysCreateReport() throws Exception { StudySessionsEntity session = createSession(StudySessionStateEnum.ONGOING.name(), 3); mockGetOneOpt(session); studySessionsService.endedStudySession("SESSION_TEST", "测试报告内容"); verify(studyReportsMapper).insert(argThat(report -> "SESSION_TEST".equals(((StudyReportsEntity) report).getSessionNum()) && "测试报告内容".equals(((StudyReportsEntity) report).getContent()) )); } /** * 场景6:ONGOING 状态,有效时间恰好 = 10分钟 → 有效时间保留(600 不 < 600) */ @Test void endedStudySession_ongoing_exactly10Min_shouldReturnNull() throws Exception { StudySessionsEntity session = createSession(StudySessionStateEnum.ONGOING.name(), 10); mockGetOneOpt(session); String message = studySessionsService.endedStudySession("SESSION_TEST", "学习内容"); assertNull(message, "有效时间等于10分钟应保留(阈值为 < 600)"); assertTrue(session.getEffectiveTime() >= 10 * 60, "effectiveTime 应保留"); } /** * 场景7:会话不存在 → 抛出异常 */ @Test void endedStudySession_sessionNotFound_shouldThrowException() { doReturn(Optional.empty()).when(studySessionsService).getOneOpt(any(Wrapper.class)); assertThrows(Exception.class, () -> studySessionsService.endedStudySession("NOT_EXIST", "内容")); } /** * 场景8:ONGOING 状态,有效时间极短(1分钟) → 返回提示消息 */ @Test void endedStudySession_ongoing_veryShortTime_shouldReturnMessage() throws Exception { StudySessionsEntity session = createSession(StudySessionStateEnum.ONGOING.name(), 1); mockGetOneOpt(session); String message = studySessionsService.endedStudySession("SESSION_TEST", "学习内容"); assertEquals("本次有效学习时间不足10分钟,不计入总学习时间", message); assertEquals(0, session.getEffectiveTime()); } /** * 场景9:ONGOING 状态,刚开始就结束(effectiveTime ≈ 0) → 返回提示消息 */ @Test void endedStudySession_ongoing_immediatelyEnded_shouldReturnMessage() throws Exception { StudySessionsEntity session = new StudySessionsEntity(); session.setSessionNum("SESSION_INSTANT"); session.setSessionState(StudySessionStateEnum.ONGOING.name()); session.setStartTime(LocalDateTime.now()); session.setLastStartTime(LocalDateTime.now()); session.setEffectiveTime(0); session.setActualTime(0); mockGetOneOpt(session); String message = studySessionsService.endedStudySession("SESSION_INSTANT", "内容"); assertEquals("本次有效学习时间不足10分钟,不计入总学习时间", message); assertEquals(0, session.getEffectiveTime()); } /** * 场景10:确认 updateById 被调用(会话状态持久化) */ @Test void endedStudySession_shouldCallUpdateById() throws Exception { StudySessionsEntity session = createSession(StudySessionStateEnum.ONGOING.name(), 5); mockGetOneOpt(session); studySessionsService.endedStudySession("SESSION_TEST", "内容"); verify(studySessionsService).updateById(session); } }