Files
openclaw-zero-token/plugin-sdk/fetch-auth.test.ts
sjhu 571e14a236 feat: upgrade to upstream v2026.3.28
Major upgrade from e26988a38 to upstream v2026.3.28 (f9b107928).
Key changes:
- Upstream src/, ui/, extensions/ (89 bundled extensions)
- Zero-token web providers preserved in src/zero-token/
- AskOnce plugin restored and registered as CLI command
- Added missing packages: @anthropic-ai/vertex-sdk, @modelcontextprotocol/sdk
- Fixed tsconfig rootDir, skipLibCheck for plugin-sdk DTS build
- Added askonce to bundled plugin metadata and package.json exports
- Fixed AskOnce CLI command registration (missing commands metadata)
- Restored AskOnce adapter imports (correct 5-level relative paths)
- Removed stale migration artifacts from root directory
2026-03-30 17:58:12 +08:00

118 lines
3.9 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { fetchWithBearerAuthScopeFallback } from "./fetch-auth.js";
const asFetch = (fn: unknown): typeof fetch => fn as typeof fetch;
describe("fetchWithBearerAuthScopeFallback", () => {
it("rejects non-https urls when https is required", async () => {
await expect(
fetchWithBearerAuthScopeFallback({
url: "http://example.com/file",
scopes: [],
requireHttps: true,
}),
).rejects.toThrow("URL must use HTTPS");
});
it.each([
{
name: "returns immediately when the first attempt succeeds",
url: "https://example.com/file",
scopes: ["https://graph.microsoft.com"],
responses: [new Response("ok", { status: 200 })],
shouldAttachAuth: undefined,
expectedStatus: 200,
expectedFetchCalls: 1,
expectedTokenCalls: [] as string[],
expectedAuthHeader: null,
},
{
name: "retries with auth scopes after a 401 response",
url: "https://graph.microsoft.com/v1.0/me",
scopes: ["https://graph.microsoft.com", "https://api.botframework.com"],
responses: [
new Response("unauthorized", { status: 401 }),
new Response("ok", { status: 200 }),
],
shouldAttachAuth: undefined,
expectedStatus: 200,
expectedFetchCalls: 2,
expectedTokenCalls: ["https://graph.microsoft.com"],
expectedAuthHeader: "Bearer token-1",
},
{
name: "does not attach auth when host predicate rejects url",
url: "https://example.com/file",
scopes: ["https://graph.microsoft.com"],
responses: [new Response("unauthorized", { status: 401 })],
shouldAttachAuth: () => false,
expectedStatus: 401,
expectedFetchCalls: 1,
expectedTokenCalls: [] as string[],
expectedAuthHeader: null,
},
])(
"$name",
async ({
url,
scopes,
responses,
shouldAttachAuth,
expectedStatus,
expectedFetchCalls,
expectedTokenCalls,
expectedAuthHeader,
}) => {
const fetchFn = vi.fn();
for (const response of responses) {
fetchFn.mockResolvedValueOnce(response);
}
const tokenProvider = { getAccessToken: vi.fn(async () => "token-1") };
const response = await fetchWithBearerAuthScopeFallback({
url,
scopes,
fetchFn: asFetch(fetchFn),
tokenProvider,
shouldAttachAuth,
});
expect(response.status).toBe(expectedStatus);
expect(fetchFn).toHaveBeenCalledTimes(expectedFetchCalls);
const tokenCalls = tokenProvider.getAccessToken.mock.calls as unknown as Array<[string]>;
expect(tokenCalls.map(([scope]) => scope)).toEqual(expectedTokenCalls);
if (expectedAuthHeader === null) {
return;
}
const secondCallInit = fetchFn.mock.calls.at(1)?.[1] as RequestInit | undefined;
const secondHeaders = new Headers(secondCallInit?.headers);
expect(secondHeaders.get("authorization")).toBe(expectedAuthHeader);
},
);
it("continues across scopes when token retrieval fails", async () => {
const fetchFn = vi
.fn()
.mockResolvedValueOnce(new Response("unauthorized", { status: 401 }))
.mockResolvedValueOnce(new Response("ok", { status: 200 }));
const tokenProvider = {
getAccessToken: vi
.fn()
.mockRejectedValueOnce(new Error("first scope failed"))
.mockResolvedValueOnce("token-2"),
};
const response = await fetchWithBearerAuthScopeFallback({
url: "https://graph.microsoft.com/v1.0/me",
scopes: ["https://first.example", "https://second.example"],
fetchFn: asFetch(fetchFn),
tokenProvider,
});
expect(response.status).toBe(200);
expect(tokenProvider.getAccessToken).toHaveBeenCalledTimes(2);
expect(tokenProvider.getAccessToken).toHaveBeenNthCalledWith(1, "https://first.example");
expect(tokenProvider.getAccessToken).toHaveBeenNthCalledWith(2, "https://second.example");
});
});