package com.tcm.training.admin.statistics.service; import com.tcm.training.admin.statistics.mapper.UserStatisticsMapper; import com.tcm.training.admin.statistics.row.UserStatisticsActiveTrendRow; import com.tcm.training.admin.statistics.row.UserStatisticsCompositionRow; import com.tcm.training.admin.statistics.row.UserStatisticsOverviewRow; import com.tcm.training.admin.statistics.row.UserStatisticsRegisterTrendRow; import com.tcm.training.admin.statistics.vo.UserStatisticsVO; 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 java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class UserStatisticsAppServiceTest { @Mock private UserStatisticsMapper userStatisticsMapper; @InjectMocks private UserStatisticsAppService userStatisticsAppService; @Test void statisticsShouldBuildOverviewTrendAndComposition() { UserStatisticsOverviewRow overviewRow = new UserStatisticsOverviewRow(); overviewRow.setRegisteredUserCount(8L); overviewRow.setDoctorRegisteredCount(2L); overviewRow.setMemberOpenCount(5L); overviewRow.setCoursePurchaseCount(7L); overviewRow.setLiveWatchClickCount(11L); overviewRow.setActiveEnterUserCount(4L); overviewRow.setTotalUserCount(20L); UserStatisticsRegisterTrendRow registerTrendRow = new UserStatisticsRegisterTrendRow(); registerTrendRow.setStatDate(LocalDate.of(2026, 3, 1)); registerTrendRow.setRegisteredUserCount(3L); registerTrendRow.setDoctorRegisteredCount(1L); UserStatisticsActiveTrendRow activeTrendRow = new UserStatisticsActiveTrendRow(); activeTrendRow.setStatDate(LocalDate.of(2026, 3, 2)); activeTrendRow.setActiveEnterUserCount(2L); UserStatisticsCompositionRow compositionRow = new UserStatisticsCompositionRow(); compositionRow.setMonthMemberPurchaseCount(2L); compositionRow.setQuarterMemberPurchaseCount(1L); compositionRow.setCourseMemberUserCount(3L); when(userStatisticsMapper.selectOverview( eq(null), eq(LocalDateTime.of(2026, 3, 1, 0, 0, 0)), eq(LocalDateTime.of(2026, 3, 2, LocalTime.MAX.getHour(), LocalTime.MAX.getMinute(), LocalTime.MAX.getSecond(), LocalTime.MAX.getNano())) )).thenReturn(overviewRow); when(userStatisticsMapper.selectRegisterTrend( eq(null), eq(LocalDateTime.of(2026, 3, 1, 0, 0, 0)), eq(LocalDateTime.of(2026, 3, 2, LocalTime.MAX.getHour(), LocalTime.MAX.getMinute(), LocalTime.MAX.getSecond(), LocalTime.MAX.getNano())) )).thenReturn(Collections.singletonList(registerTrendRow)); when(userStatisticsMapper.selectActiveTrend( eq(null), eq(LocalDateTime.of(2026, 3, 1, 0, 0, 0)), eq(LocalDateTime.of(2026, 3, 2, LocalTime.MAX.getHour(), LocalTime.MAX.getMinute(), LocalTime.MAX.getSecond(), LocalTime.MAX.getNano())) )).thenReturn(Collections.singletonList(activeTrendRow)); when(userStatisticsMapper.selectMemberComposition( eq(null), eq(LocalDateTime.of(2026, 3, 1, 0, 0, 0)), eq(LocalDateTime.of(2026, 3, 2, LocalTime.MAX.getHour(), LocalTime.MAX.getMinute(), LocalTime.MAX.getSecond(), LocalTime.MAX.getNano())) )).thenReturn(compositionRow); UserStatisticsVO result = userStatisticsAppService.statistics( "ALL", LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 2) ); assertEquals(8L, result.getOverview().getRegisteredUserCount()); assertEquals(2L, result.getOverview().getDoctorRegisteredCount()); assertEquals(5L, result.getOverview().getMemberOpenCount()); assertEquals(7L, result.getOverview().getCoursePurchaseCount()); assertEquals(11L, result.getOverview().getLiveWatchClickCount()); assertEquals(new BigDecimal("20.00"), result.getOverview().getUserActivityRate()); assertEquals(2, result.getTrend().size()); assertEquals(3L, result.getTrend().get(0).getRegisteredUserCount()); assertEquals(1L, result.getTrend().get(0).getDoctorRegisteredCount()); assertEquals(0L, result.getTrend().get(0).getActiveEnterUserCount()); assertEquals(0L, result.getTrend().get(1).getRegisteredUserCount()); assertEquals(2L, result.getTrend().get(1).getActiveEnterUserCount()); assertEquals(2L, result.getMemberComposition().getMonthMemberPurchaseCount()); assertEquals(1L, result.getMemberComposition().getQuarterMemberPurchaseCount()); assertEquals(3L, result.getMemberComposition().getCourseMemberUserCount()); } @Test void statisticsShouldUseMemberTypeFilter() { UserStatisticsOverviewRow overviewRow = new UserStatisticsOverviewRow(); overviewRow.setTotalUserCount(8L); overviewRow.setActiveEnterUserCount(2L); when(userStatisticsMapper.selectOverview( eq("MEMBER"), eq(LocalDateTime.of(2026, 3, 5, 0, 0, 0)), eq(LocalDateTime.of(2026, 3, 5, LocalTime.MAX.getHour(), LocalTime.MAX.getMinute(), LocalTime.MAX.getSecond(), LocalTime.MAX.getNano())) )).thenReturn(overviewRow); when(userStatisticsMapper.selectRegisterTrend( eq("MEMBER"), eq(LocalDateTime.of(2026, 3, 5, 0, 0, 0)), eq(LocalDateTime.of(2026, 3, 5, LocalTime.MAX.getHour(), LocalTime.MAX.getMinute(), LocalTime.MAX.getSecond(), LocalTime.MAX.getNano())) )).thenReturn(Collections.emptyList()); when(userStatisticsMapper.selectActiveTrend( eq("MEMBER"), eq(LocalDateTime.of(2026, 3, 5, 0, 0, 0)), eq(LocalDateTime.of(2026, 3, 5, LocalTime.MAX.getHour(), LocalTime.MAX.getMinute(), LocalTime.MAX.getSecond(), LocalTime.MAX.getNano())) )).thenReturn(Collections.emptyList()); when(userStatisticsMapper.selectMemberComposition( eq("MEMBER"), eq(LocalDateTime.of(2026, 3, 5, 0, 0, 0)), eq(LocalDateTime.of(2026, 3, 5, LocalTime.MAX.getHour(), LocalTime.MAX.getMinute(), LocalTime.MAX.getSecond(), LocalTime.MAX.getNano())) )).thenReturn(new UserStatisticsCompositionRow()); UserStatisticsVO result = userStatisticsAppService.statistics( "MEMBER", LocalDate.of(2026, 3, 5), LocalDate.of(2026, 3, 5) ); assertEquals(new BigDecimal("25.00"), result.getOverview().getUserActivityRate()); } @Test void statisticsShouldRejectInvalidType() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> userStatisticsAppService.statistics("OTHER", LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 1))); assertEquals("用户类型不合法", exception.getMessage()); } @Test void statisticsShouldRejectInvalidDateRange() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> userStatisticsAppService.statistics("ALL", LocalDate.of(2026, 3, 10), LocalDate.of(2026, 3, 1))); assertEquals("开始日期不能晚于结束日期", exception.getMessage()); } }