@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.
- package/README.md +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- package/dist/bun-proxy.mjs +0 -291
|
@@ -31,39 +31,39 @@ export type MockResponseMap = Record<string, Partial<Response> | (() => Partial<
|
|
|
31
31
|
|
|
32
32
|
/** Account data structure for harness initialization */
|
|
33
33
|
export interface HarnessAccount {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
refreshToken?: string;
|
|
35
|
+
access?: string;
|
|
36
|
+
expires?: number;
|
|
37
|
+
email?: string;
|
|
38
|
+
enabled?: boolean;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** Options for creating a fetch harness */
|
|
42
42
|
export interface HarnessOptions {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
/** Account overrides to initialize the harness with (default: single test account) */
|
|
44
|
+
accounts?: HarnessAccount[];
|
|
45
|
+
/** Plugin configuration overrides (default: test-friendly defaults) */
|
|
46
|
+
config?: Partial<AnthropicAuthConfig>;
|
|
47
|
+
/** Mock responses for specific URLs (default: empty) */
|
|
48
|
+
mockResponses?: MockResponseMap;
|
|
49
|
+
/** Initial account index to set as active (default: 0) */
|
|
50
|
+
initialAccount?: number;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/** The fetch harness instance returned by createFetchHarness */
|
|
54
54
|
export interface FetchHarness {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
/** The fetch interceptor function returned by plugin.auth.loader */
|
|
56
|
+
fetch: (input: string | Request | URL, init?: RequestInit) => Promise<Response>;
|
|
57
|
+
/** The mocked global fetch (vi.fn()) — use for assertions */
|
|
58
|
+
mockFetch: Mock;
|
|
59
|
+
/** Cleanup function to restore global state */
|
|
60
|
+
tearDown: () => void;
|
|
61
|
+
/** Helper to wait for an assertion to pass (polling) */
|
|
62
|
+
waitFor: (assertion: () => void, timeoutMs?: number) => Promise<void>;
|
|
63
|
+
/** Get the headers from a specific mock fetch call */
|
|
64
|
+
getFetchHeaders: (callIndex: number) => Headers | undefined;
|
|
65
|
+
/** Get the URL from a specific mock fetch call */
|
|
66
|
+
getFetchUrl: (callIndex: number) => string | Request | undefined;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// ---------------------------------------------------------------------------
|
|
@@ -72,112 +72,112 @@ export interface FetchHarness {
|
|
|
72
72
|
|
|
73
73
|
/** Default test account factory */
|
|
74
74
|
function makeStoredAccount(index: number, overrides: HarnessAccount = {}) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
75
|
+
const addedAt = Date.now();
|
|
76
|
+
return {
|
|
77
|
+
id: `test-${addedAt}-${index}`,
|
|
78
|
+
index,
|
|
79
|
+
refreshToken: overrides.refreshToken ?? `refresh-${index + 1}`,
|
|
80
|
+
access: overrides.access ?? `access-${index + 1}`,
|
|
81
|
+
expires: overrides.expires ?? Date.now() + 3600_000,
|
|
82
|
+
token_updated_at: addedAt,
|
|
83
|
+
addedAt,
|
|
84
|
+
lastUsed: 0,
|
|
85
|
+
enabled: overrides.enabled ?? true,
|
|
86
|
+
rateLimitResetTimes: {},
|
|
87
|
+
consecutiveFailures: 0,
|
|
88
|
+
lastFailureTime: null,
|
|
89
|
+
email: overrides.email,
|
|
90
|
+
stats: {
|
|
91
|
+
requests: 0,
|
|
92
|
+
inputTokens: 0,
|
|
93
|
+
outputTokens: 0,
|
|
94
|
+
cacheReadTokens: 0,
|
|
95
|
+
cacheWriteTokens: 0,
|
|
96
|
+
lastReset: addedAt,
|
|
97
|
+
},
|
|
98
|
+
source: "oauth" as const,
|
|
99
|
+
};
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/** Default mock client factory */
|
|
103
103
|
function makeMockClient() {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
return {
|
|
105
|
+
auth: {
|
|
106
|
+
set: vi.fn().mockResolvedValue(undefined),
|
|
107
|
+
},
|
|
108
|
+
session: {
|
|
109
|
+
prompt: vi.fn().mockResolvedValue(undefined),
|
|
110
|
+
},
|
|
111
|
+
tui: {
|
|
112
|
+
showToast: vi.fn().mockResolvedValue(undefined),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/** Default mock provider factory */
|
|
118
118
|
function makeMockProvider() {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
119
|
+
return {
|
|
120
|
+
models: {
|
|
121
|
+
"claude-sonnet": {
|
|
122
|
+
id: "claude-sonnet",
|
|
123
|
+
cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } },
|
|
124
|
+
limit: { context: 200_000, output: 8192 },
|
|
125
|
+
},
|
|
126
|
+
"claude-opus-4-6": {
|
|
127
|
+
id: "claude-opus-4-6",
|
|
128
|
+
cost: { input: 15, output: 75, cache: { read: 1.5, write: 18.75 } },
|
|
129
|
+
limit: { context: 200_000, output: 32_000 },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/** Default test configuration */
|
|
136
136
|
const DEFAULT_TEST_CONFIG: AnthropicAuthConfig = {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
137
|
+
account_selection_strategy: "sticky",
|
|
138
|
+
failure_ttl_seconds: 3600,
|
|
139
|
+
debug: false,
|
|
140
|
+
signature_emulation: {
|
|
141
|
+
enabled: true,
|
|
142
|
+
fetch_claude_code_version_on_startup: false,
|
|
143
|
+
prompt_compaction: "minimal",
|
|
144
|
+
sanitize_system_prompt: false,
|
|
145
|
+
},
|
|
146
|
+
override_model_limits: {
|
|
147
|
+
enabled: false,
|
|
148
|
+
context: 1_000_000,
|
|
149
|
+
output: 0,
|
|
150
|
+
},
|
|
151
|
+
custom_betas: [],
|
|
152
|
+
health_score: {
|
|
153
|
+
initial: 70,
|
|
154
|
+
success_reward: 1,
|
|
155
|
+
rate_limit_penalty: -10,
|
|
156
|
+
failure_penalty: -20,
|
|
157
|
+
recovery_rate_per_hour: 2,
|
|
158
|
+
min_usable: 50,
|
|
159
|
+
max_score: 100,
|
|
160
|
+
},
|
|
161
|
+
token_bucket: {
|
|
162
|
+
max_tokens: 50,
|
|
163
|
+
regeneration_rate_per_minute: 6,
|
|
164
|
+
initial_tokens: 50,
|
|
165
|
+
},
|
|
166
|
+
toasts: {
|
|
167
|
+
quiet: true,
|
|
168
|
+
debounce_seconds: 30,
|
|
169
|
+
},
|
|
170
|
+
headers: {},
|
|
171
|
+
idle_refresh: {
|
|
172
|
+
enabled: false,
|
|
173
|
+
window_minutes: 60,
|
|
174
|
+
min_interval_minutes: 30,
|
|
175
|
+
},
|
|
176
|
+
cc_credential_reuse: {
|
|
177
|
+
enabled: false,
|
|
178
|
+
auto_detect: false,
|
|
179
|
+
prefer_over_oauth: false,
|
|
180
|
+
},
|
|
181
181
|
};
|
|
182
182
|
|
|
183
183
|
// ---------------------------------------------------------------------------
|
|
@@ -185,9 +185,9 @@ const DEFAULT_TEST_CONFIG: AnthropicAuthConfig = {
|
|
|
185
185
|
// ---------------------------------------------------------------------------
|
|
186
186
|
|
|
187
187
|
let mockAccountsData: {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
version: number;
|
|
189
|
+
accounts: ReturnType<typeof makeStoredAccount>[];
|
|
190
|
+
activeIndex: number;
|
|
191
191
|
} | null = null;
|
|
192
192
|
|
|
193
193
|
/**
|
|
@@ -195,14 +195,14 @@ let mockAccountsData: {
|
|
|
195
195
|
* Call this before createFetchHarness to pre-seed accounts.
|
|
196
196
|
*/
|
|
197
197
|
export function setMockAccounts(accounts: ReturnType<typeof makeStoredAccount>[], activeIndex = 0): void {
|
|
198
|
-
|
|
198
|
+
mockAccountsData = { version: 1, accounts, activeIndex };
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/**
|
|
202
202
|
* Clear the mock accounts data.
|
|
203
203
|
*/
|
|
204
204
|
export function clearMockAccounts(): void {
|
|
205
|
-
|
|
205
|
+
mockAccountsData = null;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// ---------------------------------------------------------------------------
|
|
@@ -223,180 +223,180 @@ export function clearMockAccounts(): void {
|
|
|
223
223
|
* @returns A FetchHarness instance ready for testing
|
|
224
224
|
*/
|
|
225
225
|
export async function createFetchHarness(opts: HarnessOptions = {}): Promise<FetchHarness> {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Check for matching mock response
|
|
266
|
-
for (const [pattern, response] of Object.entries(mockResponses)) {
|
|
267
|
-
if (url && url.includes(pattern)) {
|
|
268
|
-
const responseObj = typeof response === "function" ? response() : response;
|
|
269
|
-
if (responseObj instanceof Response) {
|
|
270
|
-
return Promise.resolve(responseObj);
|
|
226
|
+
const { accounts = [{}], config = {}, mockResponses = {}, initialAccount = 0 } = opts;
|
|
227
|
+
|
|
228
|
+
// Merge config with defaults
|
|
229
|
+
const mergedConfig: AnthropicAuthConfig = {
|
|
230
|
+
...DEFAULT_TEST_CONFIG,
|
|
231
|
+
...config,
|
|
232
|
+
signature_emulation: {
|
|
233
|
+
...DEFAULT_TEST_CONFIG.signature_emulation,
|
|
234
|
+
...config.signature_emulation,
|
|
235
|
+
},
|
|
236
|
+
health_score: { ...DEFAULT_TEST_CONFIG.health_score, ...config.health_score },
|
|
237
|
+
token_bucket: { ...DEFAULT_TEST_CONFIG.token_bucket, ...config.token_bucket },
|
|
238
|
+
toasts: { ...DEFAULT_TEST_CONFIG.toasts, ...config.toasts },
|
|
239
|
+
override_model_limits: {
|
|
240
|
+
...DEFAULT_TEST_CONFIG.override_model_limits,
|
|
241
|
+
...config.override_model_limits,
|
|
242
|
+
},
|
|
243
|
+
idle_refresh: { ...DEFAULT_TEST_CONFIG.idle_refresh, ...config.idle_refresh },
|
|
244
|
+
cc_credential_reuse: { ...DEFAULT_TEST_CONFIG.cc_credential_reuse, ...config.cc_credential_reuse },
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Create mock client
|
|
248
|
+
const client = makeMockClient();
|
|
249
|
+
|
|
250
|
+
// Store original fetch and create mock
|
|
251
|
+
const originalFetch = globalThis.fetch;
|
|
252
|
+
const mockFetch = vi.fn();
|
|
253
|
+
|
|
254
|
+
// Set up mock response handler
|
|
255
|
+
mockFetch.mockImplementation((input: string | Request | URL) => {
|
|
256
|
+
let url: string;
|
|
257
|
+
if (typeof input === "string") {
|
|
258
|
+
url = input;
|
|
259
|
+
} else if (input instanceof URL) {
|
|
260
|
+
url = input.toString();
|
|
261
|
+
} else {
|
|
262
|
+
url = input.url;
|
|
271
263
|
}
|
|
272
264
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
265
|
+
// Check for matching mock response
|
|
266
|
+
for (const [pattern, response] of Object.entries(mockResponses)) {
|
|
267
|
+
if (url && url.includes(pattern)) {
|
|
268
|
+
const responseObj = typeof response === "function" ? response() : response;
|
|
269
|
+
if (responseObj instanceof Response) {
|
|
270
|
+
return Promise.resolve(responseObj);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (typeof responseObj.json === "function") {
|
|
274
|
+
return responseObj.json().then(
|
|
275
|
+
(jsonBody) =>
|
|
276
|
+
new Response(JSON.stringify(jsonBody), {
|
|
277
|
+
status: responseObj.status ?? 200,
|
|
278
|
+
statusText: responseObj.statusText ?? "OK",
|
|
279
|
+
headers:
|
|
280
|
+
responseObj.headers instanceof Headers
|
|
281
|
+
? responseObj.headers
|
|
282
|
+
: new Headers(responseObj.headers ?? { "content-type": "application/json" }),
|
|
283
|
+
}),
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (typeof responseObj.text === "function") {
|
|
288
|
+
return responseObj.text().then(
|
|
289
|
+
(textBody) =>
|
|
290
|
+
new Response(textBody, {
|
|
291
|
+
status: responseObj.status ?? 200,
|
|
292
|
+
statusText: responseObj.statusText ?? "OK",
|
|
293
|
+
headers:
|
|
294
|
+
responseObj.headers instanceof Headers
|
|
295
|
+
? responseObj.headers
|
|
296
|
+
: new Headers(responseObj.headers ?? undefined),
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return Promise.resolve(
|
|
302
|
+
new Response(undefined, {
|
|
303
|
+
status: responseObj.status ?? 200,
|
|
304
|
+
statusText: responseObj.statusText ?? "OK",
|
|
305
|
+
headers:
|
|
306
|
+
responseObj.headers instanceof Headers
|
|
307
|
+
? responseObj.headers
|
|
308
|
+
: new Headers(responseObj.headers ?? undefined),
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
285
312
|
}
|
|
286
313
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
314
|
+
// Default: return empty successful response
|
|
315
|
+
return Promise.resolve(new Response("{}", { status: 200, headers: { "content-type": "application/json" } }));
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Install mock
|
|
319
|
+
globalThis.fetch = mockFetch as unknown as typeof globalThis.fetch;
|
|
320
|
+
|
|
321
|
+
// Build accounts data
|
|
322
|
+
const managedAccounts = accounts.map((overrides, index) => makeStoredAccount(index, overrides));
|
|
323
|
+
|
|
324
|
+
// Pre-seed mock accounts
|
|
325
|
+
mockAccountsData = { version: 1, accounts: managedAccounts, activeIndex: initialAccount };
|
|
326
|
+
|
|
327
|
+
// Dynamically import modules to get mocked versions
|
|
328
|
+
const { AnthropicAuthPlugin } = await import("../../index.js");
|
|
329
|
+
const { loadConfig, loadConfigFresh } = await import("../../config.js");
|
|
330
|
+
const { loadAccounts } = await import("../../storage.js");
|
|
331
|
+
|
|
332
|
+
// Override config mock to return our merged config
|
|
333
|
+
vi.mocked(loadConfig).mockReturnValue(mergedConfig);
|
|
334
|
+
vi.mocked(loadConfigFresh).mockReturnValue(mergedConfig);
|
|
335
|
+
vi.mocked(loadAccounts).mockResolvedValue(mockAccountsData);
|
|
336
|
+
|
|
337
|
+
// Initialize plugin
|
|
338
|
+
const plugin = await AnthropicAuthPlugin({ client });
|
|
339
|
+
|
|
340
|
+
// Create mock auth getter
|
|
341
|
+
const getAuth = vi.fn().mockResolvedValue({
|
|
342
|
+
type: "oauth",
|
|
343
|
+
refresh: managedAccounts[initialAccount]?.refreshToken ?? "refresh-1",
|
|
344
|
+
access: managedAccounts[initialAccount]?.access ?? "access-1",
|
|
345
|
+
expires: Date.now() + 3600_000,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Initialize the plugin's auth loader
|
|
349
|
+
const provider = makeMockProvider();
|
|
350
|
+
const result = await plugin.auth.loader(getAuth, provider);
|
|
351
|
+
|
|
352
|
+
// Helper: wait for assertion
|
|
353
|
+
async function waitFor(assertion: () => void, timeoutMs = 500): Promise<void> {
|
|
354
|
+
const started = Date.now();
|
|
355
|
+
while (true) {
|
|
356
|
+
try {
|
|
357
|
+
assertion();
|
|
358
|
+
return;
|
|
359
|
+
} catch (err) {
|
|
360
|
+
if (Date.now() - started >= timeoutMs) throw err;
|
|
361
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
362
|
+
}
|
|
299
363
|
}
|
|
364
|
+
}
|
|
300
365
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
headers:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
366
|
+
// Helper: get fetch headers
|
|
367
|
+
function getFetchHeaders(callIndex: number): Headers | undefined {
|
|
368
|
+
const [input, init] = mockFetch.mock.calls[callIndex] ?? [];
|
|
369
|
+
if (init?.headers) {
|
|
370
|
+
return init.headers instanceof Headers ? init.headers : new Headers(init.headers);
|
|
371
|
+
}
|
|
372
|
+
if (input instanceof Request) {
|
|
373
|
+
return input.headers;
|
|
374
|
+
}
|
|
375
|
+
return undefined;
|
|
312
376
|
}
|
|
313
377
|
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
// Install mock
|
|
319
|
-
globalThis.fetch = mockFetch as unknown as typeof globalThis.fetch;
|
|
320
|
-
|
|
321
|
-
// Build accounts data
|
|
322
|
-
const managedAccounts = accounts.map((overrides, index) => makeStoredAccount(index, overrides));
|
|
323
|
-
|
|
324
|
-
// Pre-seed mock accounts
|
|
325
|
-
mockAccountsData = { version: 1, accounts: managedAccounts, activeIndex: initialAccount };
|
|
326
|
-
|
|
327
|
-
// Dynamically import modules to get mocked versions
|
|
328
|
-
const { AnthropicAuthPlugin } = await import("../../index.js");
|
|
329
|
-
const { loadConfig, loadConfigFresh } = await import("../../config.js");
|
|
330
|
-
const { loadAccounts } = await import("../../storage.js");
|
|
331
|
-
|
|
332
|
-
// Override config mock to return our merged config
|
|
333
|
-
vi.mocked(loadConfig).mockReturnValue(mergedConfig);
|
|
334
|
-
vi.mocked(loadConfigFresh).mockReturnValue(mergedConfig);
|
|
335
|
-
vi.mocked(loadAccounts).mockResolvedValue(mockAccountsData);
|
|
336
|
-
|
|
337
|
-
// Initialize plugin
|
|
338
|
-
const plugin = await AnthropicAuthPlugin({ client });
|
|
339
|
-
|
|
340
|
-
// Create mock auth getter
|
|
341
|
-
const getAuth = vi.fn().mockResolvedValue({
|
|
342
|
-
type: "oauth",
|
|
343
|
-
refresh: managedAccounts[initialAccount]?.refreshToken ?? "refresh-1",
|
|
344
|
-
access: managedAccounts[initialAccount]?.access ?? "access-1",
|
|
345
|
-
expires: Date.now() + 3600_000,
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
// Initialize the plugin's auth loader
|
|
349
|
-
const provider = makeMockProvider();
|
|
350
|
-
const result = await plugin.auth.loader(getAuth, provider);
|
|
351
|
-
|
|
352
|
-
// Helper: wait for assertion
|
|
353
|
-
async function waitFor(assertion: () => void, timeoutMs = 500): Promise<void> {
|
|
354
|
-
const started = Date.now();
|
|
355
|
-
while (true) {
|
|
356
|
-
try {
|
|
357
|
-
assertion();
|
|
358
|
-
return;
|
|
359
|
-
} catch (err) {
|
|
360
|
-
if (Date.now() - started >= timeoutMs) throw err;
|
|
361
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
362
|
-
}
|
|
378
|
+
// Helper: get fetch URL
|
|
379
|
+
function getFetchUrl(callIndex: number): string | Request | undefined {
|
|
380
|
+
const [input] = mockFetch.mock.calls[callIndex] ?? [];
|
|
381
|
+
return input;
|
|
363
382
|
}
|
|
364
|
-
}
|
|
365
383
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return init.headers instanceof Headers ? init.headers : new Headers(init.headers);
|
|
384
|
+
// Cleanup function
|
|
385
|
+
function tearDown(): void {
|
|
386
|
+
globalThis.fetch = originalFetch;
|
|
387
|
+
clearMockAccounts();
|
|
371
388
|
}
|
|
372
|
-
|
|
373
|
-
|
|
389
|
+
|
|
390
|
+
if (!result.fetch) {
|
|
391
|
+
throw new Error("Plugin did not return a fetch function");
|
|
374
392
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// Cleanup function
|
|
385
|
-
function tearDown(): void {
|
|
386
|
-
globalThis.fetch = originalFetch;
|
|
387
|
-
clearMockAccounts();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (!result.fetch) {
|
|
391
|
-
throw new Error("Plugin did not return a fetch function");
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return {
|
|
395
|
-
fetch: result.fetch,
|
|
396
|
-
mockFetch,
|
|
397
|
-
tearDown,
|
|
398
|
-
waitFor,
|
|
399
|
-
getFetchHeaders,
|
|
400
|
-
getFetchUrl,
|
|
401
|
-
};
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
fetch: result.fetch,
|
|
396
|
+
mockFetch,
|
|
397
|
+
tearDown,
|
|
398
|
+
waitFor,
|
|
399
|
+
getFetchHeaders,
|
|
400
|
+
getFetchUrl,
|
|
401
|
+
};
|
|
402
402
|
}
|