diff --git a/packages/openapi-ts/src/createClient.ts b/packages/openapi-ts/src/createClient.ts index 7a948e2db..55d5b886e 100644 --- a/packages/openapi-ts/src/createClient.ts +++ b/packages/openapi-ts/src/createClient.ts @@ -66,7 +66,10 @@ export async function createClient({ // if in watch mode, subsequent errors won't throw to gracefully handle // cases where server might be reloading if (error && !_watches) { - throw new Error(`Request failed with status ${response.status}: ${response.statusText}`); + const text = await response.text().catch(() => ''); + throw new Error( + `Request failed with status ${response.status}: ${text || response.statusText}`, + ); } return { arrayBuffer, resolvedInput }; diff --git a/packages/shared/src/getSpec.ts b/packages/shared/src/getSpec.ts index cfee1542a..c9c251d9c 100644 --- a/packages/shared/src/getSpec.ts +++ b/packages/shared/src/getSpec.ts @@ -105,9 +105,10 @@ export async function getSpec({ response = request.response; } catch (error) { + const message = error instanceof Error ? error.message : String(error); return { error: 'not-ok', - response: new Response(error instanceof Error ? error.message : String(error)), + response: new Response(message, { status: 500 }), }; } @@ -181,9 +182,10 @@ export async function getSpec({ response = request.response; } catch (error) { + const message = error instanceof Error ? error.message : String(error); return { error: 'not-ok', - response: new Response(error instanceof Error ? error.message : String(error)), + response: new Response(message, { status: 500 }), }; } -- 2.51.2 From 1f927bb40d28404dfd779781845a4baee14b3597 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:30:49 +0000 Subject: [PATCH 3/4] fix: add getSpec tests and apply same fix to openapi-python Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com> --- packages/openapi-python/src/createClient.ts | 5 +- packages/shared/src/__tests__/getSpec.test.ts | 103 ++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 packages/shared/src/__tests__/getSpec.test.ts diff --git a/packages/openapi-python/src/createClient.ts b/packages/openapi-python/src/createClient.ts index 363204308..0e4600a6f 100644 --- a/packages/openapi-python/src/createClient.ts +++ b/packages/openapi-python/src/createClient.ts @@ -66,7 +66,10 @@ export async function createClient({ // if in watch mode, subsequent errors won't throw to gracefully handle // cases where server might be reloading if (error && !_watches) { - throw new Error(`Request failed with status ${response.status}: ${response.statusText}`); + const text = await response.text().catch(() => ''); + throw new Error( + `Request failed with status ${response.status}: ${text || response.statusText}`, + ); } return { arrayBuffer, resolvedInput }; diff --git a/packages/shared/src/__tests__/getSpec.test.ts b/packages/shared/src/__tests__/getSpec.test.ts new file mode 100644 index 000000000..8cb46e53e --- /dev/null +++ b/packages/shared/src/__tests__/getSpec.test.ts @@ -0,0 +1,103 @@ +import * as refParser from '@hey-api/json-schema-ref-parser'; + +import { getSpec } from '../getSpec'; + +vi.mock('@hey-api/json-schema-ref-parser', () => ({ + getResolvedInput: vi.fn(({ pathOrUrlOrSchema }: { pathOrUrlOrSchema: string }) => ({ + path: pathOrUrlOrSchema, + schema: undefined, + type: 'url', + })), + sendRequest: vi.fn(), +})); + +const mockSendRequest = vi.mocked(refParser.sendRequest); + +describe('getSpec', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('URL input', () => { + it('returns error with status 500 and error message when GET request throws an exception', async () => { + mockSendRequest.mockRejectedValueOnce(new Error('fetch failed')); + + const result = await getSpec({ + fetchOptions: undefined, + inputPath: 'http://example.com/openapi.json', + timeout: undefined, + watch: { headers: new Headers() }, + }); + + expect(result.error).toBe('not-ok'); + expect(result.response!.status).toBe(500); + expect(await result.response!.text()).toBe('fetch failed'); + }); + + it('returns error with status 500 and string message when non-Error is thrown during GET request', async () => { + mockSendRequest.mockRejectedValueOnce('network unavailable'); + + const result = await getSpec({ + fetchOptions: undefined, + inputPath: 'http://example.com/openapi.json', + timeout: undefined, + watch: { headers: new Headers() }, + }); + + expect(result.error).toBe('not-ok'); + expect(result.response!.status).toBe(500); + expect(await result.response!.text()).toBe('network unavailable'); + }); + + it('returns error when GET response has status >= 300', async () => { + mockSendRequest.mockResolvedValueOnce({ + response: new Response(null, { status: 404, statusText: 'Not Found' }), + }); + + const result = await getSpec({ + fetchOptions: undefined, + inputPath: 'http://example.com/openapi.json', + timeout: undefined, + watch: { headers: new Headers() }, + }); + + expect(result.error).toBe('not-ok'); + expect(result.response!.status).toBe(404); + }); + + it('returns error with status 500 and error message when HEAD request throws an exception', async () => { + mockSendRequest.mockRejectedValueOnce(new Error('connection refused')); + + const result = await getSpec({ + fetchOptions: undefined, + inputPath: 'http://example.com/openapi.json', + timeout: undefined, + watch: { headers: new Headers(), isHeadMethodSupported: true, lastValue: 'previous' }, + }); + + expect(result.error).toBe('not-ok'); + expect(result.response!.status).toBe(500); + expect(await result.response!.text()).toBe('connection refused'); + }); + + it('returns arrayBuffer on successful GET', async () => { + const content = '{"openapi":"3.0.0"}'; + const encoder = new TextEncoder(); + const buffer = encoder.encode(content).buffer as ArrayBuffer; + + mockSendRequest.mockResolvedValueOnce({ + response: new Response(buffer, { status: 200 }), + }); + + const result = await getSpec({ + fetchOptions: undefined, + inputPath: 'http://example.com/openapi.json', + timeout: undefined, + watch: { headers: new Headers() }, + }); + + expect(result.error).toBeUndefined(); + expect(result.arrayBuffer).toBeDefined(); + }); + }); +}); -- 2.51.2 From 37dd92c6629511860ae434c64ac8af9154ed6aed Mon Sep 17 00:00:00 2001 From: Lubos Date: Fri, 20 Feb 2026 19:05:22 +0200 Subject: [PATCH 4/4] Improve error handling for spec fetch failures --- .changeset/fluffy-turtles-explain.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/fluffy-turtles-explain.md diff --git a/.changeset/fluffy-turtles-explain.md b/.changeset/fluffy-turtles-explain.md new file mode 100644 index 000000000..14111ce6c --- /dev/null +++ b/.changeset/fluffy-turtles-explain.md @@ -0,0 +1,6 @@ +--- +"@hey-api/openapi-ts": patch +"@hey-api/shared": patch +--- + +**input**: fix: improve returned status code when spec fetch fails