



























@@ -3,6 +3,28 @@ import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
33import { afterEach, describe, expect, it, vi } from "vitest";
44import { loginMiniMaxPortalOAuth, normalizeOAuthExpires } from "./oauth.js";
556+function cancelTrackedResponse(
7+text: string,
8+init: ResponseInit,
9+): {
10+response: Response;
11+wasCanceled: () => boolean;
12+} {
13+let canceled = false;
14+const stream = new ReadableStream<Uint8Array>({
15+start(controller) {
16+controller.enqueue(new TextEncoder().encode(text));
17+},
18+cancel() {
19+canceled = true;
20+},
21+});
22+return {
23+response: new Response(stream, init),
24+wasCanceled: () => canceled,
25+};
26+}
27+628afterEach(() => {
729vi.useRealTimers();
830vi.restoreAllMocks();
@@ -30,6 +52,76 @@ describe("normalizeOAuthExpires", () => {
3052});
31533254describe("loginMiniMaxPortalOAuth", () => {
55+it("bounds authorization error bodies without using response.text()", async () => {
56+const tracked = cancelTrackedResponse(
57+`${"minimax authorization unavailable ".repeat(1024)}tail`,
58+{
59+status: 503,
60+headers: { "Content-Type": "text/plain" },
61+},
62+);
63+const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
64+vi.stubGlobal(
65+"fetch",
66+vi.fn(async () => tracked.response),
67+);
68+69+const error = await loginMiniMaxPortalOAuth({
70+openUrl: vi.fn(async () => undefined),
71+note: vi.fn(async () => undefined),
72+progress: { update: vi.fn(), stop: vi.fn() },
73+}).catch((cause: unknown) => cause);
74+75+expect(error).toBeInstanceOf(Error);
76+expect((error as Error).message).toMatch(
77+/MiniMax OAuth authorization failed: minimax authorization unavailable/,
78+);
79+expect((error as Error).message).not.toContain("tail");
80+expect(tracked.wasCanceled()).toBe(true);
81+expect(textSpy).not.toHaveBeenCalled();
82+});
83+84+it("bounds token error bodies without using response.text()", async () => {
85+const tracked = cancelTrackedResponse(`${"minimax token unavailable ".repeat(1024)}tail`, {
86+status: 503,
87+headers: { "Content-Type": "text/plain" },
88+});
89+const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
90+let callCount = 0;
91+const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
92+callCount += 1;
93+const body =
94+init?.body instanceof URLSearchParams
95+ ? init.body
96+ : new URLSearchParams(typeof init?.body === "string" ? init.body : "");
97+if (callCount === 1) {
98+return new Response(
99+JSON.stringify({
100+user_code: "CODE",
101+verification_uri: "https://example.com/device",
102+expired_in: Date.now() + 10_000,
103+state: body.get("state"),
104+}),
105+{ status: 200, headers: { "Content-Type": "application/json" } },
106+);
107+}
108+return tracked.response;
109+});
110+vi.stubGlobal("fetch", fetchMock);
111+112+const error = await loginMiniMaxPortalOAuth({
113+openUrl: vi.fn(async () => undefined),
114+note: vi.fn(async () => undefined),
115+progress: { update: vi.fn(), stop: vi.fn() },
116+}).catch((cause: unknown) => cause);
117+118+expect(error).toBeInstanceOf(Error);
119+expect((error as Error).message).toContain("minimax token unavailable");
120+expect((error as Error).message).not.toContain("tail");
121+expect(tracked.wasCanceled()).toBe(true);
122+expect(textSpy).not.toHaveBeenCalled();
123+});
124+33125it("uses MiniMax account OAuth endpoints directly for global and CN login", async () => {
34126for (const [region, expectedHosts] of [
35127[
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。