test(cache): 修复CacheConfigTest边界值测试
- 修改 shouldVerifyCacheManager_withMaximumIntegerTtl 为 shouldVerifyCacheManager_withMaximumAllowedTtl - 使用正确的最大TTL值(10080分钟,7天)而不是 Integer.MAX_VALUE - 新增 shouldThrowException_whenTtlExceedsMaximum 测试验证边界检查 - 所有1266个测试用例通过 - 覆盖率: 指令81.89%, 行88.48%, 分支51.55% docs: 添加项目状态报告 - 生成 PROJECT_STATUS_REPORT.md 详细记录项目当前状态 - 包含质量指标、已完成功能、待办事项和技术债务
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
package com.mosquito.project.job;
|
||||
|
||||
import com.mosquito.project.domain.Activity;
|
||||
import com.mosquito.project.domain.DailyActivityStats;
|
||||
import com.mosquito.project.persistence.entity.DailyActivityStatsEntity;
|
||||
import com.mosquito.project.persistence.repository.DailyActivityStatsRepository;
|
||||
import com.mosquito.project.service.ActivityService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StatisticsAggregationJobCompleteTest {
|
||||
|
||||
@Mock
|
||||
private ActivityService activityService;
|
||||
|
||||
@Mock
|
||||
private DailyActivityStatsRepository dailyStatsRepository;
|
||||
|
||||
@InjectMocks
|
||||
private StatisticsAggregationJob job;
|
||||
|
||||
private LocalDate testDate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testDate = LocalDate.of(2024, 6, 15);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAggregateDailyStats_whenActivitiesExist() {
|
||||
Activity activity1 = createActivity(1L, "Activity 1");
|
||||
Activity activity2 = createActivity(2L, "Activity 2");
|
||||
List<Activity> activities = List.of(activity1, activity2);
|
||||
|
||||
when(activityService.getAllActivities()).thenReturn(activities);
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
job.aggregateDailyStats();
|
||||
|
||||
verify(activityService, times(1)).getAllActivities();
|
||||
verify(dailyStatsRepository, times(4)).save(any(DailyActivityStatsEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleEmptyActivityList_whenNoActivities() {
|
||||
when(activityService.getAllActivities()).thenReturn(Collections.emptyList());
|
||||
|
||||
job.aggregateDailyStats();
|
||||
|
||||
verify(activityService, times(1)).getAllActivities();
|
||||
verify(dailyStatsRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateStatsInValidRange_whenAggregateStatsForActivityCalled() {
|
||||
Activity activity = createActivity(1L, "Test Activity");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
DailyActivityStats stats = job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
assertThat(stats).isNotNull();
|
||||
assertThat(stats.getActivityId()).isEqualTo(1L);
|
||||
assertThat(stats.getStatDate()).isEqualTo(testDate);
|
||||
assertThat(stats.getViews()).isBetween(1000, 1499);
|
||||
assertThat(stats.getShares()).isBetween(200, 299);
|
||||
assertThat(stats.getNewRegistrations()).isBetween(50, 99);
|
||||
assertThat(stats.getConversions()).isBetween(10, 29);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetCorrectActivityId_whenDifferentActivitiesProcessed() {
|
||||
Activity activity1 = createActivity(100L, "Activity 100");
|
||||
Activity activity2 = createActivity(200L, "Activity 200");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
DailyActivityStats stats1 = job.aggregateStatsForActivity(activity1, testDate);
|
||||
DailyActivityStats stats2 = job.aggregateStatsForActivity(activity2, testDate);
|
||||
|
||||
assertThat(stats1.getActivityId()).isEqualTo(100L);
|
||||
assertThat(stats2.getActivityId()).isEqualTo(200L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetCorrectDate_whenDifferentDatesProcessed() {
|
||||
Activity activity = createActivity(1L, "Test");
|
||||
LocalDate date1 = LocalDate.of(2024, 1, 1);
|
||||
LocalDate date2 = LocalDate.of(2024, 12, 31);
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
DailyActivityStats stats1 = job.aggregateStatsForActivity(activity, date1);
|
||||
DailyActivityStats stats2 = job.aggregateStatsForActivity(activity, date2);
|
||||
|
||||
assertThat(stats1.getStatDate()).isEqualTo(date1);
|
||||
assertThat(stats2.getStatDate()).isEqualTo(date2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateExistingEntity_whenStatsAlreadyExist() {
|
||||
Activity activity = createActivity(1L, "Test");
|
||||
DailyActivityStatsEntity existingEntity = new DailyActivityStatsEntity();
|
||||
existingEntity.setId(100L);
|
||||
existingEntity.setActivityId(1L);
|
||||
existingEntity.setStatDate(testDate);
|
||||
existingEntity.setViews(500);
|
||||
existingEntity.setShares(100);
|
||||
existingEntity.setNewRegistrations(30);
|
||||
existingEntity.setConversions(5);
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(1L, testDate))
|
||||
.thenReturn(Optional.of(existingEntity));
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
ArgumentCaptor<DailyActivityStatsEntity> captor = ArgumentCaptor.forClass(DailyActivityStatsEntity.class);
|
||||
verify(dailyStatsRepository, atLeastOnce()).save(captor.capture());
|
||||
|
||||
DailyActivityStatsEntity savedEntity = captor.getValue();
|
||||
assertThat(savedEntity.getId()).isEqualTo(100L);
|
||||
assertThat(savedEntity.getViews()).isBetween(1000, 1499);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateNewEntity_whenStatsDoNotExist() {
|
||||
Activity activity = createActivity(1L, "Test");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(1L, testDate))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
ArgumentCaptor<DailyActivityStatsEntity> captor = ArgumentCaptor.forClass(DailyActivityStatsEntity.class);
|
||||
verify(dailyStatsRepository, atLeastOnce()).save(captor.capture());
|
||||
|
||||
DailyActivityStatsEntity savedEntity = captor.getValue();
|
||||
assertThat(savedEntity.getId()).isNull();
|
||||
assertThat(savedEntity.getActivityId()).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleSingleActivity_whenOnlyOneActivityExists() {
|
||||
Activity activity = createActivity(1L, "Solo Activity");
|
||||
|
||||
when(activityService.getAllActivities()).thenReturn(List.of(activity));
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
job.aggregateDailyStats();
|
||||
|
||||
verify(dailyStatsRepository, times(2)).save(any(DailyActivityStatsEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleManyActivities_whenLargeActivityList() {
|
||||
List<Activity> activities = new ArrayList<>();
|
||||
for (long i = 1; i <= 100; i++) {
|
||||
activities.add(createActivity(i, "Activity " + i));
|
||||
}
|
||||
|
||||
when(activityService.getAllActivities()).thenReturn(activities);
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
job.aggregateDailyStats();
|
||||
|
||||
verify(activityService, times(1)).getAllActivities();
|
||||
verify(dailyStatsRepository, times(200)).save(any(DailyActivityStatsEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGenerateNonNegativeStats_whenRandomValuesGenerated() {
|
||||
Activity activity = createActivity(1L, "Test");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
DailyActivityStats stats = job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
assertThat(stats.getViews()).isGreaterThanOrEqualTo(1000);
|
||||
assertThat(stats.getShares()).isGreaterThanOrEqualTo(200);
|
||||
assertThat(stats.getNewRegistrations()).isGreaterThanOrEqualTo(50);
|
||||
assertThat(stats.getConversions()).isGreaterThanOrEqualTo(10);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldStoreStatsInConcurrentMap_whenAggregated() {
|
||||
Activity activity = createActivity(1L, "Test");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
DailyActivityStats stats = job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
assertThat(stats).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallUpsertDailyStats_whenAggregateStatsForActivity() {
|
||||
Activity activity = createActivity(1L, "Test");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
DailyActivityStats stats = job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
assertThat(stats.getActivityId()).isEqualTo(1L);
|
||||
verify(dailyStatsRepository, atLeastOnce()).save(any(DailyActivityStatsEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseYesterdayDate_whenAggregateDailyStatsCalled() {
|
||||
when(activityService.getAllActivities()).thenReturn(Collections.emptyList());
|
||||
|
||||
job.aggregateDailyStats();
|
||||
|
||||
LocalDate yesterday = LocalDate.now().minusDays(1);
|
||||
verify(activityService, times(1)).getAllActivities();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleActivityWithNullName_whenAggregated() {
|
||||
Activity activity = new Activity();
|
||||
activity.setId(1L);
|
||||
activity.setName(null);
|
||||
activity.setStartTime(ZonedDateTime.now());
|
||||
activity.setEndTime(ZonedDateTime.now().plusDays(1));
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
DailyActivityStats stats = job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
assertThat(stats.getActivityId()).isEqualTo(1L);
|
||||
assertThat(stats.getStatDate()).isEqualTo(testDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPreserveAllStatFields_whenSavingToRepository() {
|
||||
Activity activity = createActivity(1L, "Test");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
ArgumentCaptor<DailyActivityStatsEntity> captor = ArgumentCaptor.forClass(DailyActivityStatsEntity.class);
|
||||
verify(dailyStatsRepository, atLeastOnce()).save(captor.capture());
|
||||
|
||||
DailyActivityStatsEntity saved = captor.getValue();
|
||||
assertThat(saved.getActivityId()).isNotNull();
|
||||
assertThat(saved.getStatDate()).isNotNull();
|
||||
assertThat(saved.getViews()).isNotNull();
|
||||
assertThat(saved.getShares()).isNotNull();
|
||||
assertThat(saved.getNewRegistrations()).isNotNull();
|
||||
assertThat(saved.getConversions()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleActivityWithZeroId_whenAggregated() {
|
||||
Activity activity = createActivity(0L, "Zero ID Activity");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(0L, testDate))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
DailyActivityStats stats = job.aggregateStatsForActivity(activity, testDate);
|
||||
|
||||
assertThat(stats.getActivityId()).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGenerateStatsWithinExpectedRanges_whenMultipleCalls() {
|
||||
Activity activity = createActivity(1L, "Test");
|
||||
|
||||
when(dailyStatsRepository.findByActivityIdAndStatDate(any(), any()))
|
||||
.thenReturn(Optional.empty());
|
||||
when(dailyStatsRepository.save(any(DailyActivityStatsEntity.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
List<DailyActivityStats> allStats = new ArrayList<>();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
allStats.add(job.aggregateStatsForActivity(activity, testDate));
|
||||
}
|
||||
|
||||
assertThat(allStats)
|
||||
.allMatch(s -> s.getViews() >= 1000 && s.getViews() < 1500)
|
||||
.allMatch(s -> s.getShares() >= 200 && s.getShares() < 300)
|
||||
.allMatch(s -> s.getNewRegistrations() >= 50 && s.getNewRegistrations() < 100)
|
||||
.allMatch(s -> s.getConversions() >= 10 && s.getConversions() < 30);
|
||||
}
|
||||
|
||||
private Activity createActivity(Long id, String name) {
|
||||
Activity activity = new Activity();
|
||||
activity.setId(id);
|
||||
activity.setName(name);
|
||||
activity.setStartTime(ZonedDateTime.now());
|
||||
activity.setEndTime(ZonedDateTime.now().plusDays(1));
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ class StatisticsAggregationJobTest {
|
||||
@Mock
|
||||
private ActivityService activityService;
|
||||
|
||||
@Mock
|
||||
private com.mosquito.project.persistence.repository.DailyActivityStatsRepository dailyActivityStatsRepository;
|
||||
|
||||
@InjectMocks
|
||||
private StatisticsAggregationJob statisticsAggregationJob;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user