import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test" import { buildAuthURL, exchangeCode } from "./oauth" import { ANTIGRAVITY_CLIENT_ID, GOOGLE_TOKEN_URL } from "./constants" describe("OAuth PKCE Removal", () => { describe("buildAuthURL", () => { it("should NOT include code_challenge parameter", async () => { // #given const projectId = "test-project" // #when const result = await buildAuthURL(projectId) const url = new URL(result.url) // #then expect(url.searchParams.has("code_challenge")).toBe(false) }) it("should NOT include code_challenge_method parameter", async () => { // #given const projectId = "test-project" // #when const result = await buildAuthURL(projectId) const url = new URL(result.url) // #then expect(url.searchParams.has("code_challenge_method")).toBe(false) }) it("should include state parameter for CSRF protection", async () => { // #given const projectId = "test-project" // #when const result = await buildAuthURL(projectId) const url = new URL(result.url) const state = url.searchParams.get("state") // #then expect(state).toBeTruthy() }) it("should have state as simple random string (not JSON/base64)", async () => { // #given const projectId = "test-project" // #when const result = await buildAuthURL(projectId) const url = new URL(result.url) const state = url.searchParams.get("state")! // #then - positive assertions for simple random string expect(state.length).toBeGreaterThanOrEqual(16) expect(state.length).toBeLessThanOrEqual(64) // Should be URL-safe (alphanumeric, no special chars like { } " :) expect(state).toMatch(/^[a-zA-Z0-9_-]+$/) // Should NOT contain JSON indicators expect(state).not.toContain("{") expect(state).not.toContain("}") expect(state).not.toContain('"') }) it("should include access_type=offline", async () => { // #given const projectId = "test-project" // #when const result = await buildAuthURL(projectId) const url = new URL(result.url) // #then expect(url.searchParams.get("access_type")).toBe("offline") }) it("should include prompt=consent", async () => { // #given const projectId = "test-project" // #when const result = await buildAuthURL(projectId) const url = new URL(result.url) // #then expect(url.searchParams.get("prompt")).toBe("consent") }) it("should NOT return verifier property (PKCE removed)", async () => { // #given const projectId = "test-project" // #when const result = await buildAuthURL(projectId) // #then expect(result).not.toHaveProperty("verifier") expect(result).toHaveProperty("url") expect(result).toHaveProperty("state") }) it("should return state that matches URL state param", async () => { // #given const projectId = "test-project" // #when const result = await buildAuthURL(projectId) const url = new URL(result.url) // #then expect(result.state).toBe(url.searchParams.get("state")!) }) }) describe("exchangeCode", () => { let originalFetch: typeof fetch beforeEach(() => { originalFetch = globalThis.fetch }) afterEach(() => { globalThis.fetch = originalFetch }) it("should NOT send code_verifier in token exchange", async () => { // #given let capturedBody: string | null = null globalThis.fetch = mock(async (url: string, init?: RequestInit) => { if (url === GOOGLE_TOKEN_URL) { capturedBody = init?.body as string return new Response(JSON.stringify({ access_token: "test-access", refresh_token: "test-refresh", expires_in: 3600, token_type: "Bearer" })) } return new Response("", { status: 404 }) }) as unknown as typeof fetch // #when await exchangeCode("test-code", "http://localhost:51121/oauth-callback") // #then expect(capturedBody).toBeTruthy() const params = new URLSearchParams(capturedBody!) expect(params.has("code_verifier")).toBe(false) }) it("should send required OAuth parameters", async () => { // #given let capturedBody: string | null = null globalThis.fetch = mock(async (url: string, init?: RequestInit) => { if (url === GOOGLE_TOKEN_URL) { capturedBody = init?.body as string return new Response(JSON.stringify({ access_token: "test-access", refresh_token: "test-refresh", expires_in: 3600, token_type: "Bearer" })) } return new Response("", { status: 404 }) }) as unknown as typeof fetch // #when await exchangeCode("test-code", "http://localhost:51121/oauth-callback") // #then const params = new URLSearchParams(capturedBody!) expect(params.get("grant_type")).toBe("authorization_code") expect(params.get("code")).toBe("test-code") expect(params.get("client_id")).toBe(ANTIGRAVITY_CLIENT_ID) expect(params.get("redirect_uri")).toBe("http://localhost:51121/oauth-callback") }) }) describe("State/CSRF Validation", () => { it("should generate unique state for each call", async () => { // #given const projectId = "test-project" // #when const result1 = await buildAuthURL(projectId) const result2 = await buildAuthURL(projectId) // #then expect(result1.state).not.toBe(result2.state) }) }) })