Download src/agents/auth-profiles/oauth-refresh-error.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 5.78 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/auth-profiles/oauth-refresh-error.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/agents/auth-profiles/oauth-refresh-error.test.ts
-
curl -L -o oauth-refresh-error.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/auth-profiles/oauth-refresh-error.test.ts
5.78 kB
| /** | |
| * Tests refresh_token_reused error detection. | |
| * Protects the recovery path that adopts a winner's fresh token instead of | |
| * failing over after concurrent refresh races. | |
| */ | |
| import { expectDefined } from "@openclaw/normalization-core"; | |
| import { describe, expect, it } from "vitest"; | |
| import { | |
| makeSeededRandom, | |
| randomAsciiString as randomJunk, | |
| randomlyCased, | |
| } from "./oauth-test-utils.js"; | |
| import { isRefreshTokenReusedError } from "./oauth.test-support.js"; | |
| describe("isRefreshTokenReusedError", () => { | |
| describe("positive cases", () => { | |
| it("detects the canonical OAuth snake_case code", () => { | |
| expect(isRefreshTokenReusedError(new Error("refresh_token_reused"))).toBe(true); | |
| }); | |
| it("detects mixed-case variants", () => { | |
| expect(isRefreshTokenReusedError(new Error("REFRESH_TOKEN_REUSED"))).toBe(true); | |
| expect(isRefreshTokenReusedError(new Error("Refresh_Token_Reused"))).toBe(true); | |
| }); | |
| it("detects OpenAI-style natural-language variants", () => { | |
| expect( | |
| isRefreshTokenReusedError( | |
| new Error("Your refresh token has already been used to generate a new access token."), | |
| ), | |
| ).toBe(true); | |
| expect( | |
| isRefreshTokenReusedError( | |
| new Error("The refresh token has already been used to generate a new access token."), | |
| ), | |
| ).toBe(true); | |
| }); | |
| it("detects full JSON-wrapped 401 payloads", () => { | |
| expect( | |
| isRefreshTokenReusedError( | |
| new Error( | |
| '401 {"error":{"message":"Your refresh token has already been used to generate a new access token.","type":"invalid_request_error","code":"refresh_token_reused"}}', | |
| ), | |
| ), | |
| ).toBe(true); | |
| }); | |
| it("detects when message is a plain string (non-Error throw)", () => { | |
| expect(isRefreshTokenReusedError("refresh_token_reused")).toBe(true); | |
| }); | |
| it("detects when message is wrapped via Error.cause (single level)", () => { | |
| // formatErrorMessage traverses the .cause chain and concatenates | |
| // messages with " | ", so a marker hidden in the cause still counts. | |
| const inner = new Error("refresh_token_reused"); | |
| const outer = new Error("OAuth token refresh failed", { cause: inner }); | |
| expect(isRefreshTokenReusedError(outer)).toBe(true); | |
| }); | |
| it("detects when message is wrapped in a multi-level cause chain", () => { | |
| const root = new Error("already been used to generate a new access token"); | |
| const mid = new Error("plugin adapter failure", { cause: root }); | |
| const outer = new Error("OAuth token refresh failed", { cause: mid }); | |
| expect(isRefreshTokenReusedError(outer)).toBe(true); | |
| }); | |
| it("detects when cause is a bare string (no Error wrapper)", () => { | |
| const outer = new Error("upstream", { cause: "refresh_token_reused" }); | |
| expect(isRefreshTokenReusedError(outer)).toBe(true); | |
| }); | |
| it("still matches when the marker phrase is embedded in a longer message", () => { | |
| expect( | |
| isRefreshTokenReusedError( | |
| new Error("auth failed: already been used to generate a new access token (retry)"), | |
| ), | |
| ).toBe(true); | |
| }); | |
| }); | |
| describe("negative cases", () => { | |
| it("returns false for unrelated auth errors", () => { | |
| expect(isRefreshTokenReusedError(new Error("invalid_grant"))).toBe(false); | |
| expect(isRefreshTokenReusedError(new Error("HTTP 500 Internal Server Error"))).toBe(false); | |
| expect(isRefreshTokenReusedError(new Error("network timeout"))).toBe(false); | |
| expect(isRefreshTokenReusedError(new Error("expired or revoked"))).toBe(false); | |
| }); | |
| it("returns false for null/undefined/non-stringable values", () => { | |
| expect(isRefreshTokenReusedError(null)).toBe(false); | |
| expect(isRefreshTokenReusedError(undefined)).toBe(false); | |
| expect(isRefreshTokenReusedError(42)).toBe(false); | |
| expect(isRefreshTokenReusedError({})).toBe(false); | |
| }); | |
| it("returns false for an empty error message", () => { | |
| expect(isRefreshTokenReusedError(new Error(""))).toBe(false); | |
| }); | |
| }); | |
| describe("fuzz: random noisy messages", () => { | |
| it("always detects the marker when embedded at random positions with noise", () => { | |
| const rng = makeSeededRandom(0xabad1dea); | |
| const markers = [ | |
| "refresh_token_reused", | |
| "Your refresh token has already been used to generate a new access token", | |
| "already been used to generate a new access token", | |
| ]; | |
| for (let i = 0; i < 500; i += 1) { | |
| const marker = randomlyCased( | |
| expectDefined(markers[i % markers.length], "markers[i % markers.length] test invariant"), | |
| rng, | |
| ); | |
| const prefix = randomJunk(rng, 64); | |
| const suffix = randomJunk(rng, 64); | |
| const msg = `${prefix}${marker}${suffix}`; | |
| expect(isRefreshTokenReusedError(new Error(msg))).toBe(true); | |
| // Same for plain-string throws. | |
| expect(isRefreshTokenReusedError(msg)).toBe(true); | |
| } | |
| }); | |
| it("never yields a false positive on marker-free random messages", () => { | |
| const rng = makeSeededRandom(0x1337_beef); | |
| for (let i = 0; i < 500; i += 1) { | |
| // Bound length so we never randomly emit one of the marker substrings. | |
| const msg = randomJunk(rng, 32); | |
| if ( | |
| msg.toLowerCase().includes("refresh_token_reused") || | |
| msg.toLowerCase().includes("refresh token has already been used") || | |
| msg.toLowerCase().includes("already been used to generate a new access token") | |
| ) { | |
| // Extremely unlikely with 32-char random junk; skip if it happens. | |
| continue; | |
| } | |
| expect(isRefreshTokenReusedError(new Error(msg))).toBe(false); | |
| } | |
| }); | |
| }); | |
| }); | |