package service import ( "context" "errors" "testing" "time" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/stretchr/testify/require" ) type dashboardAggregationRepoTestStub struct { aggregateCalls int lastStart time.Time lastEnd time.Time watermark time.Time aggregateErr error cleanupAggregatesErr error cleanupUsageErr error } func (s *dashboardAggregationRepoTestStub) AggregateRange(ctx context.Context, start, end time.Time) error { s.aggregateCalls++ s.lastStart = start s.lastEnd = end return s.aggregateErr } func (s *dashboardAggregationRepoTestStub) RecomputeRange(ctx context.Context, start, end time.Time) error { return s.AggregateRange(ctx, start, end) } func (s *dashboardAggregationRepoTestStub) GetAggregationWatermark(ctx context.Context) (time.Time, error) { return s.watermark, nil } func (s *dashboardAggregationRepoTestStub) UpdateAggregationWatermark(ctx context.Context, aggregatedAt time.Time) error { return nil } func (s *dashboardAggregationRepoTestStub) CleanupAggregates(ctx context.Context, hourlyCutoff, dailyCutoff time.Time) error { return s.cleanupAggregatesErr } func (s *dashboardAggregationRepoTestStub) CleanupUsageLogs(ctx context.Context, cutoff time.Time) error { return s.cleanupUsageErr } func (s *dashboardAggregationRepoTestStub) EnsureUsageLogsPartitions(ctx context.Context, now time.Time) error { return nil } func TestDashboardAggregationService_RunScheduledAggregation_EpochUsesRetentionStart(t *testing.T) { repo := &dashboardAggregationRepoTestStub{watermark: time.Unix(0, 0).UTC()} svc := &DashboardAggregationService{ repo: repo, cfg: config.DashboardAggregationConfig{ Enabled: true, IntervalSeconds: 60, LookbackSeconds: 120, Retention: config.DashboardAggregationRetentionConfig{ UsageLogsDays: 1, HourlyDays: 1, DailyDays: 1, }, }, } svc.runScheduledAggregation() require.Equal(t, 1, repo.aggregateCalls) require.False(t, repo.lastEnd.IsZero()) require.Equal(t, truncateToDayUTC(repo.lastEnd.AddDate(0, 0, -1)), repo.lastStart) } func TestDashboardAggregationService_CleanupRetentionFailure_DoesNotRecord(t *testing.T) { repo := &dashboardAggregationRepoTestStub{cleanupAggregatesErr: errors.New("清理失败")} svc := &DashboardAggregationService{ repo: repo, cfg: config.DashboardAggregationConfig{ Retention: config.DashboardAggregationRetentionConfig{ UsageLogsDays: 1, HourlyDays: 1, DailyDays: 1, }, }, } svc.maybeCleanupRetention(context.Background(), time.Now().UTC()) require.Nil(t, svc.lastRetentionCleanup.Load()) } func TestDashboardAggregationService_TriggerBackfill_TooLarge(t *testing.T) { repo := &dashboardAggregationRepoTestStub{} svc := &DashboardAggregationService{ repo: repo, cfg: config.DashboardAggregationConfig{ BackfillEnabled: true, BackfillMaxDays: 1, }, } start := time.Now().AddDate(0, 0, -3) end := time.Now() err := svc.TriggerBackfill(start, end) require.ErrorIs(t, err, ErrDashboardBackfillTooLarge) require.Equal(t, 0, repo.aggregateCalls) }