| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- package service
- import (
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // --- Task 5: 验证 calculateProgress 纯函数行为正确 ---
- func newTestSubscriptionService() *SubscriptionService {
- return &SubscriptionService{}
- }
- func ptrFloat64(v float64) *float64 { return &v }
- func ptrTime(t time.Time) *time.Time { return &t }
- func TestCalculateProgress_BasicFields(t *testing.T) {
- svc := newTestSubscriptionService()
- now := time.Now()
- sub := &UserSubscription{
- ID: 100,
- ExpiresAt: now.Add(30 * 24 * time.Hour),
- }
- group := &Group{
- Name: "Premium",
- }
- progress := svc.calculateProgress(sub, group)
- assert.Equal(t, int64(100), progress.ID)
- assert.Equal(t, "Premium", progress.GroupName)
- assert.Equal(t, sub.ExpiresAt, progress.ExpiresAt)
- assert.Equal(t, 30, progress.ExpiresInDays)
- assert.Nil(t, progress.Daily, "无日限额时 Daily 应为 nil")
- assert.Nil(t, progress.Weekly, "无周限额时 Weekly 应为 nil")
- assert.Nil(t, progress.Monthly, "无月限额时 Monthly 应为 nil")
- }
- func TestCalculateProgress_DailyUsage(t *testing.T) {
- svc := newTestSubscriptionService()
- now := time.Now()
- dailyStart := now.Add(-12 * time.Hour)
- sub := &UserSubscription{
- ID: 1,
- ExpiresAt: now.Add(10 * 24 * time.Hour),
- DailyUsageUSD: 3.0,
- DailyWindowStart: ptrTime(dailyStart),
- }
- group := &Group{
- Name: "Pro",
- DailyLimitUSD: ptrFloat64(10.0),
- }
- progress := svc.calculateProgress(sub, group)
- require.NotNil(t, progress.Daily, "有日限额和窗口时 Daily 不应为 nil")
- assert.Equal(t, 10.0, progress.Daily.LimitUSD)
- assert.Equal(t, 3.0, progress.Daily.UsedUSD)
- assert.Equal(t, 7.0, progress.Daily.RemainingUSD)
- assert.Equal(t, 30.0, progress.Daily.Percentage)
- assert.Equal(t, dailyStart, progress.Daily.WindowStart)
- }
- func TestCalculateProgress_DailyCardUsesExpiryAsDailyResetTime(t *testing.T) {
- svc := newTestSubscriptionService()
- startsAt := time.Now().Add(-12 * time.Hour)
- dailyStart := time.Date(startsAt.Year(), startsAt.Month(), startsAt.Day(), 0, 0, 0, 0, startsAt.Location())
- expiresAt := startsAt.Add(24 * time.Hour)
- sub := &UserSubscription{
- ID: 1,
- StartsAt: startsAt,
- ExpiresAt: expiresAt,
- DailyUsageUSD: 3.0,
- DailyWindowStart: ptrTime(dailyStart),
- }
- group := &Group{
- Name: "Daily",
- DailyLimitUSD: ptrFloat64(10.0),
- }
- progress := svc.calculateProgress(sub, group)
- require.NotNil(t, progress.Daily, "日卡有日限额和窗口时 Daily 不应为 nil")
- assert.Equal(t, expiresAt, progress.Daily.ResetsAt, "日卡的一次性日额度结束时间应为订阅过期时间")
- }
- func TestCalculateProgress_WeeklyUsage(t *testing.T) {
- svc := newTestSubscriptionService()
- now := time.Now()
- weeklyStart := now.Add(-3 * 24 * time.Hour)
- sub := &UserSubscription{
- ID: 1,
- ExpiresAt: now.Add(10 * 24 * time.Hour),
- WeeklyUsageUSD: 25.0,
- WeeklyWindowStart: ptrTime(weeklyStart),
- }
- group := &Group{
- Name: "Pro",
- WeeklyLimitUSD: ptrFloat64(50.0),
- }
- progress := svc.calculateProgress(sub, group)
- require.NotNil(t, progress.Weekly, "有周限额和窗口时 Weekly 不应为 nil")
- assert.Equal(t, 50.0, progress.Weekly.LimitUSD)
- assert.Equal(t, 25.0, progress.Weekly.UsedUSD)
- assert.Equal(t, 25.0, progress.Weekly.RemainingUSD)
- assert.Equal(t, 50.0, progress.Weekly.Percentage)
- }
- // 周窗口初始化在开通日零点(legacy anchor)时,展示的 ResetsAt 应与
- // automaticWindowStartAt 的实际推进时间一致(StartsAt+7d),而非窗口起点+7d。
- func TestCalculateProgress_WeeklyResetsAt_LegacyMidnightAnchor(t *testing.T) {
- svc := newTestSubscriptionService()
- startsAt := time.Date(2026, 7, 31, 13, 37, 6, 0, time.FixedZone("UTC+8", 8*3600))
- weeklyStart := time.Date(startsAt.Year(), startsAt.Month(), startsAt.Day(), 0, 0, 0, 0, startsAt.Location())
- sub := &UserSubscription{
- ID: 1,
- StartsAt: startsAt,
- ExpiresAt: startsAt.AddDate(0, 0, 30),
- WeeklyUsageUSD: 2000.18,
- WeeklyWindowStart: ptrTime(weeklyStart),
- }
- group := &Group{
- Name: "Pro",
- WeeklyLimitUSD: ptrFloat64(2000.0),
- }
- progress := svc.calculateProgress(sub, group)
- require.NotNil(t, progress.Weekly)
- assert.True(t, startsAt.Add(7*24*time.Hour).Equal(progress.Weekly.ResetsAt),
- "legacy 午夜锚点的周窗口 ResetsAt 应为 StartsAt+7d,与实际推进时间一致")
- }
- func TestCalculateProgress_MonthlyUsage(t *testing.T) {
- svc := newTestSubscriptionService()
- now := time.Now()
- monthlyStart := now.Add(-15 * 24 * time.Hour)
- sub := &UserSubscription{
- ID: 1,
- ExpiresAt: now.Add(10 * 24 * time.Hour),
- MonthlyUsageUSD: 80.0,
- MonthlyWindowStart: ptrTime(monthlyStart),
- }
- group := &Group{
- Name: "Enterprise",
- MonthlyLimitUSD: ptrFloat64(100.0),
- }
- progress := svc.calculateProgress(sub, group)
- require.NotNil(t, progress.Monthly, "有月限额和窗口时 Monthly 不应为 nil")
- assert.Equal(t, 100.0, progress.Monthly.LimitUSD)
- assert.Equal(t, 80.0, progress.Monthly.UsedUSD)
- assert.Equal(t, 20.0, progress.Monthly.RemainingUSD)
- assert.Equal(t, 80.0, progress.Monthly.Percentage)
- }
- func TestCalculateProgress_OverLimit_ClampedTo100Percent(t *testing.T) {
- svc := newTestSubscriptionService()
- now := time.Now()
- sub := &UserSubscription{
- ID: 1,
- ExpiresAt: now.Add(10 * 24 * time.Hour),
- DailyUsageUSD: 15.0, // 超过限额
- DailyWindowStart: ptrTime(now.Add(-1 * time.Hour)),
- }
- group := &Group{
- Name: "Pro",
- DailyLimitUSD: ptrFloat64(10.0),
- }
- progress := svc.calculateProgress(sub, group)
- require.NotNil(t, progress.Daily)
- assert.Equal(t, 100.0, progress.Daily.Percentage, "超额使用应被截断为 100%")
- assert.Equal(t, 0.0, progress.Daily.RemainingUSD, "超额使用时剩余应为 0")
- }
- func TestCalculateProgress_NoWindowStart_NoProgress(t *testing.T) {
- svc := newTestSubscriptionService()
- now := time.Now()
- // 有限额但无窗口起始时间(订阅未激活)
- sub := &UserSubscription{
- ID: 1,
- ExpiresAt: now.Add(10 * 24 * time.Hour),
- DailyUsageUSD: 0,
- WeeklyUsageUSD: 0,
- }
- group := &Group{
- Name: "Pro",
- DailyLimitUSD: ptrFloat64(10.0),
- WeeklyLimitUSD: ptrFloat64(50.0),
- }
- progress := svc.calculateProgress(sub, group)
- assert.Nil(t, progress.Daily, "无 DailyWindowStart 时 Daily 应为 nil")
- assert.Nil(t, progress.Weekly, "无 WeeklyWindowStart 时 Weekly 应为 nil")
- }
- func TestCalculateProgress_AllLimits(t *testing.T) {
- svc := newTestSubscriptionService()
- now := time.Now()
- sub := &UserSubscription{
- ID: 1,
- ExpiresAt: now.Add(10 * 24 * time.Hour),
- DailyUsageUSD: 5.0,
- WeeklyUsageUSD: 20.0,
- MonthlyUsageUSD: 60.0,
- DailyWindowStart: ptrTime(now.Add(-6 * time.Hour)),
- WeeklyWindowStart: ptrTime(now.Add(-3 * 24 * time.Hour)),
- MonthlyWindowStart: ptrTime(now.Add(-15 * 24 * time.Hour)),
- }
- group := &Group{
- Name: "Full",
- DailyLimitUSD: ptrFloat64(10.0),
- WeeklyLimitUSD: ptrFloat64(50.0),
- MonthlyLimitUSD: ptrFloat64(100.0),
- }
- progress := svc.calculateProgress(sub, group)
- require.NotNil(t, progress.Daily)
- require.NotNil(t, progress.Weekly)
- require.NotNil(t, progress.Monthly)
- assert.Equal(t, 50.0, progress.Daily.Percentage)
- assert.Equal(t, 40.0, progress.Weekly.Percentage)
- assert.Equal(t, 60.0, progress.Monthly.Percentage)
- }
- func TestCalculateProgress_ExpiredSubscription(t *testing.T) {
- svc := newTestSubscriptionService()
- sub := &UserSubscription{
- ID: 1,
- ExpiresAt: time.Now().Add(-24 * time.Hour), // 已过期
- }
- group := &Group{Name: "Expired"}
- progress := svc.calculateProgress(sub, group)
- assert.Equal(t, 0, progress.ExpiresInDays, "过期订阅的剩余天数应为 0")
- }
- func TestCalculateProgress_ResetsInSeconds_NotNegative(t *testing.T) {
- svc := newTestSubscriptionService()
- // 使用过去的窗口起始时间,使得重置时间已过
- pastStart := time.Now().Add(-48 * time.Hour)
- sub := &UserSubscription{
- ID: 1,
- ExpiresAt: time.Now().Add(10 * 24 * time.Hour),
- DailyUsageUSD: 1.0,
- DailyWindowStart: ptrTime(pastStart),
- }
- group := &Group{
- Name: "Test",
- DailyLimitUSD: ptrFloat64(10.0),
- }
- progress := svc.calculateProgress(sub, group)
- require.NotNil(t, progress.Daily)
- assert.GreaterOrEqual(t, progress.Daily.ResetsInSeconds, int64(0),
- "ResetsInSeconds 不应为负数")
- }
|