user_subscription_reset_time_test.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package service
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. )
  9. // 周窗口初始化在开通日零点(legacy anchor)时,automaticWindowStartAt 会把实际
  10. // 推进锚点修正为 StartsAt。展示层的 WeeklyResetTime 必须应用同一修正,
  11. // 否则用户看到的重置时间会比实际重置时间早(差值 = StartsAt 的时分秒)。
  12. func TestWeeklyResetTime_LegacyMidnightAnchor_UsesStartsAt(t *testing.T) {
  13. startsAt := time.Date(2026, 7, 31, 13, 37, 6, 0, time.FixedZone("UTC+8", 8*3600))
  14. windowStart := startOfDay(startsAt)
  15. sub := &UserSubscription{
  16. StartsAt: startsAt,
  17. ExpiresAt: startsAt.AddDate(0, 0, 30),
  18. WeeklyWindowStart: ptrTime(windowStart),
  19. }
  20. got := sub.WeeklyResetTime()
  21. require.NotNil(t, got)
  22. assert.True(t, startsAt.Add(7*24*time.Hour).Equal(*got),
  23. "legacy 午夜锚点应按 StartsAt+7d 计算重置时间,而不是窗口起点+7d")
  24. }
  25. // 非 legacy 锚点(手动重置或已推进过的窗口)保持权威,不做修正。
  26. func TestWeeklyResetTime_RegularAnchor_UsesWindowStart(t *testing.T) {
  27. startsAt := time.Date(2026, 7, 31, 13, 37, 6, 0, time.FixedZone("UTC+8", 8*3600))
  28. windowStart := startsAt.Add(7 * 24 * time.Hour) // 已推进过一轮,非开通日零点
  29. sub := &UserSubscription{
  30. StartsAt: startsAt,
  31. ExpiresAt: startsAt.AddDate(0, 0, 30),
  32. WeeklyWindowStart: ptrTime(windowStart),
  33. }
  34. got := sub.WeeklyResetTime()
  35. require.NotNil(t, got)
  36. assert.True(t, windowStart.Add(7*24*time.Hour).Equal(*got),
  37. "非 legacy 锚点应按窗口起点+7d 计算重置时间")
  38. }
  39. // 展示与执行一致性:WeeklyResetTime 前一秒窗口不应推进,到点后应推进。
  40. func TestWeeklyResetTime_MatchesAutomaticWindowStart(t *testing.T) {
  41. startsAt := time.Date(2026, 7, 31, 13, 37, 6, 0, time.FixedZone("UTC+8", 8*3600))
  42. windowStart := startOfDay(startsAt)
  43. sub := &UserSubscription{
  44. StartsAt: startsAt,
  45. ExpiresAt: startsAt.AddDate(0, 0, 30),
  46. WeeklyWindowStart: ptrTime(windowStart),
  47. }
  48. resetAt := *sub.WeeklyResetTime()
  49. _, ok := sub.automaticWindowStartAt(sub.WeeklyWindowStart, 7*24*time.Hour, resetAt.Add(-time.Second))
  50. assert.False(t, ok, "展示的重置时间之前窗口不应可推进")
  51. newStart, ok := sub.automaticWindowStartAt(sub.WeeklyWindowStart, 7*24*time.Hour, resetAt)
  52. assert.True(t, ok, "到达展示的重置时间后窗口应可推进")
  53. assert.True(t, resetAt.Equal(newStart), "推进后的新窗口起点应等于展示的重置时间")
  54. }
  55. // 月窗口与周窗口同属期限对齐滚动窗口,应用同一 legacy 锚点修正。
  56. // 日窗口按日历日对齐(见 automaticDailyWindowStartAt),不走此修正,故不在此覆盖。
  57. func TestMonthlyResetTime_LegacyMidnightAnchor_UsesStartsAt(t *testing.T) {
  58. startsAt := time.Date(2026, 7, 31, 13, 37, 6, 0, time.FixedZone("UTC+8", 8*3600))
  59. windowStart := startOfDay(startsAt)
  60. sub := &UserSubscription{
  61. StartsAt: startsAt,
  62. ExpiresAt: startsAt.AddDate(0, 0, 60),
  63. MonthlyWindowStart: ptrTime(windowStart),
  64. }
  65. monthly := sub.MonthlyResetTime()
  66. require.NotNil(t, monthly)
  67. assert.True(t, startsAt.Add(30*24*time.Hour).Equal(*monthly),
  68. "legacy 午夜锚点应按 StartsAt+30d 计算重置时间,而不是窗口起点+30d")
  69. }
  70. // 日窗口不受本修正影响:DailyResetTime 保持 #5380 的日历日对齐语义。
  71. // 基准取配置时区的 0 点(与 subscription_daily_midnight_reset_test.go 同构),
  72. // 保证断言在任意本地时区下都成立。
  73. func TestDailyResetTime_UnaffectedByWindowResetAnchor(t *testing.T) {
  74. base := timezone.StartOfDay(time.Date(2026, 7, 31, 12, 0, 0, 0, timezone.Location()))
  75. startsAt := base.Add(13*time.Hour + 37*time.Minute + 6*time.Second)
  76. windowStart := base // legacy 锚点:开通日 0 点
  77. sub := &UserSubscription{
  78. StartsAt: startsAt,
  79. ExpiresAt: startsAt.AddDate(0, 0, 30),
  80. DailyWindowStart: ptrTime(windowStart),
  81. }
  82. got := sub.DailyResetTime()
  83. require.NotNil(t, got)
  84. assert.True(t, got.Equal(base.AddDate(0, 0, 1)),
  85. "日窗口应保持日历日对齐(窗口起点所在日的次日 0 点)")
  86. assert.False(t, got.Equal(startsAt.Add(24*time.Hour)),
  87. "日窗口不应被 windowResetAnchor 修正成 StartsAt+24h(那是周/月的语义)")
  88. }