0ac4beaaa8
- Add requireAuth() to admin-permissions.ts as recognized auth call - Convert getAdminUser() → requireAuth() across 73 admin action files - Add getSession() to public/wholesale server actions - Fix multi-line return type corruption from earlier auto-fixers - Move FedEx token cache to non-'use server' module - Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD, EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS - Update Stripe API version 2026-05-27 → 2026-06-24 - Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient - Fix 51 TypeScript errors (return type corruption, missing imports)
101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
/**
|
|
* 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");
|
|
});
|
|
});
|