gemini_error_policy_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. "time"
  9. "github.com/Wei-Shaw/sub2api/internal/config"
  10. "github.com/gin-gonic/gin"
  11. "github.com/stretchr/testify/require"
  12. )
  13. // ---------------------------------------------------------------------------
  14. // TestShouldFailoverGeminiUpstreamError — verifies the failover decision
  15. // for the ErrorPolicyNone path (original logic preserved).
  16. // ---------------------------------------------------------------------------
  17. func TestShouldFailoverGeminiUpstreamError(t *testing.T) {
  18. svc := &GeminiMessagesCompatService{}
  19. tests := []struct {
  20. name string
  21. statusCode int
  22. expected bool
  23. }{
  24. {"401_failover", 401, true},
  25. {"403_failover", 403, true},
  26. {"429_failover", 429, true},
  27. {"529_failover", 529, true},
  28. {"500_failover", 500, true},
  29. {"502_failover", 502, true},
  30. {"503_failover", 503, true},
  31. {"400_no_failover", 400, false},
  32. {"404_no_failover", 404, false},
  33. {"422_no_failover", 422, false},
  34. }
  35. for _, tt := range tests {
  36. t.Run(tt.name, func(t *testing.T) {
  37. got := svc.shouldFailoverGeminiUpstreamError(tt.statusCode)
  38. require.Equal(t, tt.expected, got)
  39. })
  40. }
  41. }
  42. // ---------------------------------------------------------------------------
  43. // TestCheckErrorPolicy_GeminiAccounts — verifies CheckErrorPolicy works
  44. // correctly for Gemini platform accounts (API Key type).
  45. // ---------------------------------------------------------------------------
  46. func TestCheckErrorPolicy_GeminiAccounts(t *testing.T) {
  47. tests := []struct {
  48. name string
  49. account *Account
  50. statusCode int
  51. body []byte
  52. expected ErrorPolicyResult
  53. }{
  54. {
  55. name: "gemini_apikey_custom_codes_hit",
  56. account: &Account{
  57. ID: 100,
  58. Type: AccountTypeAPIKey,
  59. Platform: PlatformGemini,
  60. Credentials: map[string]any{
  61. "custom_error_codes_enabled": true,
  62. "custom_error_codes": []any{float64(429), float64(500)},
  63. },
  64. },
  65. statusCode: 429,
  66. body: []byte(`{"error":"rate limited"}`),
  67. expected: ErrorPolicyMatched,
  68. },
  69. {
  70. name: "gemini_apikey_custom_codes_miss",
  71. account: &Account{
  72. ID: 101,
  73. Type: AccountTypeAPIKey,
  74. Platform: PlatformGemini,
  75. Credentials: map[string]any{
  76. "custom_error_codes_enabled": true,
  77. "custom_error_codes": []any{float64(429)},
  78. },
  79. },
  80. statusCode: 500,
  81. body: []byte(`{"error":"internal"}`),
  82. expected: ErrorPolicySkipped,
  83. },
  84. {
  85. name: "gemini_apikey_no_custom_codes_returns_none",
  86. account: &Account{
  87. ID: 102,
  88. Type: AccountTypeAPIKey,
  89. Platform: PlatformGemini,
  90. },
  91. statusCode: 500,
  92. body: []byte(`{"error":"internal"}`),
  93. expected: ErrorPolicyNone,
  94. },
  95. {
  96. name: "gemini_apikey_temp_unschedulable_hit",
  97. account: &Account{
  98. ID: 103,
  99. Type: AccountTypeAPIKey,
  100. Platform: PlatformGemini,
  101. Credentials: map[string]any{
  102. "temp_unschedulable_enabled": true,
  103. "temp_unschedulable_rules": []any{
  104. map[string]any{
  105. "error_code": float64(503),
  106. "keywords": []any{"overloaded"},
  107. "duration_minutes": float64(10),
  108. },
  109. },
  110. },
  111. },
  112. statusCode: 503,
  113. body: []byte(`overloaded service`),
  114. expected: ErrorPolicyTempUnscheduled,
  115. },
  116. {
  117. name: "gemini_apikey_temp_unschedulable_401_second_hit_returns_none",
  118. account: &Account{
  119. ID: 105,
  120. Type: AccountTypeAPIKey,
  121. Platform: PlatformGemini,
  122. TempUnschedulableReason: `{"status_code":401,"until_unix":1735689600}`,
  123. Credentials: map[string]any{
  124. "temp_unschedulable_enabled": true,
  125. "temp_unschedulable_rules": []any{
  126. map[string]any{
  127. "error_code": float64(401),
  128. "keywords": []any{"unauthorized"},
  129. "duration_minutes": float64(10),
  130. },
  131. },
  132. },
  133. },
  134. statusCode: 401,
  135. body: []byte(`unauthorized`),
  136. expected: ErrorPolicyNone,
  137. },
  138. {
  139. name: "gemini_custom_codes_override_temp_unschedulable",
  140. account: &Account{
  141. ID: 104,
  142. Type: AccountTypeAPIKey,
  143. Platform: PlatformGemini,
  144. Credentials: map[string]any{
  145. "custom_error_codes_enabled": true,
  146. "custom_error_codes": []any{float64(503)},
  147. "temp_unschedulable_enabled": true,
  148. "temp_unschedulable_rules": []any{
  149. map[string]any{
  150. "error_code": float64(503),
  151. "keywords": []any{"overloaded"},
  152. "duration_minutes": float64(10),
  153. },
  154. },
  155. },
  156. },
  157. statusCode: 503,
  158. body: []byte(`overloaded`),
  159. expected: ErrorPolicyMatched, // custom codes take precedence
  160. },
  161. }
  162. for _, tt := range tests {
  163. t.Run(tt.name, func(t *testing.T) {
  164. repo := &errorPolicyRepoStub{}
  165. svc := NewRateLimitService(repo, nil, &config.Config{}, nil, nil)
  166. result := svc.CheckErrorPolicy(context.Background(), tt.account, tt.statusCode, tt.body)
  167. require.Equal(t, tt.expected, result)
  168. })
  169. }
  170. }
  171. // ---------------------------------------------------------------------------
  172. // TestGeminiErrorPolicyIntegration — verifies the Gemini error handling
  173. // paths produce the correct behavior for each ErrorPolicyResult.
  174. //
  175. // These tests simulate the inline error policy switch in handleClaudeCompat
  176. // and forwardNativeGemini by calling the same methods in the same order.
  177. // ---------------------------------------------------------------------------
  178. func TestGeminiErrorPolicyIntegration(t *testing.T) {
  179. gin.SetMode(gin.TestMode)
  180. tests := []struct {
  181. name string
  182. account *Account
  183. statusCode int
  184. respBody []byte
  185. expectFailover bool // expect UpstreamFailoverError
  186. expectHandleError bool // expect handleGeminiUpstreamError to be called
  187. expectShouldFailover bool // for None path, whether shouldFailover triggers
  188. expectModelScope string
  189. }{
  190. {
  191. name: "custom_codes_matched_429_failover",
  192. account: &Account{
  193. ID: 200,
  194. Type: AccountTypeAPIKey,
  195. Platform: PlatformGemini,
  196. Credentials: map[string]any{
  197. "custom_error_codes_enabled": true,
  198. "custom_error_codes": []any{float64(429)},
  199. },
  200. },
  201. statusCode: 429,
  202. respBody: []byte(`{"error":"rate limited"}`),
  203. expectFailover: true,
  204. expectHandleError: true,
  205. },
  206. {
  207. name: "custom_codes_skipped_500_failover",
  208. account: &Account{
  209. ID: 201,
  210. Type: AccountTypeAPIKey,
  211. Platform: PlatformGemini,
  212. Credentials: map[string]any{
  213. "custom_error_codes_enabled": true,
  214. "custom_error_codes": []any{float64(429)},
  215. },
  216. },
  217. statusCode: 500,
  218. respBody: []byte(`{"error":"internal"}`),
  219. expectFailover: true,
  220. expectHandleError: false,
  221. },
  222. {
  223. name: "custom_codes_skipped_400_no_failover",
  224. account: &Account{
  225. ID: 205,
  226. Type: AccountTypeAPIKey,
  227. Platform: PlatformGemini,
  228. Credentials: map[string]any{
  229. "custom_error_codes_enabled": true,
  230. "custom_error_codes": []any{float64(429)},
  231. },
  232. },
  233. statusCode: 400,
  234. respBody: []byte(`{"error":"bad request"}`),
  235. expectFailover: false,
  236. expectHandleError: false,
  237. },
  238. {
  239. name: "temp_unschedulable_matched_failover",
  240. account: &Account{
  241. ID: 202,
  242. Type: AccountTypeAPIKey,
  243. Platform: PlatformGemini,
  244. Credentials: map[string]any{
  245. "temp_unschedulable_enabled": true,
  246. "temp_unschedulable_rules": []any{
  247. map[string]any{
  248. "error_code": float64(503),
  249. "keywords": []any{"overloaded"},
  250. "duration_minutes": float64(10),
  251. },
  252. },
  253. },
  254. },
  255. statusCode: 503,
  256. respBody: []byte(`overloaded`),
  257. expectFailover: true,
  258. expectHandleError: false,
  259. expectModelScope: "gemini-2.5-pro",
  260. },
  261. {
  262. name: "no_policy_429_failover_via_shouldFailover",
  263. account: &Account{
  264. ID: 203,
  265. Type: AccountTypeAPIKey,
  266. Platform: PlatformGemini,
  267. },
  268. statusCode: 429,
  269. respBody: []byte(`{"error":"rate limited"}`),
  270. expectFailover: true,
  271. expectHandleError: true,
  272. expectShouldFailover: true,
  273. },
  274. {
  275. name: "no_policy_400_no_failover",
  276. account: &Account{
  277. ID: 204,
  278. Type: AccountTypeAPIKey,
  279. Platform: PlatformGemini,
  280. },
  281. statusCode: 400,
  282. respBody: []byte(`{"error":"bad request"}`),
  283. expectFailover: false,
  284. expectHandleError: true,
  285. },
  286. }
  287. for _, tt := range tests {
  288. t.Run(tt.name, func(t *testing.T) {
  289. repo := &geminiErrorPolicyRepo{}
  290. rlSvc := NewRateLimitService(repo, nil, &config.Config{}, nil, nil)
  291. svc := &GeminiMessagesCompatService{
  292. accountRepo: repo,
  293. rateLimitService: rlSvc,
  294. }
  295. writer := httptest.NewRecorder()
  296. c, _ := gin.CreateTestContext(writer)
  297. c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", nil)
  298. // Simulate the Claude compat error handling path (same logic as native).
  299. // This mirrors the inline switch in handleClaudeCompat.
  300. var handleErrorCalled bool
  301. var gotFailover bool
  302. ctx := context.Background()
  303. statusCode := tt.statusCode
  304. respBody := tt.respBody
  305. account := tt.account
  306. headers := http.Header{}
  307. if svc.rateLimitService != nil {
  308. policy := svc.rateLimitService.CheckErrorPolicy(ctx, account, statusCode, respBody, "gemini-2.5-pro")
  309. switch policy {
  310. case ErrorPolicySkipped:
  311. // Skipped → 不标记账号状态;可 failover 的状态码仍换号
  312. handleErrorCalled = false
  313. gotFailover = svc.skippedErrorPolicyFailoverError(c, account, statusCode, respBody, "req-test") != nil
  314. goto verify
  315. case ErrorPolicyMatched:
  316. svc.handleGeminiUpstreamError(ctx, account, statusCode, headers, respBody)
  317. handleErrorCalled = true
  318. gotFailover = true
  319. goto verify
  320. case ErrorPolicyTempUnscheduled:
  321. handleErrorCalled = false
  322. gotFailover = true
  323. goto verify
  324. }
  325. }
  326. // ErrorPolicyNone → original logic
  327. svc.handleGeminiUpstreamError(ctx, account, statusCode, headers, respBody)
  328. handleErrorCalled = true
  329. if svc.shouldFailoverGeminiUpstreamError(statusCode) {
  330. gotFailover = true
  331. }
  332. verify:
  333. require.Equal(t, tt.expectFailover, gotFailover, "failover mismatch")
  334. require.Equal(t, tt.expectHandleError, handleErrorCalled, "handleGeminiUpstreamError call mismatch")
  335. if tt.expectModelScope != "" {
  336. require.Equal(t, 1, repo.setModelRateLimitedCalls)
  337. require.Equal(t, tt.expectModelScope, repo.lastModelScope)
  338. require.Zero(t, repo.setTempCalls)
  339. require.Zero(t, repo.setRateLimitedCalls, "model temp rule must not be widened into an account rate limit")
  340. }
  341. if tt.expectShouldFailover {
  342. require.True(t, svc.shouldFailoverGeminiUpstreamError(statusCode),
  343. "shouldFailoverGeminiUpstreamError should return true for status %d", statusCode)
  344. }
  345. })
  346. }
  347. }
  348. // ---------------------------------------------------------------------------
  349. // TestSkippedErrorPolicyFailoverError — ErrorPolicySkipped(池模式、或自定义
  350. // 错误码未命中)不豁免换号:可 failover 的状态码返回 UpstreamFailoverError,
  351. // 仅池模式账号可携带同账号重试标记。
  352. // ---------------------------------------------------------------------------
  353. func TestSkippedErrorPolicyFailoverError(t *testing.T) {
  354. gin.SetMode(gin.TestMode)
  355. svc := &GeminiMessagesCompatService{}
  356. poolAccount := func(extra map[string]any) *Account {
  357. creds := map[string]any{"pool_mode": true}
  358. for k, v := range extra {
  359. creds[k] = v
  360. }
  361. return &Account{ID: 300, Type: AccountTypeAPIKey, Platform: PlatformGemini, Credentials: creds}
  362. }
  363. customCodesAccount := &Account{
  364. ID: 301, Type: AccountTypeAPIKey, Platform: PlatformGemini,
  365. Credentials: map[string]any{
  366. "custom_error_codes_enabled": true,
  367. "custom_error_codes": []any{float64(429)},
  368. },
  369. }
  370. tests := []struct {
  371. name string
  372. account *Account
  373. statusCode int
  374. expectFailover bool
  375. expectSameAccount bool
  376. }{
  377. {"pool_500_failover_no_same_account_retry", poolAccount(nil), 500, true, false},
  378. {"pool_429_failover_with_same_account_retry", poolAccount(nil), 429, true, true},
  379. {"pool_custom_retry_codes_500", poolAccount(map[string]any{
  380. "pool_mode_retry_status_codes": []any{float64(500)},
  381. }), 500, true, true},
  382. {"pool_400_not_failover_worthy", poolAccount(nil), 400, false, false},
  383. {"custom_codes_miss_500_failover_no_same_account_retry", customCodesAccount, 500, true, false},
  384. {"custom_codes_miss_400_not_failover_worthy", customCodesAccount, 400, false, false},
  385. }
  386. for _, tt := range tests {
  387. t.Run(tt.name, func(t *testing.T) {
  388. writer := httptest.NewRecorder()
  389. c, _ := gin.CreateTestContext(writer)
  390. c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", nil)
  391. body := []byte(`{"error":{"code":"bad_response_status_code","message":"openai_error"}}`)
  392. failoverErr := svc.skippedErrorPolicyFailoverError(c, tt.account, tt.statusCode, body, "req-1")
  393. if !tt.expectFailover {
  394. require.Nil(t, failoverErr)
  395. return
  396. }
  397. require.NotNil(t, failoverErr)
  398. require.Equal(t, tt.statusCode, failoverErr.StatusCode)
  399. require.Equal(t, body, failoverErr.ResponseBody)
  400. require.Equal(t, tt.expectSameAccount, failoverErr.RetryableOnSameAccount)
  401. require.True(t, failoverErr.ShouldRetryNextAccount())
  402. })
  403. }
  404. }
  405. // ---------------------------------------------------------------------------
  406. // TestGeminiErrorPolicy_NilRateLimitService — verifies nil safety
  407. // ---------------------------------------------------------------------------
  408. func TestGeminiErrorPolicy_NilRateLimitService(t *testing.T) {
  409. svc := &GeminiMessagesCompatService{
  410. rateLimitService: nil,
  411. }
  412. // When rateLimitService is nil, error policy is skipped → falls through to
  413. // shouldFailoverGeminiUpstreamError (original logic).
  414. // Verify this doesn't panic and follows expected behavior.
  415. ctx := context.Background()
  416. account := &Account{
  417. ID: 300,
  418. Type: AccountTypeAPIKey,
  419. Platform: PlatformGemini,
  420. Credentials: map[string]any{
  421. "custom_error_codes_enabled": true,
  422. "custom_error_codes": []any{float64(429)},
  423. },
  424. }
  425. // The nil check should prevent CheckErrorPolicy from being called
  426. if svc.rateLimitService != nil {
  427. t.Fatal("rateLimitService should be nil for this test")
  428. }
  429. // shouldFailoverGeminiUpstreamError still works
  430. require.True(t, svc.shouldFailoverGeminiUpstreamError(429))
  431. require.False(t, svc.shouldFailoverGeminiUpstreamError(400))
  432. // handleGeminiUpstreamError should not panic with nil rateLimitService
  433. require.NotPanics(t, func() {
  434. svc.handleGeminiUpstreamError(ctx, account, 500, http.Header{}, []byte(`error`))
  435. })
  436. }
  437. // ---------------------------------------------------------------------------
  438. // geminiErrorPolicyRepo — minimal AccountRepository stub for Gemini error
  439. // policy tests. Embeds mockAccountRepoForGemini and adds tracking.
  440. // ---------------------------------------------------------------------------
  441. func TestHandleGeminiUpstreamError_GoogleOneCapacityExhaustedUsesTierCooldown(t *testing.T) {
  442. repo := &rateLimit429AccountRepoStub{}
  443. quotaSvc := NewGeminiQuotaService(&config.Config{}, nil)
  444. rlSvc := NewRateLimitService(repo, nil, &config.Config{}, quotaSvc, nil)
  445. svc := &GeminiMessagesCompatService{
  446. accountRepo: repo,
  447. rateLimitService: rlSvc,
  448. }
  449. account := &Account{
  450. ID: 511,
  451. Platform: PlatformGemini,
  452. Type: AccountTypeOAuth,
  453. Credentials: map[string]any{
  454. "oauth_type": "google_one",
  455. "tier_id": "google_ai_pro",
  456. },
  457. }
  458. body := []byte(`{"error":{"code":429,"details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"cloudcode-pa.googleapis.com","metadata":{"model":"gemini-3.1-pro-preview"},"reason":"MODEL_CAPACITY_EXHAUSTED"}],"message":"No capacity available for model gemini-3.1-pro-preview on the server","status":"RESOURCE_EXHAUSTED"}}`)
  459. before := time.Now()
  460. svc.handleGeminiUpstreamError(context.Background(), account, http.StatusTooManyRequests, http.Header{}, body)
  461. after := time.Now()
  462. require.Equal(t, 1, repo.rateLimitCalls)
  463. require.Equal(t, int64(511), repo.lastRateLimitID)
  464. require.WithinDuration(t, before.Add(5*time.Minute), repo.lastRateLimitReset, 2*time.Second)
  465. require.True(t, repo.lastRateLimitReset.After(before))
  466. require.True(t, repo.lastRateLimitReset.Before(after.Add(5*time.Minute).Add(2*time.Second)))
  467. }
  468. // ---------------------------------------------------------------------------
  469. // TestHandleGeminiUpstreamError_PoolMode429 — 池模式账号的 429 不写账号级限流。
  470. //
  471. // 429 的标记点在重试循环内(handleClaudeCompat / forwardNativeGemini /
  472. // chat completions 三条路径),先于 CheckErrorPolicy 执行,池模式豁免只能落在
  473. // handleGeminiUpstreamError 自身;否则一次上游 429 会把账号锁到 PST 午夜,
  474. // 即便重试已经成功返回客户端。
  475. // ---------------------------------------------------------------------------
  476. func TestHandleGeminiUpstreamError_PoolMode429(t *testing.T) {
  477. // 中转上游的真实 429 文案:不含 "per day",也没有 quotaResetDelay,
  478. // 解析失败后 apikey 账号会落到 PST 午夜兜底。
  479. body := []byte(`{"error":{"code":429,"message":"You have exhausted your capacity on this model. Your quota will reset after 6h53m10s."}}`)
  480. tests := []struct {
  481. name string
  482. account *Account
  483. expectRateLimited bool
  484. }{
  485. {
  486. name: "pool_mode_apikey_stays_in_pool",
  487. account: &Account{
  488. ID: 600,
  489. Platform: PlatformGemini,
  490. Type: AccountTypeAPIKey,
  491. Credentials: map[string]any{"pool_mode": true},
  492. },
  493. expectRateLimited: false,
  494. },
  495. {
  496. name: "custom_error_codes_hit_overrides_pool_mode",
  497. account: &Account{
  498. ID: 601,
  499. Platform: PlatformGemini,
  500. Type: AccountTypeAPIKey,
  501. Credentials: map[string]any{
  502. "pool_mode": true,
  503. "custom_error_codes_enabled": true,
  504. "custom_error_codes": []any{float64(429)},
  505. },
  506. },
  507. expectRateLimited: true,
  508. },
  509. {
  510. name: "custom_error_codes_miss_skips",
  511. account: &Account{
  512. ID: 602,
  513. Platform: PlatformGemini,
  514. Type: AccountTypeAPIKey,
  515. Credentials: map[string]any{
  516. "pool_mode": true,
  517. "custom_error_codes_enabled": true,
  518. "custom_error_codes": []any{float64(500)},
  519. },
  520. },
  521. expectRateLimited: false,
  522. },
  523. {
  524. name: "non_pool_apikey_still_rate_limited",
  525. account: &Account{
  526. ID: 603,
  527. Platform: PlatformGemini,
  528. Type: AccountTypeAPIKey,
  529. },
  530. expectRateLimited: true,
  531. },
  532. {
  533. name: "oauth_account_ignores_pool_mode_flag",
  534. account: &Account{
  535. ID: 604,
  536. Platform: PlatformGemini,
  537. Type: AccountTypeOAuth,
  538. Credentials: map[string]any{"pool_mode": true},
  539. },
  540. expectRateLimited: true,
  541. },
  542. }
  543. for _, tt := range tests {
  544. t.Run(tt.name, func(t *testing.T) {
  545. repo := &rateLimit429AccountRepoStub{}
  546. svc := &GeminiMessagesCompatService{
  547. accountRepo: repo,
  548. rateLimitService: NewRateLimitService(repo, nil, &config.Config{}, nil, nil),
  549. }
  550. svc.handleGeminiUpstreamError(context.Background(), tt.account, http.StatusTooManyRequests, http.Header{}, body)
  551. if !tt.expectRateLimited {
  552. require.Zero(t, repo.rateLimitCalls, "池模式账号不应被标记账号级限流")
  553. return
  554. }
  555. require.Equal(t, 1, repo.rateLimitCalls)
  556. require.Equal(t, tt.account.ID, repo.lastRateLimitID)
  557. require.True(t, repo.lastRateLimitReset.After(time.Now()))
  558. })
  559. }
  560. }
  561. type geminiErrorPolicyRepo struct {
  562. mockAccountRepoForGemini
  563. setErrorCalls int
  564. setRateLimitedCalls int
  565. setTempCalls int
  566. setModelRateLimitedCalls int
  567. lastModelScope string
  568. }
  569. func (r *geminiErrorPolicyRepo) SetError(_ context.Context, _ int64, _ string) error {
  570. r.setErrorCalls++
  571. return nil
  572. }
  573. func (r *geminiErrorPolicyRepo) SetRateLimited(_ context.Context, _ int64, _ time.Time) error {
  574. r.setRateLimitedCalls++
  575. return nil
  576. }
  577. func (r *geminiErrorPolicyRepo) SetTempUnschedulable(_ context.Context, _ int64, _ time.Time, _ string) error {
  578. r.setTempCalls++
  579. return nil
  580. }
  581. func (r *geminiErrorPolicyRepo) SetModelRateLimit(_ context.Context, _ int64, scope string, _ time.Time, _ ...string) error {
  582. r.setModelRateLimitedCalls++
  583. r.lastModelScope = scope
  584. return nil
  585. }