Skip to content

Commit 4a81363

Browse files
committed
Add comprehensive tests for HTTP utilities
- Test httpRequest for successful requests and errors - Test HTTP vs HTTPS URL handling - Test network error handling and timeouts - Test httpGetJson for JSON parsing and invalid JSON - Test httpGetText for text responses - Mock node:http and node:https modules for testing - Follow existing test patterns with vitest
1 parent 4f84ff4 commit 4a81363

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed

src/utils/http.test.mts

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2+
import type { IncomingMessage } from 'node:http'
3+
4+
// Mock node:http and node:https modules
5+
const mockRequest = vi.fn()
6+
const mockHttpsRequest = vi.fn()
7+
8+
vi.mock('node:http', () => ({
9+
default: {
10+
request: mockRequest,
11+
},
12+
}))
13+
14+
vi.mock('node:https', () => ({
15+
default: {
16+
request: mockHttpsRequest,
17+
},
18+
}))
19+
20+
import { httpGetJson, httpGetText, httpRequest } from './http.mts'
21+
22+
describe('HTTP utilities', () => {
23+
beforeEach(() => {
24+
vi.clearAllMocks()
25+
})
26+
27+
afterEach(() => {
28+
vi.restoreAllMocks()
29+
})
30+
31+
describe('httpRequest', () => {
32+
it('should make a successful HTTP request', async () => {
33+
const mockResponse = {
34+
statusCode: 200,
35+
statusMessage: 'OK',
36+
headers: { 'content-type': 'application/json' },
37+
on: vi.fn((event, callback) => {
38+
if (event === 'data') {
39+
callback(Buffer.from('{"test": "data"}'))
40+
} else if (event === 'end') {
41+
callback()
42+
}
43+
}),
44+
} as unknown as IncomingMessage
45+
46+
mockRequest.mockImplementation((options, callback) => {
47+
callback(mockResponse)
48+
return {
49+
on: vi.fn(),
50+
write: vi.fn(),
51+
end: vi.fn(),
52+
}
53+
})
54+
55+
const result = await httpRequest('http://example.com/api')
56+
57+
expect(result.ok).toBe(true)
58+
if (result.ok) {
59+
expect(result.data.status).toBe(200)
60+
expect(result.data.ok).toBe(true)
61+
expect(result.data.text()).toBe('{"test": "data"}')
62+
}
63+
})
64+
65+
it('should handle HTTP errors', async () => {
66+
const mockResponse = {
67+
statusCode: 404,
68+
statusMessage: 'Not Found',
69+
headers: {},
70+
on: vi.fn((event, callback) => {
71+
if (event === 'end') {
72+
callback()
73+
}
74+
}),
75+
} as unknown as IncomingMessage
76+
77+
mockRequest.mockImplementation((options, callback) => {
78+
callback(mockResponse)
79+
return {
80+
on: vi.fn(),
81+
write: vi.fn(),
82+
end: vi.fn(),
83+
}
84+
})
85+
86+
const result = await httpRequest('http://example.com/notfound')
87+
88+
expect(result.ok).toBe(true)
89+
if (result.ok) {
90+
expect(result.data.status).toBe(404)
91+
expect(result.data.ok).toBe(false)
92+
}
93+
})
94+
95+
it('should use HTTPS for https:// URLs', async () => {
96+
const mockResponse = {
97+
statusCode: 200,
98+
statusMessage: 'OK',
99+
headers: {},
100+
on: vi.fn((event, callback) => {
101+
if (event === 'end') {
102+
callback()
103+
}
104+
}),
105+
} as unknown as IncomingMessage
106+
107+
mockHttpsRequest.mockImplementation((options, callback) => {
108+
callback(mockResponse)
109+
return {
110+
on: vi.fn(),
111+
write: vi.fn(),
112+
end: vi.fn(),
113+
}
114+
})
115+
116+
await httpRequest('https://secure.example.com/api')
117+
118+
expect(mockHttpsRequest).toHaveBeenCalled()
119+
expect(mockRequest).not.toHaveBeenCalled()
120+
})
121+
122+
it('should handle network errors', async () => {
123+
mockRequest.mockImplementation(() => {
124+
return {
125+
on: vi.fn((event, callback) => {
126+
if (event === 'error') {
127+
callback(new Error('Network error'))
128+
}
129+
}),
130+
write: vi.fn(),
131+
end: vi.fn(),
132+
}
133+
})
134+
135+
const result = await httpRequest('http://example.com/error')
136+
137+
expect(result.ok).toBe(false)
138+
if (!result.ok) {
139+
expect(result.message).toContain('Network error')
140+
}
141+
})
142+
143+
it('should handle request timeout', async () => {
144+
mockRequest.mockImplementation(() => {
145+
return {
146+
on: vi.fn((event, callback) => {
147+
if (event === 'timeout') {
148+
callback()
149+
}
150+
}),
151+
write: vi.fn(),
152+
end: vi.fn(),
153+
destroy: vi.fn(),
154+
}
155+
})
156+
157+
const result = await httpRequest('http://example.com/slow', {
158+
timeout: 100,
159+
})
160+
161+
expect(result.ok).toBe(false)
162+
if (!result.ok) {
163+
expect(result.message).toContain('timeout')
164+
}
165+
})
166+
})
167+
168+
describe('httpGetJson', () => {
169+
it('should parse JSON response', async () => {
170+
const mockResponse = {
171+
statusCode: 200,
172+
statusMessage: 'OK',
173+
headers: {},
174+
on: vi.fn((event, callback) => {
175+
if (event === 'data') {
176+
callback(Buffer.from('{"name": "test", "value": 42}'))
177+
} else if (event === 'end') {
178+
callback()
179+
}
180+
}),
181+
} as unknown as IncomingMessage
182+
183+
mockRequest.mockImplementation((options, callback) => {
184+
callback(mockResponse)
185+
return {
186+
on: vi.fn(),
187+
write: vi.fn(),
188+
end: vi.fn(),
189+
}
190+
})
191+
192+
const result = await httpGetJson<{ name: string; value: number }>(
193+
'http://example.com/data.json',
194+
)
195+
196+
expect(result.ok).toBe(true)
197+
if (result.ok) {
198+
expect(result.data.name).toBe('test')
199+
expect(result.data.value).toBe(42)
200+
}
201+
})
202+
203+
it('should handle invalid JSON', async () => {
204+
const mockResponse = {
205+
statusCode: 200,
206+
statusMessage: 'OK',
207+
headers: {},
208+
on: vi.fn((event, callback) => {
209+
if (event === 'data') {
210+
callback(Buffer.from('not valid json'))
211+
} else if (event === 'end') {
212+
callback()
213+
}
214+
}),
215+
} as unknown as IncomingMessage
216+
217+
mockRequest.mockImplementation((options, callback) => {
218+
callback(mockResponse)
219+
return {
220+
on: vi.fn(),
221+
write: vi.fn(),
222+
end: vi.fn(),
223+
}
224+
})
225+
226+
const result = await httpGetJson('http://example.com/invalid')
227+
228+
expect(result.ok).toBe(false)
229+
if (!result.ok) {
230+
expect(result.message).toContain('JSON')
231+
}
232+
})
233+
})
234+
235+
describe('httpGetText', () => {
236+
it('should return text response', async () => {
237+
const mockResponse = {
238+
statusCode: 200,
239+
statusMessage: 'OK',
240+
headers: {},
241+
on: vi.fn((event, callback) => {
242+
if (event === 'data') {
243+
callback(Buffer.from('Hello, World!'))
244+
} else if (event === 'end') {
245+
callback()
246+
}
247+
}),
248+
} as unknown as IncomingMessage
249+
250+
mockRequest.mockImplementation((options, callback) => {
251+
callback(mockResponse)
252+
return {
253+
on: vi.fn(),
254+
write: vi.fn(),
255+
end: vi.fn(),
256+
}
257+
})
258+
259+
const result = await httpGetText('http://example.com/text')
260+
261+
expect(result.ok).toBe(true)
262+
if (result.ok) {
263+
expect(result.data).toBe('Hello, World!')
264+
}
265+
})
266+
})
267+
})

0 commit comments

Comments
 (0)