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.<UserStatisticsRegisterTrendRow>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.<UserStatisticsActiveTrendRow>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());
|
}
|
}
|