import { describe, expect, it, vi } from "vitest"; import { buildAuthorizeUrl, exchangeCodeForUser, type FeishuOAuthConfig, } from "../../src/admin/auth/feishuOAuth.js"; describe("buildAuthorizeUrl", () => { it("includes client_id, redirect, state, and scope", () => { const url = buildAuthorizeUrl( { appId: "cli_test", appSecret: "secret", redirectUri: "http://localhost:8788/auth/feishu/callback", scope: "contact:user.base:readonly", }, "state-token", ); const parsed = new URL(url); expect(parsed.origin + parsed.pathname).toBe( "https://accounts.feishu.cn/open-apis/authen/v1/authorize", ); expect(parsed.searchParams.get("client_id")).toBe("cli_test"); expect(parsed.searchParams.get("response_type")).toBe("code"); expect(parsed.searchParams.get("redirect_uri")).toBe( "http://localhost:8788/auth/feishu/callback", ); expect(parsed.searchParams.get("state")).toBe("state-token"); expect(parsed.searchParams.get("scope")).toBe("contact:user.base:readonly"); }); }); describe("exchangeCodeForUser", () => { it("exchanges code then loads user_info", async () => { const fetchImpl = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); if (url.includes("/oauth/token")) { expect(init?.method).toBe("POST"); const body = JSON.parse(String(init?.body)) as { code: string }; expect(body.code).toBe("auth-code"); return new Response( JSON.stringify({ code: 0, access_token: "u-token", token_type: "Bearer", expires_in: 7200 }), { status: 200, headers: { "Content-Type": "application/json" } }, ); } if (url.includes("/user_info")) { expect((init?.headers as Record).Authorization).toBe("Bearer u-token"); return new Response( JSON.stringify({ code: 0, data: { open_id: "ou_alice", name: "Alice", avatar_url: "https://img/a.png" }, }), { status: 200, headers: { "Content-Type": "application/json" } }, ); } throw new Error(`unexpected url ${url}`); }); const config: FeishuOAuthConfig = { appId: "cli_test", appSecret: "secret", redirectUri: "http://localhost:8788/auth/feishu/callback", scope: "contact:user.base:readonly", fetchImpl: fetchImpl as unknown as typeof fetch, }; const user = await exchangeCodeForUser(config, "auth-code"); expect(user).toEqual({ openId: "ou_alice", displayName: "Alice", avatarUrl: "https://img/a.png", }); expect(fetchImpl).toHaveBeenCalledTimes(2); }); it("throws on token exchange failure", async () => { const fetchImpl = vi.fn(async () => new Response(JSON.stringify({ code: 20003, error: "invalid_grant", error_description: "bad code" }), { status: 200, headers: { "Content-Type": "application/json" }, }), ); await expect( exchangeCodeForUser( { appId: "cli", appSecret: "s", redirectUri: "http://localhost/cb", scope: "", fetchImpl: fetchImpl as unknown as typeof fetch, }, "bad", ), ).rejects.toThrow(/token exchange failed/); }); });