token_refresher_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. //go:build unit
  2. package service
  3. import (
  4. "strconv"
  5. "testing"
  6. "time"
  7. "github.com/stretchr/testify/require"
  8. )
  9. func TestClaudeTokenRefresher_NeedsRefresh(t *testing.T) {
  10. refresher := &ClaudeTokenRefresher{}
  11. refreshWindow := 30 * time.Minute
  12. tests := []struct {
  13. name string
  14. credentials map[string]any
  15. wantRefresh bool
  16. }{
  17. {
  18. name: "expires_at as string - expired",
  19. credentials: map[string]any{
  20. "expires_at": "1000", // 1970-01-01 00:16:40 UTC, 已过期
  21. },
  22. wantRefresh: true,
  23. },
  24. {
  25. name: "expires_at as float64 - expired",
  26. credentials: map[string]any{
  27. "expires_at": float64(1000), // 数字类型,已过期
  28. },
  29. wantRefresh: true,
  30. },
  31. {
  32. name: "expires_at as RFC3339 - expired",
  33. credentials: map[string]any{
  34. "expires_at": "1970-01-01T00:00:00Z", // RFC3339 格式,已过期
  35. },
  36. wantRefresh: true,
  37. },
  38. {
  39. name: "expires_at as string - far future",
  40. credentials: map[string]any{
  41. "expires_at": "9999999999", // 远未来
  42. },
  43. wantRefresh: false,
  44. },
  45. {
  46. name: "expires_at as float64 - far future",
  47. credentials: map[string]any{
  48. "expires_at": float64(9999999999), // 远未来,数字类型
  49. },
  50. wantRefresh: false,
  51. },
  52. {
  53. name: "expires_at as RFC3339 - far future",
  54. credentials: map[string]any{
  55. "expires_at": "2099-12-31T23:59:59Z", // RFC3339 格式,远未来
  56. },
  57. wantRefresh: false,
  58. },
  59. {
  60. name: "expires_at missing",
  61. credentials: map[string]any{},
  62. wantRefresh: false,
  63. },
  64. {
  65. name: "expires_at is nil",
  66. credentials: map[string]any{
  67. "expires_at": nil,
  68. },
  69. wantRefresh: false,
  70. },
  71. {
  72. name: "expires_at is invalid string",
  73. credentials: map[string]any{
  74. "expires_at": "invalid",
  75. },
  76. wantRefresh: false,
  77. },
  78. {
  79. name: "credentials is nil",
  80. credentials: nil,
  81. wantRefresh: false,
  82. },
  83. }
  84. for _, tt := range tests {
  85. t.Run(tt.name, func(t *testing.T) {
  86. account := &Account{
  87. Platform: PlatformAnthropic,
  88. Type: AccountTypeOAuth,
  89. Credentials: tt.credentials,
  90. }
  91. got := refresher.NeedsRefresh(account, refreshWindow)
  92. require.Equal(t, tt.wantRefresh, got)
  93. })
  94. }
  95. }
  96. func TestClaudeTokenRefresher_NeedsRefresh_WithinWindow(t *testing.T) {
  97. refresher := &ClaudeTokenRefresher{}
  98. refreshWindow := 30 * time.Minute
  99. // 设置一个在刷新窗口内的时间(当前时间 + 15分钟)
  100. expiresAt := time.Now().Add(15 * time.Minute).Unix()
  101. tests := []struct {
  102. name string
  103. credentials map[string]any
  104. }{
  105. {
  106. name: "string type - within refresh window",
  107. credentials: map[string]any{
  108. "expires_at": strconv.FormatInt(expiresAt, 10),
  109. },
  110. },
  111. {
  112. name: "float64 type - within refresh window",
  113. credentials: map[string]any{
  114. "expires_at": float64(expiresAt),
  115. },
  116. },
  117. }
  118. for _, tt := range tests {
  119. t.Run(tt.name, func(t *testing.T) {
  120. account := &Account{
  121. Platform: PlatformAnthropic,
  122. Type: AccountTypeOAuth,
  123. Credentials: tt.credentials,
  124. }
  125. got := refresher.NeedsRefresh(account, refreshWindow)
  126. require.True(t, got, "should need refresh when within window")
  127. })
  128. }
  129. }
  130. func TestClaudeTokenRefresher_NeedsRefresh_OutsideWindow(t *testing.T) {
  131. refresher := &ClaudeTokenRefresher{}
  132. refreshWindow := 30 * time.Minute
  133. // 设置一个在刷新窗口外的时间(当前时间 + 1小时)
  134. expiresAt := time.Now().Add(1 * time.Hour).Unix()
  135. tests := []struct {
  136. name string
  137. credentials map[string]any
  138. }{
  139. {
  140. name: "string type - outside refresh window",
  141. credentials: map[string]any{
  142. "expires_at": strconv.FormatInt(expiresAt, 10),
  143. },
  144. },
  145. {
  146. name: "float64 type - outside refresh window",
  147. credentials: map[string]any{
  148. "expires_at": float64(expiresAt),
  149. },
  150. },
  151. }
  152. for _, tt := range tests {
  153. t.Run(tt.name, func(t *testing.T) {
  154. account := &Account{
  155. Platform: PlatformAnthropic,
  156. Type: AccountTypeOAuth,
  157. Credentials: tt.credentials,
  158. }
  159. got := refresher.NeedsRefresh(account, refreshWindow)
  160. require.False(t, got, "should not need refresh when outside window")
  161. })
  162. }
  163. }
  164. func TestClaudeTokenRefresher_CanRefresh(t *testing.T) {
  165. refresher := &ClaudeTokenRefresher{}
  166. tests := []struct {
  167. name string
  168. platform string
  169. accType string
  170. want bool
  171. }{
  172. {
  173. name: "anthropic oauth - can refresh",
  174. platform: PlatformAnthropic,
  175. accType: AccountTypeOAuth,
  176. want: true,
  177. },
  178. {
  179. name: "anthropic setup-token - can refresh",
  180. platform: PlatformAnthropic,
  181. accType: AccountTypeSetupToken,
  182. want: true,
  183. },
  184. {
  185. name: "anthropic api-key - cannot refresh",
  186. platform: PlatformAnthropic,
  187. accType: AccountTypeAPIKey,
  188. want: false,
  189. },
  190. {
  191. name: "openai oauth - cannot refresh",
  192. platform: PlatformOpenAI,
  193. accType: AccountTypeOAuth,
  194. want: false,
  195. },
  196. {
  197. name: "gemini oauth - cannot refresh",
  198. platform: PlatformGemini,
  199. accType: AccountTypeOAuth,
  200. want: false,
  201. },
  202. }
  203. for _, tt := range tests {
  204. t.Run(tt.name, func(t *testing.T) {
  205. account := &Account{
  206. Platform: tt.platform,
  207. Type: tt.accType,
  208. }
  209. got := refresher.CanRefresh(account)
  210. require.Equal(t, tt.want, got)
  211. })
  212. }
  213. }
  214. func TestOpenAITokenRefresher_CanRefresh(t *testing.T) {
  215. refresher := &OpenAITokenRefresher{}
  216. tests := []struct {
  217. name string
  218. platform string
  219. accType string
  220. want bool
  221. }{
  222. {
  223. name: "openai oauth - can refresh",
  224. platform: PlatformOpenAI,
  225. accType: AccountTypeOAuth,
  226. want: true,
  227. },
  228. {
  229. name: "openai apikey - cannot refresh",
  230. platform: PlatformOpenAI,
  231. accType: AccountTypeAPIKey,
  232. want: false,
  233. },
  234. }
  235. for _, tt := range tests {
  236. t.Run(tt.name, func(t *testing.T) {
  237. account := &Account{
  238. Platform: tt.platform,
  239. Type: tt.accType,
  240. }
  241. require.Equal(t, tt.want, refresher.CanRefresh(account))
  242. })
  243. }
  244. }