























11/** Tests Chutes OAuth token exchange and refresh HTTP flows. */
2-import { describe, expect, it } from "vitest";
2+import { describe, expect, it, vi } from "vitest";
33import { withFetchPreconnect } from "../test-utils/fetch-mock.js";
44import {
55CHUTES_TOKEN_ENDPOINT,
@@ -116,6 +116,45 @@ describe("chutes-oauth", () => {
116116).rejects.toThrow("Chutes token exchange returned invalid expires_in");
117117});
118118119+it("cancels failed userinfo response bodies during token exchange", async () => {
120+const userInfoResponse = new Response("temporarily unavailable", { status: 503 });
121+const cancel = vi.spyOn(userInfoResponse.body!, "cancel").mockResolvedValue(undefined);
122+const fetchFn = withFetchPreconnect(async (input: RequestInfo | URL) => {
123+const url = urlToString(input);
124+if (url === CHUTES_TOKEN_ENDPOINT) {
125+return new Response(
126+JSON.stringify({
127+access_token: "at_123",
128+refresh_token: "rt_123",
129+expires_in: 3600,
130+}),
131+{ status: 200, headers: { "Content-Type": "application/json" } },
132+);
133+}
134+if (url === CHUTES_USERINFO_ENDPOINT) {
135+return userInfoResponse;
136+}
137+return new Response("not found", { status: 404 });
138+});
139+140+const creds = await exchangeChutesCodeForTokens({
141+app: {
142+clientId: "cid_test",
143+redirectUri: "http://127.0.0.1:1456/oauth-callback",
144+scopes: ["openid"],
145+},
146+code: "code_123",
147+codeVerifier: "verifier_123",
148+ fetchFn,
149+now: 1_000_000,
150+});
151+152+expect(cancel).toHaveBeenCalledOnce();
153+expect(creds.access).toBe("at_123");
154+expect(creds.email).toBeUndefined();
155+expect((creds as unknown as { accountId?: string }).accountId).toBeUndefined();
156+});
157+119158it("refreshes tokens using stored client id and falls back to old refresh token", async () => {
120159const fetchFn = withFetchPreconnect(async (input: RequestInfo | URL, init?: RequestInit) => {
121160const url = urlToString(input);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。