sticky_session_test.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. //go:build unit
  2. // Package service 提供 API 网关核心服务。
  3. // 本文件包含 shouldClearStickySession 函数的单元测试,
  4. // 验证粘性会话清理逻辑在各种账号状态下的正确行为。
  5. //
  6. // This file contains unit tests for the shouldClearStickySession function,
  7. // verifying correct sticky session clearing behavior under various account states.
  8. package service
  9. import (
  10. "testing"
  11. "time"
  12. "github.com/stretchr/testify/require"
  13. )
  14. // TestShouldClearStickySession tests sticky session clearing via IsSchedulable() delegation
  15. // plus model-level rate limiting.
  16. func TestShouldClearStickySession(t *testing.T) {
  17. now := time.Now()
  18. future := now.Add(1 * time.Hour)
  19. past := now.Add(-1 * time.Hour)
  20. // 短限流时间(有限流即清除粘性会话)
  21. shortRateLimitReset := now.Add(5 * time.Second).Format(time.RFC3339)
  22. // 长限流时间(有限流即清除粘性会话)
  23. longRateLimitReset := now.Add(30 * time.Second).Format(time.RFC3339)
  24. tests := []struct {
  25. name string
  26. account *Account
  27. requestedModel string
  28. want bool
  29. }{
  30. {name: "nil account", account: nil, requestedModel: "", want: false},
  31. {name: "status error", account: &Account{Status: StatusError, Schedulable: true}, requestedModel: "", want: true},
  32. {name: "status disabled", account: &Account{Status: StatusDisabled, Schedulable: true}, requestedModel: "", want: true},
  33. {name: "schedulable false", account: &Account{Status: StatusActive, Schedulable: false}, requestedModel: "", want: true},
  34. {name: "temp unschedulable", account: &Account{Status: StatusActive, Schedulable: true, TempUnschedulableUntil: &future}, requestedModel: "", want: true},
  35. {name: "temp unschedulable expired", account: &Account{Status: StatusActive, Schedulable: true, TempUnschedulableUntil: &past}, requestedModel: "", want: false},
  36. {name: "active schedulable", account: &Account{Status: StatusActive, Schedulable: true}, requestedModel: "", want: false},
  37. // 模型限流测试:有限流即清除
  38. {
  39. name: "model rate limited short duration",
  40. account: &Account{
  41. Status: StatusActive,
  42. Schedulable: true,
  43. Extra: map[string]any{
  44. "model_rate_limits": map[string]any{
  45. "claude-sonnet-4": map[string]any{
  46. "rate_limit_reset_at": shortRateLimitReset,
  47. },
  48. },
  49. },
  50. },
  51. requestedModel: "claude-sonnet-4",
  52. want: true, // 有限流即清除
  53. },
  54. {
  55. name: "model rate limited long duration",
  56. account: &Account{
  57. Status: StatusActive,
  58. Schedulable: true,
  59. Extra: map[string]any{
  60. "model_rate_limits": map[string]any{
  61. "claude-sonnet-4": map[string]any{
  62. "rate_limit_reset_at": longRateLimitReset,
  63. },
  64. },
  65. },
  66. },
  67. requestedModel: "claude-sonnet-4",
  68. want: true, // 有限流即清除
  69. },
  70. {
  71. name: "model rate limited different model",
  72. account: &Account{
  73. Status: StatusActive,
  74. Schedulable: true,
  75. Extra: map[string]any{
  76. "model_rate_limits": map[string]any{
  77. "claude-sonnet-4": map[string]any{
  78. "rate_limit_reset_at": longRateLimitReset,
  79. },
  80. },
  81. },
  82. },
  83. requestedModel: "claude-opus-4", // 请求不同模型
  84. want: false, // 不同模型不受影响
  85. },
  86. {
  87. name: "apikey quota exceeded",
  88. account: &Account{
  89. Status: StatusActive,
  90. Schedulable: true,
  91. Type: AccountTypeAPIKey,
  92. Extra: map[string]any{
  93. "quota_daily_limit": 10.0,
  94. "quota_daily_used": 10.0,
  95. "quota_daily_start": now.Add(-1 * time.Hour).Format(time.RFC3339),
  96. },
  97. },
  98. requestedModel: "",
  99. want: true,
  100. },
  101. {
  102. name: "oauth quota exceeded not cleared",
  103. account: &Account{
  104. Status: StatusActive,
  105. Schedulable: true,
  106. Type: AccountTypeOAuth,
  107. Extra: map[string]any{
  108. "quota_daily_limit": 10.0,
  109. "quota_daily_used": 10.0,
  110. "quota_daily_start": now.Add(-1 * time.Hour).Format(time.RFC3339),
  111. },
  112. },
  113. requestedModel: "",
  114. want: false,
  115. },
  116. {
  117. name: "overloaded account",
  118. account: &Account{
  119. Status: StatusActive,
  120. Schedulable: true,
  121. OverloadUntil: &future,
  122. },
  123. requestedModel: "",
  124. want: true,
  125. },
  126. {
  127. name: "account-level rate limited",
  128. account: &Account{
  129. Status: StatusActive,
  130. Schedulable: true,
  131. RateLimitResetAt: &future,
  132. },
  133. requestedModel: "",
  134. want: true,
  135. },
  136. }
  137. for _, tt := range tests {
  138. t.Run(tt.name, func(t *testing.T) {
  139. require.Equal(t, tt.want, shouldClearStickySession(tt.account, tt.requestedModel))
  140. })
  141. }
  142. }