fix: исправить edge cases HttpClient

- ошибки парсинга успешных ответов проброшены в onError
- добавлена защита от перезаписи явного Authorization
- обновлены тесты, README и примеры кастомизации
This commit is contained in:
2026-07-01 00:13:18 +03:00
parent 886e20ef88
commit 4ce5ea9b65
6 changed files with 135 additions and 19 deletions

View File

@@ -168,7 +168,9 @@ bun test -t "should generate client with custom name"
- ✅ POST с body
- ✅ Обработка 404 статуса
- ✅ Bearer authentication через `onRequest`
- ✅ Сохранение явно переданного `Authorization`
- ✅ Retry через `onError` и `ApiError`
- ✅ Ошибки парсинга успешного ответа в `onError`
- ✅ Timeout через `AbortSignal`

View File

@@ -382,6 +382,61 @@ describe('E2E Generation', () => {
expect(profile.email).toBe('test@example.com');
}, 60000);
test('onRequest не должен перетирать явно переданный Authorization', async () => {
const outputPath = join(tempDir, 'output');
await execa('bun', [
'run',
CLI_PATH,
'--input',
FIXTURES.MINIMAL,
'--output',
outputPath,
'--mode',
'split',
]);
const generatedFile = join(outputPath, 'index.ts');
const { HttpClient } = await import(generatedFile);
let authorizationHeader: string | null = null;
const http = new HttpClient({
customFetch: async (_url, init) => {
authorizationHeader = new Headers(init?.headers).get('Authorization');
return Response.json({ ok: true });
},
onRequest: (params) => {
if (!params.secure) {
return params;
}
const headers = new Headers(params.headers);
if (!headers.has('Authorization')) {
headers.set('Authorization', 'Bearer default-token');
}
return {
...params,
headers,
};
},
});
const result = await http.request({
path: '/auth-header',
method: 'GET',
secure: true,
format: 'json',
headers: {
Authorization: 'Bearer explicit-token',
},
});
expect(result).toEqual({ ok: true });
expect(authorizationHeader).toBe('Bearer explicit-token');
}, 60000);
test('onError должен поддерживать retry после ApiError', async () => {
const outputPath = join(tempDir, 'output');
@@ -429,6 +484,47 @@ describe('E2E Generation', () => {
expect(result).toEqual({ ok: true });
}, 60000);
test('onError должен получать ошибки парсинга успешного ответа', async () => {
const outputPath = join(tempDir, 'output');
await execa('bun', [
'run',
CLI_PATH,
'--input',
FIXTURES.MINIMAL,
'--output',
outputPath,
'--mode',
'split',
]);
const generatedFile = join(outputPath, 'index.ts');
const { HttpClient } = await import(generatedFile);
let handledError: unknown;
const http = new HttpClient({
customFetch: async () => new Response('{ invalid json', {
status: 200,
headers: {
'Content-Type': 'application/json',
},
}),
onError: (error) => {
handledError = error;
return { parsed: false };
},
});
const result = await http.request({
path: '/broken-json',
method: 'GET',
format: 'json',
});
expect(result).toEqual({ parsed: false });
expect(handledError).toBeInstanceOf(Error);
}, 60000);
test('timeout должен отменять зависший запрос', async () => {
const outputPath = join(tempDir, 'output');