/** * Unit tests for `signInWithGoogleAction` in `src/actions/auth-actions.ts`. * * The action wraps Neon Auth's `signIn.social({ provider: "google" })` * and returns the redirect URL to the client. It must: * - Return `{ url, error: null }` when Neon Auth responds with a URL. * - Return `{ url: null, error }` on any auth error / network failure. * - Pass through `callbackURL` and `errorCallbackURL` to Neon Auth. */ import { describe, it, expect, vi, beforeEach } from "vitest"; const { mockSocial } = vi.hoisted(() => ({ mockSocial: vi.fn(), })); vi.mock("server-only", () => ({})); // `signIn` is a namespace object re-exported from @/lib/auth with // methods like `email`, `social`, etc. — mock the whole thing. // `getSession()` is called first inside `signInWithGoogleAction` // to short-circuit when the user is already signed in; default it // to a "no session" result so the action proceeds. vi.mock("@/lib/auth", () => ({ signIn: { social: mockSocial }, signOut: vi.fn(), getSession: vi.fn().mockResolvedValue({ data: null }), })); vi.mock("next/navigation", () => ({ redirect: vi.fn(), })); const { signInWithGoogleAction } = await import("@/actions/auth-actions"); beforeEach(() => { vi.clearAllMocks(); }); describe("signInWithGoogleAction", () => { it("returns the Neon Auth redirect URL on success", async () => { mockSocial.mockResolvedValue({ data: { url: "https://accounts.google.com/o/oauth2/v2/auth?..." }, error: null, }); const r = await signInWithGoogleAction({ callbackURL: "/admin" }); expect(r.error).toBeNull(); expect(r.url).toBe("https://accounts.google.com/o/oauth2/v2/auth?..."); expect(mockSocial).toHaveBeenCalledWith({ provider: "google", callbackURL: "/admin", errorCallbackURL: "/login?error=oauth", }); }); it("defaults callbackURL to /admin and errorCallbackURL to /login?error=oauth", async () => { mockSocial.mockResolvedValue({ data: { url: "https://x" }, error: null }); await signInWithGoogleAction({}); expect(mockSocial).toHaveBeenCalledWith({ provider: "google", callbackURL: "/admin", errorCallbackURL: "/login?error=oauth", }); }); it("passes through a custom errorCallbackURL", async () => { mockSocial.mockResolvedValue({ data: { url: "https://x" }, error: null }); await signInWithGoogleAction({ callbackURL: "/wholesale/portal", errorCallbackURL: "/wholesale/login?error=oauth", }); expect(mockSocial).toHaveBeenCalledWith({ provider: "google", callbackURL: "/wholesale/portal", errorCallbackURL: "/wholesale/login?error=oauth", }); }); it("returns a structured error when Neon Auth rejects the request", async () => { mockSocial.mockResolvedValue({ data: null, error: { code: "PROVIDER_NOT_CONFIGURED", message: "Google OAuth is not enabled" }, }); const r = await signInWithGoogleAction({}); expect(r.url).toBeNull(); expect(r.error).toBe("Google OAuth is not enabled"); }); it("returns a structured error when the response has no URL", async () => { mockSocial.mockResolvedValue({ data: {}, error: null }); const r = await signInWithGoogleAction({}); expect(r.url).toBeNull(); expect(r.error).toMatch(/no redirect URL/i); }); it("returns a structured error on unexpected exceptions", async () => { mockSocial.mockRejectedValue(new Error("network down")); const r = await signInWithGoogleAction({}); expect(r.url).toBeNull(); expect(r.error).toBe("network down"); }); });