···6666 // if in watch mode, subsequent errors won't throw to gracefully handle
6767 // cases where server might be reloading
6868 if (error && !_watches) {
6969- throw new Error(`Request failed with status ${response.status}: ${response.statusText}`);
6969+ const text = await response.text().catch(() => '');
7070+ throw new Error(
7171+ `Request failed with status ${response.status}: ${text || response.statusText}`,
7272+ );
7073 }
71747275 return { arrayBuffer, resolvedInput };
+103
packages/shared/src/__tests__/getSpec.test.ts
···11+import * as refParser from '@hey-api/json-schema-ref-parser';
22+33+import { getSpec } from '../getSpec';
44+55+vi.mock('@hey-api/json-schema-ref-parser', () => ({
66+ getResolvedInput: vi.fn(({ pathOrUrlOrSchema }: { pathOrUrlOrSchema: string }) => ({
77+ path: pathOrUrlOrSchema,
88+ schema: undefined,
99+ type: 'url',
1010+ })),
1111+ sendRequest: vi.fn(),
1212+}));
1313+1414+const mockSendRequest = vi.mocked(refParser.sendRequest);
1515+1616+describe('getSpec', () => {
1717+ beforeEach(() => {
1818+ vi.clearAllMocks();
1919+ });
2020+2121+ describe('URL input', () => {
2222+ it('returns error with status 500 and error message when GET request throws an exception', async () => {
2323+ mockSendRequest.mockRejectedValueOnce(new Error('fetch failed'));
2424+2525+ const result = await getSpec({
2626+ fetchOptions: undefined,
2727+ inputPath: 'http://example.com/openapi.json',
2828+ timeout: undefined,
2929+ watch: { headers: new Headers() },
3030+ });
3131+3232+ expect(result.error).toBe('not-ok');
3333+ expect(result.response!.status).toBe(500);
3434+ expect(await result.response!.text()).toBe('fetch failed');
3535+ });
3636+3737+ it('returns error with status 500 and string message when non-Error is thrown during GET request', async () => {
3838+ mockSendRequest.mockRejectedValueOnce('network unavailable');
3939+4040+ const result = await getSpec({
4141+ fetchOptions: undefined,
4242+ inputPath: 'http://example.com/openapi.json',
4343+ timeout: undefined,
4444+ watch: { headers: new Headers() },
4545+ });
4646+4747+ expect(result.error).toBe('not-ok');
4848+ expect(result.response!.status).toBe(500);
4949+ expect(await result.response!.text()).toBe('network unavailable');
5050+ });
5151+5252+ it('returns error when GET response has status >= 300', async () => {
5353+ mockSendRequest.mockResolvedValueOnce({
5454+ response: new Response(null, { status: 404, statusText: 'Not Found' }),
5555+ });
5656+5757+ const result = await getSpec({
5858+ fetchOptions: undefined,
5959+ inputPath: 'http://example.com/openapi.json',
6060+ timeout: undefined,
6161+ watch: { headers: new Headers() },
6262+ });
6363+6464+ expect(result.error).toBe('not-ok');
6565+ expect(result.response!.status).toBe(404);
6666+ });
6767+6868+ it('returns error with status 500 and error message when HEAD request throws an exception', async () => {
6969+ mockSendRequest.mockRejectedValueOnce(new Error('connection refused'));
7070+7171+ const result = await getSpec({
7272+ fetchOptions: undefined,
7373+ inputPath: 'http://example.com/openapi.json',
7474+ timeout: undefined,
7575+ watch: { headers: new Headers(), isHeadMethodSupported: true, lastValue: 'previous' },
7676+ });
7777+7878+ expect(result.error).toBe('not-ok');
7979+ expect(result.response!.status).toBe(500);
8080+ expect(await result.response!.text()).toBe('connection refused');
8181+ });
8282+8383+ it('returns arrayBuffer on successful GET', async () => {
8484+ const content = '{"openapi":"3.0.0"}';
8585+ const encoder = new TextEncoder();
8686+ const buffer = encoder.encode(content).buffer as ArrayBuffer;
8787+8888+ mockSendRequest.mockResolvedValueOnce({
8989+ response: new Response(buffer, { status: 200 }),
9090+ });
9191+9292+ const result = await getSpec({
9393+ fetchOptions: undefined,
9494+ inputPath: 'http://example.com/openapi.json',
9595+ timeout: undefined,
9696+ watch: { headers: new Headers() },
9797+ });
9898+9999+ expect(result.error).toBeUndefined();
100100+ expect(result.arrayBuffer).toBeDefined();
101101+ });
102102+ });
103103+});