diff --git a/src/lib/browser.test.ts b/src/lib/browser.test.ts index 3d6717b..a5cb0ee 100644 --- a/src/lib/browser.test.ts +++ b/src/lib/browser.test.ts @@ -54,9 +54,16 @@ describe('openInBrowser', () => { expect(exec).not.toHaveBeenCalled(); }); - it('throws on a malformed URL', () => { + it('refuses a malformed URL with exit 5 before spawning', () => { const exec = vi.fn(); - expect(() => openInBrowser('not a url', { exec })).toThrow(); + let error: unknown; + try { + openInBrowser('not a url', { exec }); + } catch (err) { + error = err; + } + expect(error).toBeInstanceOf(ApiError); + expect((error as ApiError).exitCode).toBe(5); expect(exec).not.toHaveBeenCalled(); }); diff --git a/src/lib/browser.ts b/src/lib/browser.ts index ccd2b8a..4d25823 100644 --- a/src/lib/browser.ts +++ b/src/lib/browser.ts @@ -23,7 +23,12 @@ export interface OpenInBrowserDeps { } export function openInBrowser(url: string, deps: OpenInBrowserDeps = {}): void { - const parsed = new URL(url); + let parsed: URL; + try { + parsed = new URL(url); + } catch { + throw localValidationError('url', 'must be a valid http(s) URL', undefined, 'field'); + } if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') { // User-input error, not an internal failure: classify as VALIDATION_ERROR // so it maps to exit 5 (like every other bad-argument path), not exit 1.