From d0fa8c63f171459e9878bd8a88a30fcc999035f5 Mon Sep 17 00:00:00 2001 From: cugxuan Date: Sun, 19 Jul 2026 21:37:26 +0800 Subject: [PATCH] fix(subscription): round remaining days up --- .../subscription_calculate_progress_test.go | 2 +- backend/internal/service/user_subscription.go | 16 +++++++- .../user_subscription_days_remaining_test.go | 37 +++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 backend/internal/service/user_subscription_days_remaining_test.go diff --git a/backend/internal/service/subscription_calculate_progress_test.go b/backend/internal/service/subscription_calculate_progress_test.go index 650522d54c..8a3f8bee8e 100644 --- a/backend/internal/service/subscription_calculate_progress_test.go +++ b/backend/internal/service/subscription_calculate_progress_test.go @@ -34,7 +34,7 @@ func TestCalculateProgress_BasicFields(t *testing.T) { assert.Equal(t, int64(100), progress.ID) assert.Equal(t, "Premium", progress.GroupName) assert.Equal(t, sub.ExpiresAt, progress.ExpiresAt) - assert.True(t, progress.ExpiresInDays == 29 || progress.ExpiresInDays == 30, "ExpiresInDays should be 29 or 30, got %d", progress.ExpiresInDays) + 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") diff --git a/backend/internal/service/user_subscription.go b/backend/internal/service/user_subscription.go index 732401ada2..12e3dd428a 100644 --- a/backend/internal/service/user_subscription.go +++ b/backend/internal/service/user_subscription.go @@ -2,6 +2,8 @@ package service import "time" +const subscriptionDayDuration = 24 * time.Hour + type UserSubscription struct { ID int64 UserID int64 @@ -41,10 +43,20 @@ func (s *UserSubscription) IsExpired() bool { } func (s *UserSubscription) DaysRemaining() int { - if s.IsExpired() { + return s.daysRemainingAt(time.Now()) +} + +func (s *UserSubscription) daysRemainingAt(now time.Time) int { + remaining := s.ExpiresAt.Sub(now) + if remaining <= 0 { return 0 } - return int(time.Until(s.ExpiresAt).Hours() / 24) + + days := int(remaining / subscriptionDayDuration) + if remaining%subscriptionDayDuration != 0 { + days++ + } + return days } func (s *UserSubscription) IsWindowActivated() bool { diff --git a/backend/internal/service/user_subscription_days_remaining_test.go b/backend/internal/service/user_subscription_days_remaining_test.go new file mode 100644 index 0000000000..571d250f73 --- /dev/null +++ b/backend/internal/service/user_subscription_days_remaining_test.go @@ -0,0 +1,37 @@ +//go:build unit + +package service + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestUserSubscriptionDaysRemainingAt(t *testing.T) { + now := time.Date(2026, time.July, 19, 12, 0, 0, 0, time.UTC) + tests := []struct { + name string + expiresAt time.Time + want int + }{ + {name: "expired", expiresAt: now.Add(-time.Nanosecond), want: 0}, + {name: "expires now", expiresAt: now, want: 0}, + {name: "less than one day", expiresAt: now.Add(subscriptionDayDuration - time.Nanosecond), want: 1}, + {name: "exactly one day", expiresAt: now.Add(subscriptionDayDuration), want: 1}, + {name: "over one day", expiresAt: now.Add(subscriptionDayDuration + time.Nanosecond), want: 2}, + {name: "less than two days", expiresAt: now.Add(2*subscriptionDayDuration - time.Nanosecond), want: 2}, + {name: "exactly two days", expiresAt: now.Add(2 * subscriptionDayDuration), want: 2}, + {name: "over two days", expiresAt: now.Add(2*subscriptionDayDuration + time.Nanosecond), want: 3}, + {name: "exactly seven days", expiresAt: now.Add(7 * subscriptionDayDuration), want: 7}, + {name: "over seven days", expiresAt: now.Add(7*subscriptionDayDuration + time.Nanosecond), want: 8}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sub := &UserSubscription{ExpiresAt: tt.expiresAt} + require.Equal(t, tt.want, sub.daysRemainingAt(now)) + }) + } +}