From e5e2ed790ed3615b0ee788696b52568a5f566064 Mon Sep 17 00:00:00 2001 From: Savio Dias Date: Mon, 17 Aug 2026 16:19:04 +0530 Subject: [PATCH] fix(testmanagement): include per-region failure detail in TM connection error The generic "check your credentials and network connection" was identical for auth failures, outages and network errors. Append each region's outcome: an HTTP status means the request completed (auth/permission), a Node error code means it did not (network, DNS, proxy, TLS). Message prefix unchanged; detail is additive. Co-Authored-By: Claude Opus 5 (1M context) --- src/lib/tm-base-url.ts | 10 ++++++++- tests/lib/tm-base-url.test.ts | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/lib/tm-base-url.ts b/src/lib/tm-base-url.ts index ad0c9f2a..e92165b9 100644 --- a/src/lib/tm-base-url.ts +++ b/src/lib/tm-base-url.ts @@ -32,6 +32,8 @@ export async function getTMBaseURL( const authHeader = "Basic " + Buffer.from(`${username}:${password}`).toString("base64"); + const failures: string[] = []; + for (const baseUrl of TM_BASE_URLS) { try { const res = await apiClient.get({ @@ -40,6 +42,10 @@ export async function getTMBaseURL( raise_error: false, }); + if (!res.ok) { + failures.push(`${baseUrl}: HTTP ${res.status}`); + } + if (res.ok) { // Only populate the cache in single-tenant (stdio) mode; in remote mode // the cache must stay empty so each user discovers their own region. @@ -50,11 +56,13 @@ export async function getTMBaseURL( return baseUrl; } } catch (err) { + const code = (err as { code?: string })?.code ?? (err as Error)?.message; + failures.push(`${baseUrl}: ${code}`); logger.debug(`Failed TM base URL: ${baseUrl} (${err})`); } } throw new Error( - "Unable to connect to BrowserStack Test Management. Please check your credentials and network connection.Please open an issue on GitHub if the problem persists", + `Unable to connect to BrowserStack Test Management. Please check your credentials and network connection.Please open an issue on GitHub if the problem persists. Details: ${failures.join("; ")}`, ); } diff --git a/tests/lib/tm-base-url.test.ts b/tests/lib/tm-base-url.test.ts index f4ba3940..eb8c1a2e 100644 --- a/tests/lib/tm-base-url.test.ts +++ b/tests/lib/tm-base-url.test.ts @@ -67,3 +67,43 @@ describe("getTMBaseURL — multi-tenant cache discipline", () => { expect(apiClient.get).toHaveBeenCalledTimes(3); }); }); + +describe("getTMBaseURL — failure details", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("reports the HTTP status when requests complete (auth/permission failure)", async () => { + const { apiClient, getTMBaseURL } = await loadModule(false); + (apiClient.get as any).mockResolvedValue({ ok: false, status: 401 }); + + const err = (await getTMBaseURL(mockConfig).catch( + (e) => e as Error, + )) as unknown as Error; + expect(err.message).toMatch(/HTTP 401/); + }); + + it("reports the Node error code when requests never complete (network failure)", async () => { + const { apiClient, getTMBaseURL } = await loadModule(false); + (apiClient.get as any).mockRejectedValue( + Object.assign(new Error("self signed cert"), { + code: "UNABLE_TO_VERIFY_LEAF_SIGNATURE", + }), + ); + + const err = (await getTMBaseURL(mockConfig).catch( + (e) => e as Error, + )) as unknown as Error; + expect(err.message).toMatch(/UNABLE_TO_VERIFY_LEAF_SIGNATURE/); + }); + + it("passes through any other status verbatim (not just auth codes)", async () => { + const { apiClient, getTMBaseURL } = await loadModule(false); + (apiClient.get as any).mockResolvedValue({ ok: false, status: 503 }); + + const err = (await getTMBaseURL(mockConfig).catch( + (e) => e as Error, + )) as unknown as Error; + expect(err.message).toMatch(/HTTP 503/); + }); +});