From ab91340f5e4f1d2d804251707686da8686513e09 Mon Sep 17 00:00:00 2001 From: Raushankumar0720 Date: Thu, 1 Oct 2026 22:57:08 +0530 Subject: [PATCH 1/2] fix: add HTTP response status validation to fetch() calls Multiple fetch() calls across the codebase did not check response.ok before processing the response body. Since the JavaScript fetch() API does not throw on HTTP error statuses (e.g. 404, 500), the application silently loaded error pages as if they were valid AsyncAPI documents, causing confusing parser errors. This commit adds response.ok checks to all 4 affected fetch() calls: - editor.service.tsx: switchToFile() and importFromURL() - app.service.ts: fetchResource() - parser.service.ts: createRemoteTrackingResolver() Closes #1390 --- apps/studio/src/services/app.service.ts | 6 +- apps/studio/src/services/editor.service.tsx | 13 +++- apps/studio/src/services/parser.service.ts | 6 +- .../tests/editor-http-validation.test.ts | 63 +++++++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 apps/studio/src/services/tests/editor-http-validation.test.ts diff --git a/apps/studio/src/services/app.service.ts b/apps/studio/src/services/app.service.ts index ce3b8028e..77d71430e 100644 --- a/apps/studio/src/services/app.service.ts +++ b/apps/studio/src/services/app.service.ts @@ -107,7 +107,11 @@ export class ApplicationService extends AbstractService { let content = ''; if (url) { - content = await fetch(url).then((res) => res.text()); + const res = await fetch(url); + if (!res.ok) { + throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); + } + content = await res.text(); } else if (base64) { content = this.svcs.formatSvc.decodeBase64(base64); } else if (share) { diff --git a/apps/studio/src/services/editor.service.tsx b/apps/studio/src/services/editor.service.tsx index d41539f68..fcca6734f 100644 --- a/apps/studio/src/services/editor.service.tsx +++ b/apps/studio/src/services/editor.service.tsx @@ -145,7 +145,11 @@ export class EditorService extends AbstractService { content = await file.text(); } if (!content && (/^https?:\/\//).test(uri)) { - content = await fetch(uri).then((res) => res.text()); + const res = await fetch(uri); + if (!res.ok) { + throw new Error(`Failed to fetch ${uri}: ${res.status} ${res.statusText}`); + } + content = await res.text(); } if (typeof content !== 'string') { @@ -375,7 +379,12 @@ export class EditorService extends AbstractService { globalThis.history.pushState({}, '', `${currentUrl}?url=${url}`); return fetch(url) - .then(res => res.text()) + .then(res => { + if (!res.ok) { + throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); + } + return res.text(); + }) .then(async text => { const language = this.inferLanguageFromUri(url, text); const projectRoot = (() => { diff --git a/apps/studio/src/services/parser.service.ts b/apps/studio/src/services/parser.service.ts index 1f54d00e9..6c49825c1 100644 --- a/apps/studio/src/services/parser.service.ts +++ b/apps/studio/src/services/parser.service.ts @@ -259,7 +259,11 @@ export class ParserService extends AbstractService { }, async read(uri: any): Promise { const url = String(uri); - const content = await fetch(url).then((res) => res.text()); + const res = await fetch(url); + if (!res.ok) { + throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); + } + const content = await res.text(); filesState.getState().updateFile(url, { uri: url, name: url.split('/').pop() || url, diff --git a/apps/studio/src/services/tests/editor-http-validation.test.ts b/apps/studio/src/services/tests/editor-http-validation.test.ts new file mode 100644 index 000000000..cbb6a0eff --- /dev/null +++ b/apps/studio/src/services/tests/editor-http-validation.test.ts @@ -0,0 +1,63 @@ +import { createServices } from '../'; + +import type { EditorService } from '../editor.service'; + +// Mock global fetch +const originalFetch = globalThis.fetch; + +describe('EditorService - HTTP response validation', () => { + let editorSvc: EditorService; + + beforeAll(async () => { + const services = await createServices(); + editorSvc = services.editorSvc; + }); + + afterEach(() => { + globalThis.fetch = originalFetch; + }); + + describe('.importFromURL', () => { + test('should throw an error when fetch returns 404', async () => { + globalThis.fetch = jest.fn().mockResolvedValue({ + ok: false, + status: 404, + statusText: 'Not Found', + text: jest.fn().mockResolvedValue('Not Found'), + }); + + const url = 'https://example.com/nonexistent.yaml'; + await expect(editorSvc.importFromURL(url)).rejects.toThrow( + `Failed to fetch ${url}: 404 Not Found`, + ); + }); + + test('should throw an error when fetch returns 500', async () => { + globalThis.fetch = jest.fn().mockResolvedValue({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + text: jest.fn().mockResolvedValue('Server Error'), + }); + + const url = 'https://example.com/broken.yaml'; + await expect(editorSvc.importFromURL(url)).rejects.toThrow( + `Failed to fetch ${url}: 500 Internal Server Error`, + ); + }); + + test('should not throw when fetch returns 200', async () => { + const validContent = 'asyncapi: 2.6.0\ninfo:\n title: Test\n version: 1.0.0'; + globalThis.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + statusText: 'OK', + text: jest.fn().mockResolvedValue(validContent), + }); + + const url = 'https://example.com/valid.yaml'; + // Should not throw + await expect(editorSvc.importFromURL(url)).resolves.not.toThrow(); + }); + }); +}); From aa49fef8011c62f6f4c96ccae8b1461bb7a8b038 Mon Sep 17 00:00:00 2001 From: Raushankumar0720 Date: Thu, 1 Oct 2026 23:15:32 +0530 Subject: [PATCH 2/2] chore: add changeset for http response status validation fix --- .changeset/add-http-response-validation.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/add-http-response-validation.md diff --git a/.changeset/add-http-response-validation.md b/.changeset/add-http-response-validation.md new file mode 100644 index 000000000..64e7fc810 --- /dev/null +++ b/.changeset/add-http-response-validation.md @@ -0,0 +1,5 @@ +--- +"@asyncapi/studio": patch +--- + +fix: add HTTP response status validation to fetch() calls