Skip to content

Commit df15452

Browse files
committed
fix(tests): add forEach to createMockHeaders and createMockErrorResponse helper
1 parent a771f03 commit df15452

6 files changed

Lines changed: 4237 additions & 55 deletions

File tree

1_Test (Node 20.x).txt

Lines changed: 1345 additions & 0 deletions
Large diffs are not rendered by default.

3_Build.txt

Lines changed: 953 additions & 0 deletions
Large diffs are not rendered by default.

4_Test (Node 18.x).txt

Lines changed: 1349 additions & 0 deletions
Large diffs are not rendered by default.

5_OpenAPI Validation.txt

Lines changed: 541 additions & 0 deletions
Large diffs are not rendered by default.

src/core/http/client.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
ConnectionError,
1212
TimeoutError,
1313
RateLimitError,
14-
type NfeError
14+
NfeError
1515
} from '../errors/index.js';
1616

1717
// Simple type declarations for runtime APIs
@@ -125,6 +125,11 @@ export class HttpClient {
125125
} catch (error) {
126126
clearTimeout(timeoutId);
127127

128+
// Re-throw NfeError instances (from handleErrorResponse)
129+
if (error instanceof NfeError) {
130+
throw error;
131+
}
132+
128133
if (error instanceof Error) {
129134
if (error.name === 'AbortError') {
130135
throw new TimeoutError(`Request timeout after ${this.config.timeout}ms`, error);

tests/unit/http-client.test.ts

Lines changed: 43 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,28 @@ import { TEST_API_KEY } from '../setup';
55

66
// Helper to create mock Headers object
77
function createMockHeaders(entries: [string, string][]): any {
8-
const map = new Map(entries);
8+
const map = new Map(entries.map(([k, v]) => [k.toLowerCase(), v]));
99
return {
1010
get: (key: string) => map.get(key.toLowerCase()) || null,
1111
has: (key: string) => map.has(key.toLowerCase()),
1212
entries: () => map.entries(),
1313
keys: () => map.keys(),
1414
values: () => map.values(),
15+
forEach: (callback: (value: string, key: string) => void) => {
16+
map.forEach((value, key) => callback(value, key));
17+
},
18+
};
19+
}
20+
21+
// Helper to create mock error Response
22+
function createMockErrorResponse(status: number, statusText: string, errorData: any): any {
23+
return {
24+
ok: false,
25+
status,
26+
statusText,
27+
headers: createMockHeaders([['content-type', 'application/json']]),
28+
json: async () => errorData,
29+
text: async () => JSON.stringify(errorData),
1530
};
1631
}
1732

@@ -198,13 +213,9 @@ describe('HttpClient', () => {
198213
});
199214

200215
it('should throw AuthenticationError on 401', async () => {
201-
fetchMock.mockResolvedValue({
202-
ok: false,
203-
status: 401,
204-
statusText: 'Unauthorized',
205-
headers: createMockHeaders([['content-type', 'application/json']]),
206-
json: async () => ({ error: 'Invalid API key' }),
207-
});
216+
fetchMock.mockResolvedValue(
217+
createMockErrorResponse(401, 'Unauthorized', { error: 'Invalid API key' })
218+
);
208219

209220
// 401 errors should not retry
210221
await expect(httpClient.get('/test')).rejects.toMatchObject({
@@ -219,16 +230,12 @@ describe('HttpClient', () => {
219230

220231
describe('Error Handling', () => {
221232
it('should throw ValidationError on 400', async () => {
222-
fetchMock.mockResolvedValue({
223-
ok: false,
224-
status: 400,
225-
statusText: 'Bad Request',
226-
headers: createMockHeaders([['content-type', 'application/json']]),
227-
json: async () => ({
233+
fetchMock.mockResolvedValue(
234+
createMockErrorResponse(400, 'Bad Request', {
228235
error: 'Validation failed',
229236
details: { field: 'required' },
230-
}),
231-
});
237+
})
238+
);
232239

233240
// 400 errors should not retry
234241
await expect(httpClient.get('/test')).rejects.toMatchObject({
@@ -240,13 +247,9 @@ describe('HttpClient', () => {
240247
});
241248

242249
it('should throw NotFoundError on 404', async () => {
243-
fetchMock.mockResolvedValue({
244-
ok: false,
245-
status: 404,
246-
statusText: 'Not Found',
247-
headers: createMockHeaders([['content-type', 'application/json']]),
248-
json: async () => ({ error: 'Resource not found' }),
249-
});
250+
fetchMock.mockResolvedValue(
251+
createMockErrorResponse(404, 'Not Found', { error: 'Resource not found' })
252+
);
250253

251254
// 404 errors should not retry
252255
await expect(httpClient.get('/test')).rejects.toMatchObject({
@@ -259,16 +262,14 @@ describe('HttpClient', () => {
259262

260263
it('should throw RateLimitError on 429 after retries', async () => {
261264
// Always return 429
262-
fetchMock.mockResolvedValue({
263-
ok: false,
264-
status: 429,
265-
statusText: 'Too Many Requests',
266-
headers: new Map([
267-
['content-type', 'application/json'],
268-
['retry-after', '60'],
269-
]),
270-
json: async () => ({ error: 'Rate limit exceeded' }),
271-
});
265+
const errorResponse = createMockErrorResponse(429, 'Too Many Requests', { error: 'Rate limit exceeded' });
266+
// Add retry-after header
267+
errorResponse.headers.get = (key: string) => {
268+
if (key.toLowerCase() === 'retry-after') return '60';
269+
if (key.toLowerCase() === 'content-type') return 'application/json';
270+
return null;
271+
};
272+
fetchMock.mockResolvedValue(errorResponse);
272273

273274
const promise = httpClient.get('/test');
274275

@@ -284,13 +285,9 @@ describe('HttpClient', () => {
284285

285286
it('should throw ServerError on 500 after retries', async () => {
286287
// Always return 500
287-
fetchMock.mockResolvedValue({
288-
ok: false,
289-
status: 500,
290-
statusText: 'Internal Server Error',
291-
headers: createMockHeaders([['content-type', 'application/json']]),
292-
json: async () => ({ error: 'Server error' }),
293-
});
288+
fetchMock.mockResolvedValue(
289+
createMockErrorResponse(500, 'Internal Server Error', { error: 'Server error' })
290+
);
294291

295292
const promise = httpClient.get('/test');
296293

@@ -386,13 +383,9 @@ describe('HttpClient', () => {
386383
});
387384

388385
it('should not retry on 400 Bad Request', async () => {
389-
fetchMock.mockResolvedValue({
390-
ok: false,
391-
status: 400,
392-
statusText: 'Bad Request',
393-
headers: createMockHeaders([['content-type', 'application/json']]),
394-
json: async () => ({ error: 'Invalid input' }),
395-
});
386+
fetchMock.mockResolvedValue(
387+
createMockErrorResponse(400, 'Bad Request', { error: 'Invalid input' })
388+
);
396389

397390
const promise = httpClient.get('/test');
398391

@@ -401,13 +394,9 @@ describe('HttpClient', () => {
401394
});
402395

403396
it('should respect maxRetries limit', async () => {
404-
fetchMock.mockResolvedValue({
405-
ok: false,
406-
status: 503,
407-
statusText: 'Service Unavailable',
408-
headers: createMockHeaders([['content-type', 'application/json']]),
409-
json: async () => ({ error: 'Unavailable' }),
410-
});
397+
fetchMock.mockResolvedValue(
398+
createMockErrorResponse(503, 'Service Unavailable', { error: 'Unavailable' })
399+
);
411400

412401
const promise = httpClient.get('/test');
413402

0 commit comments

Comments
 (0)