diff --git a/src/utils/http-helper.ts b/src/utils/http-helper.ts index 9ad0155..1b944b7 100644 --- a/src/utils/http-helper.ts +++ b/src/utils/http-helper.ts @@ -57,7 +57,12 @@ export const testUrls = async (urls?: string[]) => { if (!urls?.length) { return null; } - const ret = await promiseAny(urls.map(ping)); + let ret: string | null = null; + try { + ret = await promiseAny(urls.map(ping)); + } catch (_e) { + // fallback to urls[0] + } if (ret) { return ret; } diff --git a/tests/http-helper.test.ts b/tests/http-helper.test.ts index 6d1fe4f..cd0441d 100644 --- a/tests/http-helper.test.ts +++ b/tests/http-helper.test.ts @@ -1,4 +1,10 @@ -import { describe, expect, test } from 'bun:test'; +import { afterEach, describe, expect, mock, test } from 'bun:test'; + +const runtimeFetchMock = mock(() => Promise.resolve({ status: 200 })); +mock.module('../src/utils/runtime', () => ({ + runtimeFetch: runtimeFetchMock, +})); + import { promiseAny, testUrls } from '../src/utils/http-helper'; describe('promiseAny', () => { @@ -41,3 +47,52 @@ describe('testUrls', () => { expect(result).toBeNull(); }); }); + +describe('testUrls edge cases', () => { + afterEach(() => { + runtimeFetchMock.mockReset(); + }); + + test('Happy Path: returns successful URL', async () => { + runtimeFetchMock.mockImplementation((url: string) => { + if (url === 'http://success.local') { + return Promise.resolve({ status: 200 }); + } + return Promise.reject(new Error('fail')); + }); + + const result = await testUrls([ + 'http://fail.local', + 'http://success.local', + ]); + expect(result).toBe('http://success.local'); + }); + + test('Fastest Response: returns the URL that resolves first', async () => { + runtimeFetchMock.mockImplementation((url: string) => { + if (url === 'http://fast.local') { + return new Promise((resolve) => + setTimeout(() => resolve({ status: 200 }), 10), + ); + } + if (url === 'http://slow.local') { + return new Promise((resolve) => + setTimeout(() => resolve({ status: 200 }), 50), + ); + } + return Promise.reject(new Error('fail')); + }); + + const result = await testUrls(['http://slow.local', 'http://fast.local']); + expect(result).toBe('http://fast.local'); + }); + + test('All Failures (Fallback): returns urls[0] without throwing', async () => { + runtimeFetchMock.mockImplementation(() => { + return Promise.resolve({ status: 500 }); + }); + + const result = await testUrls(['http://fail1.local', 'http://fail2.local']); + expect(result).toBe('http://fail1.local'); + }); +});