Skip to content

Commit 9a17185

Browse files
committed
refactor(tests): replace mockModule with dependency injection
- Add typed mock helpers in common/src/testing/fixtures/: - billing.ts: createGrantCreditsDbMock, createOrgBillingDbMock, etc. - fetch.ts: createMockFetch, wrapMockAsFetch, etc. - database.ts: createVersionQueryDbMock, createExistsQueryDbMock - agent-runtime.ts: mockAnalytics, mockBigQuery, mockRandomUUID - index.ts: barrel exports - Refactor billing package to use DI: - Add optional conn parameter to grant-credits, org-billing, credit-delegation - Add injectable function parameters to usage-service - Update all 4 test files to use typed mock helpers - Refactor agent-runtime tests to use shared mock helpers: - Replace inline spyOn calls with mockAnalytics/mockBigQuery/mockRandomUUID - Update 8 test files - Refactor SDK code-search to use DI: - Export SpawnFn type and codeSearchWithSpawn function - Update tests to use typed mock spawn - Refactor CLI tests to use wrapMockAsFetch: - Update codebuff-api, use-usage-query, and integration tests - Add SDK build step to init-worktree.ts for fresh worktree setup - Add barrel export for testing/fixtures in common/package.json Eliminates ~30 mockModule calls and ~50 "as unknown as" type casts. All 100+ affected tests pass.
1 parent 350f79a commit 9a17185

33 files changed

+1519
-952
lines changed

cli/src/__tests__/integration/api-integration.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { wrapMockAsFetch } from '@codebuff/common/testing/fixtures'
12
import {
23
AuthenticationError,
34
NetworkError,
@@ -44,7 +45,7 @@ describe('API Integration', () => {
4445
impl: Parameters<typeof mock>[0],
4546
): ReturnType<typeof mock> => {
4647
const fetchMock = mock(impl)
47-
globalThis.fetch = fetchMock as unknown as typeof fetch
48+
globalThis.fetch = wrapMockAsFetch(fetchMock)
4849
return fetchMock
4950
}
5051

cli/src/__tests__/integration/usage-refresh-on-completion.test.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { wrapMockAsFetch } from '@codebuff/common/testing/fixtures'
12
import { QueryClient } from '@tanstack/react-query'
23
import { describe, test, expect, beforeEach, afterEach, mock, spyOn } from 'bun:test'
34

@@ -42,17 +43,19 @@ describe('Usage Refresh on SDK Completion', () => {
4243
getAuthTokenSpy = spyOn(authModule, 'getAuthToken').mockReturnValue('test-token')
4344

4445
// Mock successful API response
45-
globalThis.fetch = mock(async () =>
46-
new Response(
47-
JSON.stringify({
48-
type: 'usage-response',
49-
usage: 100,
50-
remainingBalance: 850,
51-
next_quota_reset: '2024-03-01T00:00:00.000Z',
52-
}),
53-
{ status: 200, headers: { 'Content-Type': 'application/json' } },
46+
globalThis.fetch = wrapMockAsFetch(
47+
mock(async () =>
48+
new Response(
49+
JSON.stringify({
50+
type: 'usage-response',
51+
usage: 100,
52+
remainingBalance: 850,
53+
next_quota_reset: '2024-03-01T00:00:00.000Z',
54+
}),
55+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
56+
),
5457
),
55-
) as unknown as typeof fetch
58+
)
5659
})
5760

5861
afterEach(() => {

cli/src/hooks/__tests__/use-usage-query.test.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { wrapMockAsFetch } from '@codebuff/common/testing/fixtures'
12
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
23
import { renderHook, waitFor } from '@testing-library/react'
34
import {
@@ -44,21 +45,23 @@ describe('fetchUsageData', () => {
4445
next_quota_reset: '2024-02-01T00:00:00.000Z',
4546
}
4647

47-
globalThis.fetch = mock(
48-
async () =>
49-
new Response(JSON.stringify(mockResponse), {
50-
status: 200,
51-
headers: { 'Content-Type': 'application/json' },
52-
}),
53-
) as unknown as typeof fetch
48+
globalThis.fetch = wrapMockAsFetch(
49+
mock(
50+
async () =>
51+
new Response(JSON.stringify(mockResponse), {
52+
status: 200,
53+
headers: { 'Content-Type': 'application/json' },
54+
}),
55+
),
56+
)
5457

5558
const result = await fetchUsageData({ authToken: 'test-token' })
5659

5760
expect(result).toEqual(mockResponse)
5861
})
5962

6063
test('should throw error on failed request', async () => {
61-
globalThis.fetch = mock(async () => new Response('Error', { status: 500 })) as unknown as typeof fetch
64+
globalThis.fetch = wrapMockAsFetch(mock(async () => new Response('Error', { status: 500 })))
6265
const mockLogger = { error: mock(() => {}), warn: mock(() => {}), info: mock(() => {}), debug: mock(() => {}) }
6366

6467
await expect(fetchUsageData({ authToken: 'test-token', logger: mockLogger as any })).rejects.toThrow(
@@ -118,13 +121,15 @@ describe('useUsageQuery', () => {
118121
next_quota_reset: '2024-02-01T00:00:00.000Z',
119122
}
120123

121-
globalThis.fetch = mock(
122-
async () =>
123-
new Response(JSON.stringify(mockResponse), {
124-
status: 200,
125-
headers: { 'Content-Type': 'application/json' },
126-
}),
127-
) as unknown as typeof fetch
124+
globalThis.fetch = wrapMockAsFetch(
125+
mock(
126+
async () =>
127+
new Response(JSON.stringify(mockResponse), {
128+
status: 200,
129+
headers: { 'Content-Type': 'application/json' },
130+
}),
131+
),
132+
)
128133

129134
const { result } = renderHook(() => useUsageQuery(), {
130135
wrapper: createWrapper(),
@@ -139,8 +144,8 @@ describe('useUsageQuery', () => {
139144
getAuthTokenSpy = spyOn(authModule, 'getAuthToken').mockReturnValue(
140145
'test-token',
141146
)
142-
const fetchMock = mock(async () => new Response('{}')) as unknown as typeof fetch
143-
globalThis.fetch = fetchMock
147+
const fetchMock = mock(async () => new Response('{}'))
148+
globalThis.fetch = wrapMockAsFetch(fetchMock)
144149

145150
const { result } = renderHook(() => useUsageQuery({ enabled: false }), {
146151
wrapper: createWrapper(),
@@ -156,8 +161,8 @@ describe('useUsageQuery', () => {
156161
getAuthTokenSpy = spyOn(authModule, 'getAuthToken').mockReturnValue(
157162
undefined,
158163
)
159-
const fetchMock = mock(async () => new Response('{}')) as unknown as typeof fetch
160-
globalThis.fetch = fetchMock
164+
const fetchMock = mock(async () => new Response('{}'))
165+
globalThis.fetch = wrapMockAsFetch(fetchMock)
161166

162167
renderHook(() => useUsageQuery(), {
163168
wrapper: createWrapper(),

cli/src/utils/__tests__/codebuff-api.test.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { wrapMockAsFetch } from '@codebuff/common/testing/fixtures'
12
import { describe, test, expect, mock, beforeEach } from 'bun:test'
23

34
import { createCodebuffApiClient } from '../codebuff-api'
@@ -39,7 +40,7 @@ describe('createCodebuffApiClient', () => {
3940
test('should make GET request with correct URL', async () => {
4041
const client = createCodebuffApiClient({
4142
baseUrl: 'https://test.api',
42-
fetch: mockFetch as unknown as typeof fetch,
43+
fetch: wrapMockAsFetch(mockFetch),
4344
})
4445

4546
await client.get('/api/v1/test', { retry: false })
@@ -52,7 +53,7 @@ describe('createCodebuffApiClient', () => {
5253
test('should add query parameters', async () => {
5354
const client = createCodebuffApiClient({
5455
baseUrl: 'https://test.api',
55-
fetch: mockFetch as unknown as typeof fetch,
56+
fetch: wrapMockAsFetch(mockFetch),
5657
})
5758

5859
await client.get('/api/v1/me', {
@@ -68,7 +69,7 @@ describe('createCodebuffApiClient', () => {
6869
const client = createCodebuffApiClient({
6970
baseUrl: 'https://test.api',
7071
authToken: 'my-token',
71-
fetch: mockFetch as unknown as typeof fetch,
72+
fetch: wrapMockAsFetch(mockFetch),
7273
})
7374

7475
await client.get('/api/v1/test', { retry: false })
@@ -86,7 +87,7 @@ describe('createCodebuffApiClient', () => {
8687
const client = createCodebuffApiClient({
8788
baseUrl: 'https://test.api',
8889
authToken: 'my-token',
89-
fetch: mockFetch as unknown as typeof fetch,
90+
fetch: wrapMockAsFetch(mockFetch),
9091
})
9192

9293
await client.get('/api/v1/test', { includeAuth: false, retry: false })
@@ -103,7 +104,7 @@ describe('createCodebuffApiClient', () => {
103104
test('should make POST request with JSON body', async () => {
104105
const client = createCodebuffApiClient({
105106
baseUrl: 'https://test.api',
106-
fetch: mockFetch as unknown as typeof fetch,
107+
fetch: wrapMockAsFetch(mockFetch),
107108
})
108109

109110
await client.post('/api/v1/test', { key: 'value' }, { retry: false })
@@ -123,7 +124,7 @@ describe('createCodebuffApiClient', () => {
123124
const client = createCodebuffApiClient({
124125
baseUrl: 'https://test.api',
125126
authToken: 'my-token',
126-
fetch: mockFetch as unknown as typeof fetch,
127+
fetch: wrapMockAsFetch(mockFetch),
127128
})
128129

129130
await client.post(
@@ -147,7 +148,7 @@ describe('createCodebuffApiClient', () => {
147148
test('should make PUT request with JSON body', async () => {
148149
const client = createCodebuffApiClient({
149150
baseUrl: 'https://test.api',
150-
fetch: mockFetch as unknown as typeof fetch,
151+
fetch: wrapMockAsFetch(mockFetch),
151152
})
152153

153154
await client.put('/api/v1/test', { key: 'value' }, { retry: false })
@@ -167,7 +168,7 @@ describe('createCodebuffApiClient', () => {
167168
test('should make PATCH request with JSON body', async () => {
168169
const client = createCodebuffApiClient({
169170
baseUrl: 'https://test.api',
170-
fetch: mockFetch as unknown as typeof fetch,
171+
fetch: wrapMockAsFetch(mockFetch),
171172
})
172173

173174
await client.patch('/api/v1/test', { key: 'value' }, { retry: false })
@@ -184,7 +185,7 @@ describe('createCodebuffApiClient', () => {
184185
test('should make DELETE request without body', async () => {
185186
const client = createCodebuffApiClient({
186187
baseUrl: 'https://test.api',
187-
fetch: mockFetch as unknown as typeof fetch,
188+
fetch: wrapMockAsFetch(mockFetch),
188189
})
189190

190191
await client.delete('/api/v1/test/123', { retry: false })
@@ -212,7 +213,7 @@ describe('createCodebuffApiClient', () => {
212213

213214
const client = createCodebuffApiClient({
214215
baseUrl: 'https://test.api',
215-
fetch: mockSuccessFetch as unknown as typeof fetch,
216+
fetch: wrapMockAsFetch(mockSuccessFetch),
216217
})
217218

218219
const result = await client.get('/api/v1/me', { retry: false })
@@ -236,7 +237,7 @@ describe('createCodebuffApiClient', () => {
236237

237238
const client = createCodebuffApiClient({
238239
baseUrl: 'https://test.api',
239-
fetch: mockErrorFetch as unknown as typeof fetch,
240+
fetch: wrapMockAsFetch(mockErrorFetch),
240241
})
241242

242243
const result = await client.get('/api/v1/me', { retry: false })
@@ -249,19 +250,21 @@ describe('createCodebuffApiClient', () => {
249250
})
250251

251252
test('should handle non-JSON error responses', async () => {
253+
// This partial Response mock is acceptable - it tests a specific error path
254+
// where json() rejects and we fall back to text()
252255
const mockErrorFetch = mock<MockFetch>(() =>
253256
Promise.resolve({
254257
ok: false,
255258
status: 500,
256259
statusText: 'Internal Server Error',
257260
json: () => Promise.reject(new Error('Not JSON')),
258261
text: () => Promise.resolve('Server error occurred'),
259-
} as unknown as Response),
262+
} as Response),
260263
)
261264

262265
const client = createCodebuffApiClient({
263266
baseUrl: 'https://test.api',
264-
fetch: mockErrorFetch as unknown as typeof fetch,
267+
fetch: wrapMockAsFetch(mockErrorFetch),
265268
})
266269

267270
const result = await client.get('/api/v1/test', { retry: false })
@@ -284,7 +287,7 @@ describe('createCodebuffApiClient', () => {
284287

285288
const client = createCodebuffApiClient({
286289
baseUrl: 'https://test.api',
287-
fetch: mockNoContentFetch as unknown as typeof fetch,
290+
fetch: wrapMockAsFetch(mockNoContentFetch),
288291
})
289292

290293
const result = await client.delete('/api/v1/test/123', { retry: false })
@@ -316,7 +319,7 @@ describe('createCodebuffApiClient', () => {
316319

317320
const client = createCodebuffApiClient({
318321
baseUrl: 'https://test.api',
319-
fetch: mockRetryFetch as unknown as typeof fetch,
322+
fetch: wrapMockAsFetch(mockRetryFetch),
320323
retry: {
321324
maxRetries: 3,
322325
initialDelayMs: 10, // Fast for testing
@@ -342,7 +345,7 @@ describe('createCodebuffApiClient', () => {
342345

343346
const client = createCodebuffApiClient({
344347
baseUrl: 'https://test.api',
345-
fetch: mockBadRequestFetch as unknown as typeof fetch,
348+
fetch: wrapMockAsFetch(mockBadRequestFetch),
346349
retry: { maxRetries: 3, initialDelayMs: 10 },
347350
})
348351

@@ -365,7 +368,7 @@ describe('createCodebuffApiClient', () => {
365368

366369
const client = createCodebuffApiClient({
367370
baseUrl: 'https://test.api',
368-
fetch: mockServerErrorFetch as unknown as typeof fetch,
371+
fetch: wrapMockAsFetch(mockServerErrorFetch),
369372
retry: { maxRetries: 3 },
370373
})
371374

@@ -391,7 +394,7 @@ describe('createCodebuffApiClient', () => {
391394

392395
const client = createCodebuffApiClient({
393396
baseUrl: 'https://test.api',
394-
fetch: mockNetworkErrorFetch as unknown as typeof fetch,
397+
fetch: wrapMockAsFetch(mockNetworkErrorFetch),
395398
retry: { maxRetries: 3, initialDelayMs: 10 },
396399
})
397400

@@ -419,7 +422,7 @@ describe('createCodebuffApiClient', () => {
419422

420423
const client = createCodebuffApiClient({
421424
baseUrl: 'https://test.api',
422-
fetch: mockFetchWithSignal as unknown as typeof fetch,
425+
fetch: wrapMockAsFetch(mockFetchWithSignal),
423426
defaultTimeoutMs: 5000,
424427
})
425428

@@ -438,7 +441,7 @@ describe('createCodebuffApiClient', () => {
438441

439442
const client = createCodebuffApiClient({
440443
baseUrl: 'https://test.api',
441-
fetch: mockAbortFetch as unknown as typeof fetch,
444+
fetch: wrapMockAsFetch(mockAbortFetch),
442445
})
443446

444447
// Should retry on abort errors
@@ -453,7 +456,7 @@ describe('createCodebuffApiClient', () => {
453456
const client = createCodebuffApiClient({
454457
baseUrl: 'https://test.api',
455458
authToken: 'my-token',
456-
fetch: mockFetch as unknown as typeof fetch,
459+
fetch: wrapMockAsFetch(mockFetch),
457460
})
458461

459462
await client.get('/api/v1/test', {

common/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
"import": "./src/*.ts",
1111
"types": "./src/*.ts",
1212
"default": "./src/*.ts"
13+
},
14+
"./testing/fixtures": {
15+
"bun": "./src/testing/fixtures/index.ts",
16+
"import": "./src/testing/fixtures/index.ts",
17+
"types": "./src/testing/fixtures/index.ts",
18+
"default": "./src/testing/fixtures/index.ts"
1319
}
1420
},
1521
"scripts": {

0 commit comments

Comments
 (0)