diff --git a/src/services/auth.test.ts b/src/services/auth.test.ts new file mode 100644 index 0000000..99e0b4d --- /dev/null +++ b/src/services/auth.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, test } from "bun:test"; + +import { startAuthFlow } from "./auth.js"; + +describe("startAuthFlow", () => { + test("keeps waiting for the callback when the browser cannot be opened", async () => { + let authUrl = ""; + const flow = startAuthFlow(5_000, async (url) => { + authUrl = url.toString(); + throw new Error("spawn xdg-open ENOENT"); + }); + + await new Promise((resolve) => setTimeout(resolve, 50)); + const callback = new URL(new URL(authUrl).searchParams.get("callback")!); + callback.searchParams.set("state", "wrong"); + const response = await fetch(callback); + + expect(response.status).toBe(403); + expect(await flow).toEqual({ success: false, error: "Invalid auth state" }); + }); +}); diff --git a/src/services/auth.ts b/src/services/auth.ts index bacfac8..636bf30 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -65,7 +65,7 @@ export interface AuthResult { error?: string; } -export function startAuthFlow(timeoutMs = AUTH_TIMEOUT): Promise { +export function startAuthFlow(timeoutMs = AUTH_TIMEOUT, open = openUrl): Promise { return new Promise((resolve) => { let resolved = false; const stateToken = randomBytes(16).toString("hex"); @@ -168,12 +168,9 @@ export function startAuthFlow(timeoutMs = AUTH_TIMEOUT): Promise { console.log("Opening browser for authentication..."); console.log(`If it doesn't open, visit: ${authUrl}`); - openUrl(authUrl).catch((error) => { + open(authUrl).catch((error) => { if (!resolved) { - resolved = true; - clearTimeout(timer); - server.close(); - resolve({ success: false, error: `Failed to open browser: ${error.message}` }); + console.log(`Could not open a browser (${error.message}). Open the URL above to continue.`); } }); });