@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +88 -88
  2. package/dist/opencode-anthropic-auth-cli.mjs +804 -507
  3. package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
  4. package/package.json +67 -59
  5. package/src/__tests__/billing-edge-cases.test.ts +59 -59
  6. package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
  7. package/src/__tests__/cc-comparison.test.ts +87 -87
  8. package/src/__tests__/cc-credentials.test.ts +254 -250
  9. package/src/__tests__/cch-drift-checker.test.ts +51 -51
  10. package/src/__tests__/cch-native-style.test.ts +56 -56
  11. package/src/__tests__/debug-gating.test.ts +42 -42
  12. package/src/__tests__/decomposition-smoke.test.ts +68 -68
  13. package/src/__tests__/fingerprint-regression.test.ts +575 -566
  14. package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
  15. package/src/__tests__/helpers/conversation-history.ts +119 -119
  16. package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
  17. package/src/__tests__/helpers/deferred.ts +69 -69
  18. package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
  19. package/src/__tests__/helpers/in-memory-storage.ts +88 -88
  20. package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
  21. package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
  22. package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
  23. package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
  24. package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
  25. package/src/__tests__/helpers/sse.ts +209 -209
  26. package/src/__tests__/index.parallel.test.ts +605 -595
  27. package/src/__tests__/sanitization-regex.test.ts +112 -112
  28. package/src/__tests__/state-bounds.test.ts +90 -90
  29. package/src/account-identity.test.ts +197 -192
  30. package/src/account-identity.ts +69 -67
  31. package/src/account-state.test.ts +86 -86
  32. package/src/account-state.ts +25 -25
  33. package/src/accounts/matching.test.ts +335 -0
  34. package/src/accounts/matching.ts +167 -0
  35. package/src/accounts/persistence.test.ts +345 -0
  36. package/src/accounts/persistence.ts +432 -0
  37. package/src/accounts/repair.test.ts +276 -0
  38. package/src/accounts/repair.ts +407 -0
  39. package/src/accounts.dedup.test.ts +621 -621
  40. package/src/accounts.test.ts +933 -929
  41. package/src/accounts.ts +633 -989
  42. package/src/backoff.test.ts +345 -345
  43. package/src/backoff.ts +219 -219
  44. package/src/betas.ts +124 -124
  45. package/src/bun-fetch.test.ts +345 -342
  46. package/src/bun-fetch.ts +424 -424
  47. package/src/bun-proxy.test.ts +25 -25
  48. package/src/bun-proxy.ts +209 -209
  49. package/src/cc-credentials.ts +111 -111
  50. package/src/circuit-breaker.test.ts +184 -184
  51. package/src/circuit-breaker.ts +169 -169
  52. package/src/cli/commands/auth.ts +963 -0
  53. package/src/cli/commands/config.ts +547 -0
  54. package/src/cli/formatting.test.ts +406 -0
  55. package/src/cli/formatting.ts +219 -0
  56. package/src/cli.ts +255 -2022
  57. package/src/commands/handlers/betas.ts +100 -0
  58. package/src/commands/handlers/config.ts +99 -0
  59. package/src/commands/handlers/files.ts +375 -0
  60. package/src/commands/oauth-flow.ts +181 -166
  61. package/src/commands/prompts.ts +61 -61
  62. package/src/commands/router.test.ts +421 -0
  63. package/src/commands/router.ts +143 -635
  64. package/src/config.test.ts +482 -482
  65. package/src/config.ts +412 -404
  66. package/src/constants.ts +48 -48
  67. package/src/drift/cch-constants.ts +95 -95
  68. package/src/env.ts +111 -105
  69. package/src/headers/billing.ts +33 -33
  70. package/src/headers/builder.ts +130 -130
  71. package/src/headers/cch.ts +75 -75
  72. package/src/headers/stainless.ts +25 -25
  73. package/src/headers/user-agent.ts +23 -23
  74. package/src/index.ts +436 -828
  75. package/src/models.ts +27 -27
  76. package/src/oauth.test.ts +102 -102
  77. package/src/oauth.ts +178 -178
  78. package/src/parent-pid-watcher.test.ts +148 -148
  79. package/src/parent-pid-watcher.ts +69 -69
  80. package/src/plugin-helpers.ts +82 -82
  81. package/src/refresh-helpers.ts +145 -139
  82. package/src/refresh-lock.test.ts +94 -94
  83. package/src/refresh-lock.ts +93 -93
  84. package/src/request/body.history.test.ts +579 -571
  85. package/src/request/body.ts +255 -255
  86. package/src/request/metadata.ts +65 -65
  87. package/src/request/retry.test.ts +156 -156
  88. package/src/request/retry.ts +67 -67
  89. package/src/request/url.ts +21 -21
  90. package/src/request-orchestration-helpers.ts +648 -0
  91. package/src/response/index.ts +5 -5
  92. package/src/response/mcp.ts +58 -58
  93. package/src/response/streaming.test.ts +313 -311
  94. package/src/response/streaming.ts +412 -410
  95. package/src/rotation.test.ts +304 -301
  96. package/src/rotation.ts +205 -205
  97. package/src/storage.test.ts +547 -547
  98. package/src/storage.ts +315 -291
  99. package/src/system-prompt/builder.ts +38 -38
  100. package/src/system-prompt/index.ts +5 -5
  101. package/src/system-prompt/normalize.ts +60 -60
  102. package/src/system-prompt/sanitize.ts +30 -30
  103. package/src/thinking.ts +21 -20
  104. package/src/token-refresh.test.ts +265 -265
  105. package/src/token-refresh.ts +219 -214
  106. package/src/types.ts +30 -30
  107. package/dist/bun-proxy.mjs +0 -291
@@ -12,326 +12,326 @@ import { createFetchHarness, setMockAccounts, clearMockAccounts } from "./plugin
12
12
 
13
13
  // Mock dependencies (hoisted by Vitest)
14
14
  vi.mock("node:readline/promises", () => ({
15
- createInterface: vi.fn(() => ({
16
- question: vi.fn().mockResolvedValue("a"),
17
- close: vi.fn(),
18
- })),
15
+ createInterface: vi.fn(() => ({
16
+ question: vi.fn().mockResolvedValue("a"),
17
+ close: vi.fn(),
18
+ })),
19
19
  }));
20
20
 
21
21
  vi.mock("../../storage.js", () => ({
22
- createDefaultStats: (now?: number) => ({
23
- requests: 0,
24
- inputTokens: 0,
25
- outputTokens: 0,
26
- cacheReadTokens: 0,
27
- cacheWriteTokens: 0,
28
- lastReset: now ?? Date.now(),
29
- }),
30
- loadAccounts: vi.fn().mockResolvedValue(null),
31
- saveAccounts: vi.fn().mockResolvedValue(undefined),
32
- clearAccounts: vi.fn().mockResolvedValue(undefined),
33
- getStoragePath: vi.fn(() => "/tmp/test-accounts.json"),
22
+ createDefaultStats: (now?: number) => ({
23
+ requests: 0,
24
+ inputTokens: 0,
25
+ outputTokens: 0,
26
+ cacheReadTokens: 0,
27
+ cacheWriteTokens: 0,
28
+ lastReset: now ?? Date.now(),
29
+ }),
30
+ loadAccounts: vi.fn().mockResolvedValue(null),
31
+ saveAccounts: vi.fn().mockResolvedValue(undefined),
32
+ clearAccounts: vi.fn().mockResolvedValue(undefined),
33
+ getStoragePath: vi.fn(() => "/tmp/test-accounts.json"),
34
34
  }));
35
35
 
36
36
  vi.mock("../../config.js", () => {
37
- const DEFAULT_CONFIG = {
38
- account_selection_strategy: "sticky",
39
- failure_ttl_seconds: 3600,
40
- debug: false,
41
- signature_emulation: {
42
- enabled: true,
43
- fetch_claude_code_version_on_startup: false,
44
- prompt_compaction: "minimal",
45
- },
46
- override_model_limits: {
47
- enabled: false,
48
- context: 1_000_000,
49
- output: 0,
50
- },
51
- custom_betas: [],
52
- health_score: {
53
- initial: 70,
54
- success_reward: 1,
55
- rate_limit_penalty: -10,
56
- failure_penalty: -20,
57
- recovery_rate_per_hour: 2,
58
- min_usable: 50,
59
- max_score: 100,
60
- },
61
- token_bucket: {
62
- max_tokens: 50,
63
- regeneration_rate_per_minute: 6,
64
- initial_tokens: 50,
65
- },
66
- toasts: {
67
- quiet: true,
68
- debounce_seconds: 30,
69
- },
70
- headers: {},
71
- idle_refresh: {
72
- enabled: false,
73
- window_minutes: 60,
74
- min_interval_minutes: 30,
75
- },
76
- cc_credential_reuse: {
77
- enabled: false,
78
- auto_detect: false,
79
- prefer_over_oauth: false,
80
- },
81
- };
82
-
83
- const createBaseConfig = () => ({
84
- ...DEFAULT_CONFIG,
85
- account_selection_strategy: "sticky",
86
- signature_emulation: {
87
- ...DEFAULT_CONFIG.signature_emulation,
88
- fetch_claude_code_version_on_startup: false,
89
- },
90
- override_model_limits: {
91
- ...DEFAULT_CONFIG.override_model_limits,
92
- },
93
- custom_betas: [...DEFAULT_CONFIG.custom_betas],
94
- health_score: { ...DEFAULT_CONFIG.health_score },
95
- token_bucket: { ...DEFAULT_CONFIG.token_bucket },
96
- toasts: { ...DEFAULT_CONFIG.toasts },
97
- headers: { ...DEFAULT_CONFIG.headers },
98
- idle_refresh: { ...DEFAULT_CONFIG.idle_refresh, enabled: false },
99
- cc_credential_reuse: { ...DEFAULT_CONFIG.cc_credential_reuse },
100
- });
101
-
102
- return {
103
- CLIENT_ID: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
104
- DEFAULT_CONFIG,
105
- VALID_STRATEGIES: ["sticky", "round-robin", "hybrid"],
106
- loadConfig: vi.fn(() => createBaseConfig()),
107
- loadConfigFresh: vi.fn(() => createBaseConfig()),
108
- saveConfig: vi.fn(),
109
- getConfigDir: vi.fn(() => "/tmp/test-config"),
110
- };
37
+ const DEFAULT_CONFIG = {
38
+ account_selection_strategy: "sticky",
39
+ failure_ttl_seconds: 3600,
40
+ debug: false,
41
+ signature_emulation: {
42
+ enabled: true,
43
+ fetch_claude_code_version_on_startup: false,
44
+ prompt_compaction: "minimal",
45
+ },
46
+ override_model_limits: {
47
+ enabled: false,
48
+ context: 1_000_000,
49
+ output: 0,
50
+ },
51
+ custom_betas: [],
52
+ health_score: {
53
+ initial: 70,
54
+ success_reward: 1,
55
+ rate_limit_penalty: -10,
56
+ failure_penalty: -20,
57
+ recovery_rate_per_hour: 2,
58
+ min_usable: 50,
59
+ max_score: 100,
60
+ },
61
+ token_bucket: {
62
+ max_tokens: 50,
63
+ regeneration_rate_per_minute: 6,
64
+ initial_tokens: 50,
65
+ },
66
+ toasts: {
67
+ quiet: true,
68
+ debounce_seconds: 30,
69
+ },
70
+ headers: {},
71
+ idle_refresh: {
72
+ enabled: false,
73
+ window_minutes: 60,
74
+ min_interval_minutes: 30,
75
+ },
76
+ cc_credential_reuse: {
77
+ enabled: false,
78
+ auto_detect: false,
79
+ prefer_over_oauth: false,
80
+ },
81
+ };
82
+
83
+ const createBaseConfig = () => ({
84
+ ...DEFAULT_CONFIG,
85
+ account_selection_strategy: "sticky",
86
+ signature_emulation: {
87
+ ...DEFAULT_CONFIG.signature_emulation,
88
+ fetch_claude_code_version_on_startup: false,
89
+ },
90
+ override_model_limits: {
91
+ ...DEFAULT_CONFIG.override_model_limits,
92
+ },
93
+ custom_betas: [...DEFAULT_CONFIG.custom_betas],
94
+ health_score: { ...DEFAULT_CONFIG.health_score },
95
+ token_bucket: { ...DEFAULT_CONFIG.token_bucket },
96
+ toasts: { ...DEFAULT_CONFIG.toasts },
97
+ headers: { ...DEFAULT_CONFIG.headers },
98
+ idle_refresh: { ...DEFAULT_CONFIG.idle_refresh, enabled: false },
99
+ cc_credential_reuse: { ...DEFAULT_CONFIG.cc_credential_reuse },
100
+ });
101
+
102
+ return {
103
+ CLIENT_ID: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
104
+ DEFAULT_CONFIG,
105
+ VALID_STRATEGIES: ["sticky", "round-robin", "hybrid"],
106
+ loadConfig: vi.fn(() => createBaseConfig()),
107
+ loadConfigFresh: vi.fn(() => createBaseConfig()),
108
+ saveConfig: vi.fn(),
109
+ getConfigDir: vi.fn(() => "/tmp/test-config"),
110
+ };
111
111
  });
112
112
 
113
113
  vi.mock("../../cc-credentials.js", () => ({
114
- readCCCredentials: vi.fn(() => []),
114
+ readCCCredentials: vi.fn(() => []),
115
115
  }));
116
116
 
117
117
  vi.mock("../../refresh-lock.js", () => ({
118
- acquireRefreshLock: vi.fn().mockResolvedValue({
119
- acquired: true,
120
- lockPath: "/tmp/opencode-test.lock",
121
- }),
122
- releaseRefreshLock: vi.fn().mockResolvedValue(undefined),
118
+ acquireRefreshLock: vi.fn().mockResolvedValue({
119
+ acquired: true,
120
+ lockPath: "/tmp/opencode-test.lock",
121
+ }),
122
+ releaseRefreshLock: vi.fn().mockResolvedValue(undefined),
123
123
  }));
124
124
 
125
125
  vi.mock("@clack/prompts", () => {
126
- const noop = vi.fn();
127
- return {
128
- text: vi.fn().mockResolvedValue(""),
129
- confirm: vi.fn().mockResolvedValue(false),
130
- select: vi.fn().mockResolvedValue("cancel"),
131
- spinner: vi.fn(() => ({ start: noop, stop: noop, message: noop })),
132
- intro: noop,
133
- outro: noop,
134
- isCancel: vi.fn().mockReturnValue(false),
135
- log: {
136
- info: noop,
137
- success: noop,
138
- warn: noop,
139
- error: noop,
140
- message: noop,
141
- step: noop,
142
- },
143
- note: noop,
144
- cancel: noop,
145
- };
126
+ const noop = vi.fn();
127
+ return {
128
+ text: vi.fn().mockResolvedValue(""),
129
+ confirm: vi.fn().mockResolvedValue(false),
130
+ select: vi.fn().mockResolvedValue("cancel"),
131
+ spinner: vi.fn(() => ({ start: noop, stop: noop, message: noop })),
132
+ intro: noop,
133
+ outro: noop,
134
+ isCancel: vi.fn().mockReturnValue(false),
135
+ log: {
136
+ info: noop,
137
+ success: noop,
138
+ warn: noop,
139
+ error: noop,
140
+ message: noop,
141
+ step: noop,
142
+ },
143
+ note: noop,
144
+ cancel: noop,
145
+ };
146
146
  });
147
147
 
148
148
  describe("plugin-fetch-harness", () => {
149
- beforeEach(() => {
150
- clearMockAccounts();
151
- vi.clearAllMocks();
152
- });
153
-
154
- afterEach(() => {
155
- clearMockAccounts();
156
- });
157
-
158
- it("should create a harness with default options", async () => {
159
- const harness = await createFetchHarness();
160
-
161
- expect(harness.fetch).toBeDefined();
162
- expect(harness.mockFetch).toBeDefined();
163
- expect(harness.tearDown).toBeDefined();
164
- expect(harness.waitFor).toBeDefined();
165
- expect(harness.getFetchHeaders).toBeDefined();
166
- expect(harness.getFetchUrl).toBeDefined();
167
-
168
- harness.tearDown();
169
- });
170
-
171
- it("should fire a request and land on mock fetch", async () => {
172
- const harness = await createFetchHarness({
173
- accounts: [{ email: "test@example.com" }],
174
- mockResponses: {
175
- "api.anthropic.com": {
176
- ok: true,
177
- status: 200,
178
- json: async () => ({ id: "msg_123", content: [{ type: "text", text: "Hello" }] }),
179
- },
180
- },
149
+ beforeEach(() => {
150
+ clearMockAccounts();
151
+ vi.clearAllMocks();
181
152
  });
182
153
 
183
- const response = await harness.fetch("https://api.anthropic.com/v1/messages", {
184
- method: "POST",
185
- headers: { "Content-Type": "application/json" },
186
- body: JSON.stringify({ model: "claude-sonnet", messages: [{ role: "user", content: "Hi" }] }),
154
+ afterEach(() => {
155
+ clearMockAccounts();
187
156
  });
188
157
 
189
- expect(response.ok).toBe(true);
190
- expect(harness.mockFetch).toHaveBeenCalledTimes(1);
158
+ it("should create a harness with default options", async () => {
159
+ const harness = await createFetchHarness();
191
160
 
192
- const [callUrl] = harness.mockFetch.mock.calls[0] ?? [];
193
- expect(callUrl).toBeDefined();
161
+ expect(harness.fetch).toBeDefined();
162
+ expect(harness.mockFetch).toBeDefined();
163
+ expect(harness.tearDown).toBeDefined();
164
+ expect(harness.waitFor).toBeDefined();
165
+ expect(harness.getFetchHeaders).toBeDefined();
166
+ expect(harness.getFetchUrl).toBeDefined();
194
167
 
195
- harness.tearDown();
196
- });
197
-
198
- it("should support multiple accounts", async () => {
199
- const harness = await createFetchHarness({
200
- accounts: [
201
- { email: "alice@example.com", refreshToken: "refresh-alice" },
202
- { email: "bob@example.com", refreshToken: "refresh-bob" },
203
- ],
204
- initialAccount: 1,
205
- });
206
-
207
- await harness.fetch("https://api.anthropic.com/v1/messages", {
208
- method: "POST",
209
- body: JSON.stringify({}),
168
+ harness.tearDown();
210
169
  });
211
170
 
212
- expect(harness.mockFetch).toHaveBeenCalledTimes(1);
213
-
214
- harness.tearDown();
215
- });
216
-
217
- it("should use custom mock responses", async () => {
218
- const harness = await createFetchHarness({
219
- mockResponses: {
220
- "api.anthropic.com": () => ({
221
- ok: true,
222
- status: 200,
223
- headers: new Headers({ "x-custom-header": "test-value" }),
224
- json: async () => ({ custom: true }),
225
- }),
226
- },
171
+ it("should fire a request and land on mock fetch", async () => {
172
+ const harness = await createFetchHarness({
173
+ accounts: [{ email: "test@example.com" }],
174
+ mockResponses: {
175
+ "api.anthropic.com": {
176
+ ok: true,
177
+ status: 200,
178
+ json: async () => ({ id: "msg_123", content: [{ type: "text", text: "Hello" }] }),
179
+ },
180
+ },
181
+ });
182
+
183
+ const response = await harness.fetch("https://api.anthropic.com/v1/messages", {
184
+ method: "POST",
185
+ headers: { "Content-Type": "application/json" },
186
+ body: JSON.stringify({ model: "claude-sonnet", messages: [{ role: "user", content: "Hi" }] }),
187
+ });
188
+
189
+ expect(response.ok).toBe(true);
190
+ expect(harness.mockFetch).toHaveBeenCalledTimes(1);
191
+
192
+ const [callUrl] = harness.mockFetch.mock.calls[0] ?? [];
193
+ expect(callUrl).toBeDefined();
194
+
195
+ harness.tearDown();
227
196
  });
228
197
 
229
- const response = await harness.fetch("https://api.anthropic.com/v1/messages", {
230
- method: "POST",
231
- });
198
+ it("should support multiple accounts", async () => {
199
+ const harness = await createFetchHarness({
200
+ accounts: [
201
+ { email: "alice@example.com", refreshToken: "refresh-alice" },
202
+ { email: "bob@example.com", refreshToken: "refresh-bob" },
203
+ ],
204
+ initialAccount: 1,
205
+ });
232
206
 
233
- const data = await response.json();
234
- expect(data).toEqual({ custom: true });
207
+ await harness.fetch("https://api.anthropic.com/v1/messages", {
208
+ method: "POST",
209
+ body: JSON.stringify({}),
210
+ });
235
211
 
236
- harness.tearDown();
237
- });
212
+ expect(harness.mockFetch).toHaveBeenCalledTimes(1);
238
213
 
239
- it("should provide access to request headers", async () => {
240
- const harness = await createFetchHarness();
214
+ harness.tearDown();
215
+ });
241
216
 
242
- await harness.fetch("https://api.anthropic.com/v1/messages", {
243
- method: "POST",
244
- headers: {
245
- "x-api-key": "test-key",
246
- "content-type": "application/json",
247
- },
217
+ it("should use custom mock responses", async () => {
218
+ const harness = await createFetchHarness({
219
+ mockResponses: {
220
+ "api.anthropic.com": () => ({
221
+ ok: true,
222
+ status: 200,
223
+ headers: new Headers({ "x-custom-header": "test-value" }),
224
+ json: async () => ({ custom: true }),
225
+ }),
226
+ },
227
+ });
228
+
229
+ const response = await harness.fetch("https://api.anthropic.com/v1/messages", {
230
+ method: "POST",
231
+ });
232
+
233
+ const data = await response.json();
234
+ expect(data).toEqual({ custom: true });
235
+
236
+ harness.tearDown();
248
237
  });
249
238
 
250
- const headers = harness.getFetchHeaders(0);
251
- expect(headers).toBeDefined();
239
+ it("should provide access to request headers", async () => {
240
+ const harness = await createFetchHarness();
252
241
 
253
- harness.tearDown();
254
- });
242
+ await harness.fetch("https://api.anthropic.com/v1/messages", {
243
+ method: "POST",
244
+ headers: {
245
+ "x-api-key": "test-key",
246
+ "content-type": "application/json",
247
+ },
248
+ });
255
249
 
256
- it("should support waitFor for async assertions", async () => {
257
- const harness = await createFetchHarness();
250
+ const headers = harness.getFetchHeaders(0);
251
+ expect(headers).toBeDefined();
258
252
 
259
- let callCount = 0;
260
- harness.mockFetch.mockImplementation(async () => {
261
- callCount++;
262
- return new Response("{}", {
263
- status: 200,
264
- headers: { "content-type": "application/json" },
265
- });
253
+ harness.tearDown();
266
254
  });
267
255
 
268
- // Fire request asynchronously
269
- setTimeout(() => {
270
- harness.fetch("https://api.anthropic.com/v1/messages", { method: "POST" });
271
- }, 50);
256
+ it("should support waitFor for async assertions", async () => {
257
+ const harness = await createFetchHarness();
258
+
259
+ let callCount = 0;
260
+ harness.mockFetch.mockImplementation(async () => {
261
+ callCount++;
262
+ return new Response("{}", {
263
+ status: 200,
264
+ headers: { "content-type": "application/json" },
265
+ });
266
+ });
267
+
268
+ // Fire request asynchronously
269
+ setTimeout(() => {
270
+ harness.fetch("https://api.anthropic.com/v1/messages", { method: "POST" });
271
+ }, 50);
272
+
273
+ // Wait for the assertion to pass
274
+ await harness.waitFor(() => {
275
+ expect(callCount).toBeGreaterThan(0);
276
+ }, 500);
277
+
278
+ harness.tearDown();
279
+ });
272
280
 
273
- // Wait for the assertion to pass
274
- await harness.waitFor(() => {
275
- expect(callCount).toBeGreaterThan(0);
276
- }, 500);
281
+ it("should restore original fetch on tearDown", async () => {
282
+ const originalFetch = globalThis.fetch;
277
283
 
278
- harness.tearDown();
279
- });
284
+ const harness = await createFetchHarness();
285
+ expect(globalThis.fetch).toBe(harness.mockFetch);
280
286
 
281
- it("should restore original fetch on tearDown", async () => {
282
- const originalFetch = globalThis.fetch;
287
+ harness.tearDown();
288
+ expect(globalThis.fetch).toBe(originalFetch);
289
+ });
283
290
 
284
- const harness = await createFetchHarness();
285
- expect(globalThis.fetch).toBe(harness.mockFetch);
291
+ it("should handle config overrides", async () => {
292
+ const harness = await createFetchHarness({
293
+ config: {
294
+ debug: true,
295
+ account_selection_strategy: "round-robin",
296
+ },
297
+ });
286
298
 
287
- harness.tearDown();
288
- expect(globalThis.fetch).toBe(originalFetch);
289
- });
299
+ expect(harness.fetch).toBeDefined();
290
300
 
291
- it("should handle config overrides", async () => {
292
- const harness = await createFetchHarness({
293
- config: {
294
- debug: true,
295
- account_selection_strategy: "round-robin",
296
- },
301
+ harness.tearDown();
297
302
  });
298
-
299
- expect(harness.fetch).toBeDefined();
300
-
301
- harness.tearDown();
302
- });
303
303
  });
304
304
 
305
305
  describe("setMockAccounts / clearMockAccounts", () => {
306
- it("should set and clear mock accounts", () => {
307
- const testAccounts = [
308
- {
309
- id: "test-1",
310
- index: 0,
311
- refreshToken: "refresh-1",
312
- access: "access-1",
313
- expires: Date.now() + 3600_000,
314
- token_updated_at: Date.now(),
315
- addedAt: Date.now(),
316
- lastUsed: 0,
317
- enabled: true,
318
- rateLimitResetTimes: {},
319
- consecutiveFailures: 0,
320
- lastFailureTime: null,
321
- email: undefined,
322
- stats: {
323
- requests: 0,
324
- inputTokens: 0,
325
- outputTokens: 0,
326
- cacheReadTokens: 0,
327
- cacheWriteTokens: 0,
328
- lastReset: Date.now(),
329
- },
330
- source: "oauth" as const,
331
- },
332
- ];
333
-
334
- setMockAccounts(testAccounts, 0);
335
- clearMockAccounts();
336
- });
306
+ it("should set and clear mock accounts", () => {
307
+ const testAccounts = [
308
+ {
309
+ id: "test-1",
310
+ index: 0,
311
+ refreshToken: "refresh-1",
312
+ access: "access-1",
313
+ expires: Date.now() + 3600_000,
314
+ token_updated_at: Date.now(),
315
+ addedAt: Date.now(),
316
+ lastUsed: 0,
317
+ enabled: true,
318
+ rateLimitResetTimes: {},
319
+ consecutiveFailures: 0,
320
+ lastFailureTime: null,
321
+ email: undefined,
322
+ stats: {
323
+ requests: 0,
324
+ inputTokens: 0,
325
+ outputTokens: 0,
326
+ cacheReadTokens: 0,
327
+ cacheWriteTokens: 0,
328
+ lastReset: Date.now(),
329
+ },
330
+ source: "oauth" as const,
331
+ },
332
+ ];
333
+
334
+ setMockAccounts(testAccounts, 0);
335
+ clearMockAccounts();
336
+ });
337
337
  });