new_pull
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRadiusOAuth } from "../src/auth/oauth/radius.ts";
|
||||
import type { AuthEvent, AuthInteraction } from "../src/auth/types.ts";
|
||||
|
||||
const GATEWAY = "https://radius.example";
|
||||
|
||||
function jsonResponse(body: unknown, status = 200): Response {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
function requestUrl(input: unknown): string {
|
||||
if (typeof input === "string") return input;
|
||||
if (input instanceof URL) return input.toString();
|
||||
if (input instanceof Request) return input.url;
|
||||
throw new Error(`Unsupported request input: ${String(input)}`);
|
||||
}
|
||||
|
||||
function interaction(loginMethod: "browser" | "device-code", events: AuthEvent[] = []): AuthInteraction {
|
||||
return {
|
||||
prompt: async () => loginMethod,
|
||||
notify: (event) => events.push(event),
|
||||
};
|
||||
}
|
||||
|
||||
describe("Radius OAuth", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("uses gateway endpoints directly for device login", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-07-24T00:00:00Z"));
|
||||
const events: AuthEvent[] = [];
|
||||
const urls: string[] = [];
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async (input: unknown, init?: RequestInit) => {
|
||||
const url = requestUrl(input);
|
||||
urls.push(url);
|
||||
const form = new URLSearchParams(String(init?.body));
|
||||
if (url === `${GATEWAY}/v1/oauth/device`) {
|
||||
expect(form.get("client_id")).toBe("pi-gateway");
|
||||
expect(form.get("scope")).toBe("gateway offline_access");
|
||||
return jsonResponse({
|
||||
device_code: "device-code",
|
||||
user_code: "ABCD-1234",
|
||||
verification_uri: "https://radius-ui.example/pair",
|
||||
expires_in: 600,
|
||||
interval: 5,
|
||||
});
|
||||
}
|
||||
if (url === `${GATEWAY}/v1/oauth/token`) {
|
||||
expect(form.get("grant_type")).toBe("urn:ietf:params:oauth:grant-type:device_code");
|
||||
expect(form.get("client_id")).toBe("pi-gateway");
|
||||
expect(form.get("device_code")).toBe("device-code");
|
||||
return jsonResponse({
|
||||
access_token: "access-token",
|
||||
refresh_token: "refresh-token",
|
||||
expires_in: 3600,
|
||||
scope: "gateway offline_access",
|
||||
});
|
||||
}
|
||||
throw new Error(`Unexpected request: ${url}`);
|
||||
}),
|
||||
);
|
||||
|
||||
const oauth = createRadiusOAuth({ name: "Radius", gateway: GATEWAY });
|
||||
await expect(oauth.login(interaction("device-code", events))).resolves.toEqual({
|
||||
type: "oauth",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 3600 * 1000 - 60_000,
|
||||
scope: "gateway offline_access",
|
||||
});
|
||||
expect(events).toEqual([
|
||||
{
|
||||
type: "device_code",
|
||||
userCode: "ABCD-1234",
|
||||
verificationUri: "https://radius-ui.example/pair",
|
||||
intervalSeconds: 5,
|
||||
expiresInSeconds: 600,
|
||||
},
|
||||
]);
|
||||
expect(urls).toEqual([`${GATEWAY}/v1/oauth/device`, `${GATEWAY}/v1/oauth/token`]);
|
||||
});
|
||||
|
||||
it("refreshes directly through the gateway without discovery", async () => {
|
||||
const fetchMock = vi.fn(async (input: unknown, init?: RequestInit) => {
|
||||
expect(requestUrl(input)).toBe(`${GATEWAY}/v1/oauth/token`);
|
||||
const form = new URLSearchParams(String(init?.body));
|
||||
expect(form.get("grant_type")).toBe("refresh_token");
|
||||
expect(form.get("client_id")).toBe("pi-gateway");
|
||||
expect(form.get("refresh_token")).toBe("old-refresh");
|
||||
return jsonResponse({
|
||||
access_token: "new-access",
|
||||
refresh_token: "new-refresh",
|
||||
expires_in: 3600,
|
||||
});
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const oauth = createRadiusOAuth({ name: "Radius", gateway: GATEWAY });
|
||||
await expect(
|
||||
oauth.refresh({ type: "oauth", access: "old-access", refresh: "old-refresh", expires: 0 }),
|
||||
).resolves.toMatchObject({ access: "new-access", refresh: "new-refresh" });
|
||||
expect(fetchMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("discovers only the interactive browser authorization endpoint", async () => {
|
||||
const fetchMock = vi.fn(async (input: unknown) => {
|
||||
expect(requestUrl(input)).toBe(`${GATEWAY}/v1/oauth`);
|
||||
return jsonResponse({ issuer: "https://radius-ui.example" });
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const oauth = createRadiusOAuth({ name: "Radius", gateway: GATEWAY });
|
||||
await expect(oauth.login(interaction("browser"))).rejects.toThrow(`Invalid Radius OAuth config from ${GATEWAY}`);
|
||||
expect(fetchMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user