From cdeedd80934051390fa848858d035cf31cbefbf3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 04:16:12 +0100 Subject: [PATCH] test(chutes): require redirect URL in manual oauth --- src/commands/auth-choice.e2e.test.ts | 28 +++++++++++++++++++-------- src/commands/chutes-oauth.e2e.test.ts | 16 +++++++++++---- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/commands/auth-choice.e2e.test.ts b/src/commands/auth-choice.e2e.test.ts index ede6ce190d..c58494792b 100644 --- a/src/commands/auth-choice.e2e.test.ts +++ b/src/commands/auth-choice.e2e.test.ts @@ -1094,7 +1094,26 @@ describe("applyAuthChoice", () => { }); vi.stubGlobal("fetch", fetchSpy); - const text = vi.fn().mockResolvedValue("code_manual"); + const runtime: RuntimeEnv = { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn((code: number) => { + throw new Error(`exit:${code}`); + }), + }; + const text: WizardPrompter["text"] = vi.fn(async (params) => { + if (params.message === "Paste the redirect URL") { + const lastLog = runtime.log.mock.calls.at(-1)?.[0]; + const urlLine = typeof lastLog === "string" ? lastLog : String(lastLog ?? ""); + const urlMatch = urlLine.match(/https?:\/\/\S+/)?.[0] ?? ""; + const state = urlMatch ? new URL(urlMatch).searchParams.get("state") : null; + if (!state) { + throw new Error("missing state in oauth URL"); + } + return `?code=code_manual&state=${state}`; + } + return "code_manual"; + }); const select: WizardPrompter["select"] = vi.fn( async (params) => params.options[0]?.value as never, ); @@ -1109,13 +1128,6 @@ describe("applyAuthChoice", () => { confirm: vi.fn(async () => false), progress: vi.fn(() => ({ update: noop, stop: noop })), }; - const runtime: RuntimeEnv = { - log: vi.fn(), - error: vi.fn(), - exit: vi.fn((code: number) => { - throw new Error(`exit:${code}`); - }), - }; const result = await applyAuthChoice({ authChoice: "chutes", diff --git a/src/commands/chutes-oauth.e2e.test.ts b/src/commands/chutes-oauth.e2e.test.ts index f958d1acc6..c7d68ef389 100644 --- a/src/commands/chutes-oauth.e2e.test.ts +++ b/src/commands/chutes-oauth.e2e.test.ts @@ -73,7 +73,7 @@ describe("loginChutes", () => { expect(creds.email).toBe("local-user"); }); - it("supports manual flow with pasted code", async () => { + it("supports manual flow with pasted redirect URL", async () => { const fetchFn: typeof fetch = async (input) => { const url = urlToString(input); if (url === CHUTES_TOKEN_ENDPOINT) { @@ -95,6 +95,7 @@ describe("loginChutes", () => { return new Response("not found", { status: 404 }); }; + let capturedState: string | null = null; const creds = await loginChutes({ app: { clientId: "cid_test", @@ -102,8 +103,15 @@ describe("loginChutes", () => { scopes: ["openid"], }, manual: true, - onAuth: async () => {}, - onPrompt: async () => "code_manual", + onAuth: async ({ url }) => { + capturedState = new URL(url).searchParams.get("state"); + }, + onPrompt: async () => { + if (!capturedState) { + throw new Error("missing state"); + } + return `?code=code_manual&state=${capturedState}`; + }, fetchFn, }); @@ -154,7 +162,7 @@ describe("loginChutes", () => { expect(parsed.searchParams.get("state")).toBe("state_456"); expect(parsed.searchParams.get("state")).not.toBe("verifier_123"); }, - onPrompt: async () => "code_manual", + onPrompt: async () => "?code=code_manual&state=state_456", fetchFn, });