opencode-anthropic-fix 0.0.14
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/.github/workflows/publish.yml +31 -0
- package/.husky/pre-commit +2 -0
- package/.husky/pre-push +3 -0
- package/.planning/CLI-ENHANCEMENTS.md +221 -0
- package/.planning/codebase/ARCHITECTURE.md +201 -0
- package/.planning/codebase/CONCERNS.md +219 -0
- package/.planning/codebase/CONVENTIONS.md +335 -0
- package/.planning/codebase/INTEGRATIONS.md +215 -0
- package/.planning/codebase/STACK.md +160 -0
- package/.planning/codebase/STRUCTURE.md +244 -0
- package/.planning/codebase/TESTING.md +456 -0
- package/.prettierignore +3 -0
- package/.prettierrc +10 -0
- package/CONTRIBUTING.md +525 -0
- package/LICENSE +21 -0
- package/README.md +478 -0
- package/agents.md +22 -0
- package/bun.lock +43 -0
- package/cli.mjs +1773 -0
- package/cli.test.mjs +1797 -0
- package/docs/mimese-http-header-system-prompt.md +326 -0
- package/docs/plans/afcw-upstream-watcher.json +495 -0
- package/eslint.config.mjs +69 -0
- package/index.mjs +2209 -0
- package/index.test.mjs +2690 -0
- package/lib/accounts.mjs +670 -0
- package/lib/accounts.test.mjs +782 -0
- package/lib/backoff.mjs +217 -0
- package/lib/backoff.test.mjs +296 -0
- package/lib/config.mjs +366 -0
- package/lib/config.test.mjs +307 -0
- package/lib/oauth.mjs +103 -0
- package/lib/rotation.mjs +287 -0
- package/lib/rotation.test.mjs +346 -0
- package/lib/storage.mjs +265 -0
- package/lib/storage.test.mjs +470 -0
- package/nul +1 -0
- package/package.json +40 -0
- package/script/publish.ts +29 -0
- package/scripts/build.mjs +34 -0
- package/scripts/install.mjs +319 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# Testing Patterns
|
|
2
|
+
|
|
3
|
+
**Analysis Date:** 2026-02-07
|
|
4
|
+
|
|
5
|
+
## Test Framework
|
|
6
|
+
|
|
7
|
+
**Runner:**
|
|
8
|
+
|
|
9
|
+
- Vitest 4.x
|
|
10
|
+
- No config file — uses Vitest defaults (auto-discovers `*.test.mjs` files)
|
|
11
|
+
- Global test APIs **not** enabled — all test functions must be explicitly imported
|
|
12
|
+
|
|
13
|
+
**Assertion Library:**
|
|
14
|
+
|
|
15
|
+
- Vitest built-in `expect` (Jest-compatible API)
|
|
16
|
+
|
|
17
|
+
**Run Commands:**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm test # Run all tests (vitest run — single pass, CI mode)
|
|
21
|
+
npm run test:watch # Watch mode (vitest — re-runs on file change)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Pre-push hook:** Husky runs `npm test` before every push.
|
|
25
|
+
|
|
26
|
+
## Test File Organization
|
|
27
|
+
|
|
28
|
+
**Location:**
|
|
29
|
+
|
|
30
|
+
- Co-located with source files (test file next to the module it tests)
|
|
31
|
+
|
|
32
|
+
**Naming:**
|
|
33
|
+
|
|
34
|
+
- `{module-name}.test.mjs` — same name as source file with `.test` suffix
|
|
35
|
+
|
|
36
|
+
**Structure:**
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
project-root/
|
|
40
|
+
├── index.mjs # Plugin entry point
|
|
41
|
+
├── index.test.mjs # Integration tests (49 tests)
|
|
42
|
+
├── cli.mjs # CLI tool
|
|
43
|
+
├── cli.test.mjs # CLI command tests (87 tests)
|
|
44
|
+
└── lib/
|
|
45
|
+
├── accounts.mjs
|
|
46
|
+
├── accounts.test.mjs # AccountManager tests (67 tests)
|
|
47
|
+
├── backoff.mjs
|
|
48
|
+
├── backoff.test.mjs # Rate limit classification tests (42 tests)
|
|
49
|
+
├── config.mjs
|
|
50
|
+
├── config.test.mjs # Config loading/validation tests (25 tests)
|
|
51
|
+
├── rotation.mjs
|
|
52
|
+
├── rotation.test.mjs # Health score, token bucket, selection tests (38 tests)
|
|
53
|
+
├── storage.mjs
|
|
54
|
+
└── storage.test.mjs # Storage I/O and validation tests (27 tests)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Total:** 7 test files, 335 tests, all passing.
|
|
58
|
+
|
|
59
|
+
## Test Structure
|
|
60
|
+
|
|
61
|
+
**Imports — always explicit:**
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Suite organization — `describe` blocks by feature/method, not by file:**
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// AccountManager.load
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
describe("AccountManager.load", () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
vi.resetAllMocks();
|
|
77
|
+
vi.useFakeTimers();
|
|
78
|
+
vi.setSystemTime(new Date("2026-01-15T12:00:00Z"));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("creates empty manager when no stored accounts and no fallback", async () => {
|
|
82
|
+
loadAccounts.mockResolvedValue(null);
|
|
83
|
+
const manager = await AccountManager.load(DEFAULT_CONFIG, null);
|
|
84
|
+
expect(manager.getAccountCount()).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Key patterns:**
|
|
90
|
+
|
|
91
|
+
- Section separator comments (`// ---`) between major `describe` groups
|
|
92
|
+
- `beforeEach` with `vi.resetAllMocks()` at the start of every `describe` block
|
|
93
|
+
- `vi.useFakeTimers()` and `vi.setSystemTime()` for time-sensitive tests
|
|
94
|
+
- Flat `it()` calls with descriptive names — no deep nesting beyond one `describe` level
|
|
95
|
+
- JSDoc comments on test helper functions
|
|
96
|
+
|
|
97
|
+
## Mocking
|
|
98
|
+
|
|
99
|
+
### Module Mocks (`vi.mock`)
|
|
100
|
+
|
|
101
|
+
**Pattern: Partial mock with `importOriginal`**
|
|
102
|
+
Keep pure helper functions real, mock only I/O:
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
vi.mock("./storage.mjs", async (importOriginal) => {
|
|
106
|
+
const actual = await importOriginal();
|
|
107
|
+
return {
|
|
108
|
+
...actual, // Keep pure helpers
|
|
109
|
+
loadAccounts: vi.fn(), // Mock I/O functions
|
|
110
|
+
saveAccounts: vi.fn().mockResolvedValue(undefined),
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Used in: `lib/accounts.test.mjs`, `index.test.mjs`
|
|
116
|
+
|
|
117
|
+
**Pattern: Full mock for system modules**
|
|
118
|
+
Replace entire modules when all exports need control:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
vi.mock("node:fs", () => ({
|
|
122
|
+
existsSync: vi.fn(),
|
|
123
|
+
readFileSync: vi.fn(),
|
|
124
|
+
appendFileSync: vi.fn(),
|
|
125
|
+
writeFileSync: vi.fn(),
|
|
126
|
+
promises: {
|
|
127
|
+
readFile: vi.fn(),
|
|
128
|
+
writeFile: vi.fn(),
|
|
129
|
+
rename: vi.fn(),
|
|
130
|
+
chmod: vi.fn(),
|
|
131
|
+
unlink: vi.fn(),
|
|
132
|
+
mkdir: vi.fn(),
|
|
133
|
+
},
|
|
134
|
+
}));
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Used in: `lib/storage.test.mjs`
|
|
138
|
+
|
|
139
|
+
**Pattern: Mock with inline return value**
|
|
140
|
+
For simple stubs:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
vi.mock("node:crypto", () => ({
|
|
144
|
+
randomBytes: vi.fn(() => ({
|
|
145
|
+
toString: () => "abcdef123456",
|
|
146
|
+
})),
|
|
147
|
+
}));
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Pattern: Mock third-party packages**
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
vi.mock("@openauthjs/openauth/pkce", () => ({
|
|
154
|
+
generatePKCE: vi.fn(async () => ({
|
|
155
|
+
challenge: "test-challenge",
|
|
156
|
+
verifier: "test-verifier",
|
|
157
|
+
})),
|
|
158
|
+
}));
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Import Order Rule
|
|
162
|
+
|
|
163
|
+
**Mocks must be declared BEFORE importing the module under test.** Vitest hoists `vi.mock()` calls, but the mock setup still must appear before the import in the file for clarity:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
// 1. Set up mocks
|
|
167
|
+
vi.mock("./lib/storage.mjs", async (importOriginal) => { ... });
|
|
168
|
+
vi.mock("./lib/config.mjs", async (importOriginal) => { ... });
|
|
169
|
+
|
|
170
|
+
// 2. Global mocks
|
|
171
|
+
const mockFetch = vi.fn();
|
|
172
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
173
|
+
|
|
174
|
+
// 3. Import module under test
|
|
175
|
+
import { AnthropicAuthPlugin } from "./index.mjs";
|
|
176
|
+
import { saveAccounts, loadAccounts } from "./lib/storage.mjs";
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Global Mocks (`vi.stubGlobal`)
|
|
180
|
+
|
|
181
|
+
**Pattern: Mock `fetch` globally**
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
const mockFetch = vi.fn();
|
|
185
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Used in: `index.test.mjs`, `cli.test.mjs`
|
|
189
|
+
|
|
190
|
+
### Timer Mocks
|
|
191
|
+
|
|
192
|
+
**Pattern: Fake timers for time-dependent logic**
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
vi.useFakeTimers();
|
|
197
|
+
vi.setSystemTime(new Date("2026-01-15T12:00:00Z"));
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Used in: `lib/accounts.test.mjs` for debounced save, token expiry, etc.
|
|
202
|
+
|
|
203
|
+
**What to mock:**
|
|
204
|
+
|
|
205
|
+
- File system I/O (`node:fs`)
|
|
206
|
+
- Network requests (`fetch`)
|
|
207
|
+
- System time (`vi.useFakeTimers`)
|
|
208
|
+
- External packages (`@openauthjs/openauth/pkce`)
|
|
209
|
+
- Interactive I/O (`node:readline/promises`)
|
|
210
|
+
- Crypto (`node:crypto` for deterministic tests)
|
|
211
|
+
|
|
212
|
+
**What NOT to mock:**
|
|
213
|
+
|
|
214
|
+
- Pure functions (validation, deduplication, formatting, clamping)
|
|
215
|
+
- Class constructors and their in-memory logic
|
|
216
|
+
- Config defaults and constants
|
|
217
|
+
|
|
218
|
+
## Factory Helpers
|
|
219
|
+
|
|
220
|
+
**Test data factories are defined per test file, not shared.** Each test file defines its own factory functions at the top, after mocks.
|
|
221
|
+
|
|
222
|
+
**`makeStoredAccount(overrides)` — Build a single account object:**
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
function makeStoredAccount(overrides = {}) {
|
|
226
|
+
return {
|
|
227
|
+
refreshToken: "token1",
|
|
228
|
+
addedAt: 1000,
|
|
229
|
+
lastUsed: 0,
|
|
230
|
+
enabled: true,
|
|
231
|
+
rateLimitResetTimes: {},
|
|
232
|
+
consecutiveFailures: 0,
|
|
233
|
+
lastFailureTime: null,
|
|
234
|
+
...overrides,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Used in: `lib/accounts.test.mjs`, `index.test.mjs`
|
|
240
|
+
|
|
241
|
+
**`makeAccountsData(overrides, extra)` — Build full storage payload:**
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
function makeAccountsData(overrides = [{}], extra = {}) {
|
|
245
|
+
return {
|
|
246
|
+
version: 1,
|
|
247
|
+
accounts: overrides.map((o, i) =>
|
|
248
|
+
makeStoredAccount({ refreshToken: `token${i + 1}`, addedAt: (i + 1) * 1000, ...o }),
|
|
249
|
+
),
|
|
250
|
+
activeIndex: 0,
|
|
251
|
+
...extra,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Used in: `lib/accounts.test.mjs`
|
|
257
|
+
|
|
258
|
+
**`makeClient()` / `makeProvider()` — Build OpenCode plugin client/provider stubs:**
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
function makeClient() {
|
|
262
|
+
return {
|
|
263
|
+
auth: { set: vi.fn().mockResolvedValue(undefined) },
|
|
264
|
+
tui: { showToast: vi.fn().mockResolvedValue(undefined) },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function makeProvider() {
|
|
269
|
+
return {
|
|
270
|
+
models: {
|
|
271
|
+
"claude-sonnet": {
|
|
272
|
+
cost: { input: 3, output: 15, cache: { read: 0.3, write: 3.75 } },
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Used in: `index.test.mjs`
|
|
280
|
+
|
|
281
|
+
**`makeStorage(overrides)` / `captureOutput()` — CLI test helpers:**
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
/** Capture console.log and console.error output */
|
|
285
|
+
function captureOutput() {
|
|
286
|
+
const logs = [];
|
|
287
|
+
const errors = [];
|
|
288
|
+
const origLog = console.log;
|
|
289
|
+
const origError = console.error;
|
|
290
|
+
|
|
291
|
+
console.log = (...args) => logs.push(args.join(" "));
|
|
292
|
+
console.error = (...args) => errors.push(args.join(" "));
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
logs,
|
|
296
|
+
errors,
|
|
297
|
+
text: () => logs.join("\n").replace(/\x1b\[[0-9;]*m/g, ""),
|
|
298
|
+
errorText: () => errors.join("\n").replace(/\x1b\[[0-9;]*m/g, ""),
|
|
299
|
+
restore: () => {
|
|
300
|
+
console.log = origLog;
|
|
301
|
+
console.error = origError;
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Used in: `cli.test.mjs`
|
|
308
|
+
|
|
309
|
+
## Fixture Patterns
|
|
310
|
+
|
|
311
|
+
**No shared fixture files.** All test data is created inline via factory functions (above).
|
|
312
|
+
|
|
313
|
+
**Large data setup — use factory composition:**
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
loadAccounts.mockResolvedValue(makeAccountsData([{ lastUsed: 2000 }, { lastUsed: 4000 }], { activeIndex: 1 }));
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Mock response setup for fetch:**
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
mockFetch.mockResolvedValueOnce(
|
|
323
|
+
new Response(JSON.stringify({ error: { type: "overloaded_error", message: "Server is overloaded" } }), {
|
|
324
|
+
status: 529,
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Coverage
|
|
330
|
+
|
|
331
|
+
**Requirements:** None enforced. No coverage configuration or thresholds.
|
|
332
|
+
|
|
333
|
+
**No coverage script in package.json.** To run coverage manually:
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
npx vitest run --coverage
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Test Types
|
|
340
|
+
|
|
341
|
+
**Unit Tests** (`lib/*.test.mjs`):
|
|
342
|
+
|
|
343
|
+
- Test individual modules in isolation
|
|
344
|
+
- Mock I/O dependencies, test pure logic directly
|
|
345
|
+
- Each class/function gets its own `describe` block
|
|
346
|
+
- Files: `lib/accounts.test.mjs`, `lib/backoff.test.mjs`, `lib/config.test.mjs`, `lib/rotation.test.mjs`, `lib/storage.test.mjs`
|
|
347
|
+
|
|
348
|
+
**Integration Tests** (`index.test.mjs`):
|
|
349
|
+
|
|
350
|
+
- Test the plugin lifecycle end-to-end: auth → callback → fetch interceptor
|
|
351
|
+
- Mock external boundaries (fetch, storage, config) but exercise real plugin wiring
|
|
352
|
+
- Tests: OAuth flow, token refresh, rate-limit retry loop, SSE stream rewriting, account rotation
|
|
353
|
+
- File header documents the integration scope:
|
|
354
|
+
```
|
|
355
|
+
These test the wiring in index.mjs — the ordering of authorize → callback → loader,
|
|
356
|
+
the accountManager initialization, and the fetch interceptor retry loop.
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**CLI Tests** (`cli.test.mjs`):
|
|
360
|
+
|
|
361
|
+
- Test each CLI command function independently
|
|
362
|
+
- Mock storage/config I/O, capture console output
|
|
363
|
+
- Verify exit codes, output formatting, and state mutations
|
|
364
|
+
|
|
365
|
+
**E2E Tests:**
|
|
366
|
+
|
|
367
|
+
- Not used. No browser/E2E test framework configured.
|
|
368
|
+
|
|
369
|
+
## Common Patterns
|
|
370
|
+
|
|
371
|
+
**Async Testing:**
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
it("loads stored accounts from disk", async () => {
|
|
375
|
+
loadAccounts.mockResolvedValue(makeAccountsData([{ lastUsed: 2000 }]));
|
|
376
|
+
const manager = await AccountManager.load(DEFAULT_CONFIG, null);
|
|
377
|
+
expect(manager.getAccountCount()).toBe(1);
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Error Testing:**
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
it("returns null on malformed JSON", async () => {
|
|
385
|
+
fs.promises.readFile.mockResolvedValue("not json");
|
|
386
|
+
const result = await loadAccounts();
|
|
387
|
+
expect(result).toBeNull();
|
|
388
|
+
});
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Parameterized Tests (`it.each`):**
|
|
392
|
+
|
|
393
|
+
```javascript
|
|
394
|
+
it.each([
|
|
395
|
+
{ status: 529, errorType: "overloaded_error", errorMsg: "Server is overloaded" },
|
|
396
|
+
{ status: 503, errorType: "service_unavailable", errorMsg: "temporarily unavailable" },
|
|
397
|
+
{ status: 500, errorType: "internal_error", errorMsg: "internal server error" },
|
|
398
|
+
])("returns $status directly without switching accounts", async ({ status, errorType, errorMsg }) => {
|
|
399
|
+
// ...test body using destructured params
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Mock assertion pattern:**
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
expect(saveAccounts).toHaveBeenCalledTimes(1);
|
|
407
|
+
expect(saveAccounts).toHaveBeenCalledWith(
|
|
408
|
+
expect.objectContaining({
|
|
409
|
+
accounts: expect.arrayContaining([expect.objectContaining({ refreshToken: "token1" })]),
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**Console output testing (CLI):**
|
|
415
|
+
|
|
416
|
+
```javascript
|
|
417
|
+
const out = captureOutput();
|
|
418
|
+
try {
|
|
419
|
+
const exitCode = await cmdList(storage);
|
|
420
|
+
expect(exitCode).toBe(0);
|
|
421
|
+
expect(out.text()).toContain("alice@example.com");
|
|
422
|
+
} finally {
|
|
423
|
+
out.restore();
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Response mock chain (fetch interceptor):**
|
|
428
|
+
|
|
429
|
+
```javascript
|
|
430
|
+
// Token refresh response
|
|
431
|
+
mockFetch.mockResolvedValueOnce(new Response(JSON.stringify({ access_token: "new-access", expires_in: 3600 })));
|
|
432
|
+
// Actual API response
|
|
433
|
+
mockFetch.mockResolvedValueOnce(new Response(JSON.stringify({ content: [{ text: "Hello" }] }), { status: 200 }));
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Writing New Tests
|
|
437
|
+
|
|
438
|
+
**When adding a new module `lib/foo.mjs`:**
|
|
439
|
+
|
|
440
|
+
1. Create `lib/foo.test.mjs` next to the source file
|
|
441
|
+
2. Import test functions: `import { describe, it, expect, vi, beforeEach } from "vitest"`
|
|
442
|
+
3. Mock I/O dependencies with `vi.mock()` + `importOriginal` pattern
|
|
443
|
+
4. Define factory helpers at top of file for test data
|
|
444
|
+
5. Group tests with `describe` blocks per function/class
|
|
445
|
+
6. Add `beforeEach(() => { vi.resetAllMocks(); })` in each `describe`
|
|
446
|
+
7. Test pure logic directly, mock only I/O boundaries
|
|
447
|
+
|
|
448
|
+
**When adding tests to `index.test.mjs`:**
|
|
449
|
+
|
|
450
|
+
1. Use existing `makeClient()`, `makeProvider()`, `makeStoredAccount()` helpers
|
|
451
|
+
2. Set up mock responses with `mockFetch.mockResolvedValueOnce()`
|
|
452
|
+
3. Follow the existing `setupFetchFn()` pattern for fetch interceptor tests
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
_Testing analysis: 2026-02-07_
|
package/.prettierignore
ADDED