Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/lib/tm-base-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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.
Expand All @@ -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("; ")}`,
);
}
40 changes: 40 additions & 0 deletions tests/lib/tm-base-url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});
});
Loading