import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import type { PrismaClient } from "@prisma/client"; // Mock better-auth modules to inspect genericOAuth plugin configuration const mockGenericOAuth = vi.fn().mockReturnValue({ id: "generic-oauth" }); const mockBetterAuth = vi.fn().mockReturnValue({ handler: vi.fn() }); const mockPrismaAdapter = vi.fn().mockReturnValue({}); vi.mock("better-auth/plugins", () => ({ genericOAuth: (...args: unknown[]) => mockGenericOAuth(...args), })); vi.mock("better-auth", () => ({ betterAuth: (...args: unknown[]) => mockBetterAuth(...args), })); vi.mock("better-auth/adapters/prisma", () => ({ prismaAdapter: (...args: unknown[]) => mockPrismaAdapter(...args), })); import { isOidcEnabled, validateOidcConfig, createAuth, getTrustedOrigins, getBetterAuthBaseUrl, } from "./auth.config"; describe("auth.config", () => { // Store original env vars to restore after each test const originalEnv = { ...process.env }; beforeEach(() => { // Clear relevant env vars before each test delete process.env.OIDC_ENABLED; delete process.env.OIDC_ISSUER; delete process.env.OIDC_CLIENT_ID; delete process.env.OIDC_CLIENT_SECRET; delete process.env.OIDC_REDIRECT_URI; delete process.env.NODE_ENV; delete process.env.BETTER_AUTH_URL; delete process.env.NEXT_PUBLIC_APP_URL; delete process.env.NEXT_PUBLIC_API_URL; delete process.env.TRUSTED_ORIGINS; delete process.env.COOKIE_DOMAIN; }); afterEach(() => { // Restore original env vars process.env = { ...originalEnv }; }); describe("isOidcEnabled", () => { it("should return false when OIDC_ENABLED is not set", () => { expect(isOidcEnabled()).toBe(false); }); it("should return false when OIDC_ENABLED is 'false'", () => { process.env.OIDC_ENABLED = "false"; expect(isOidcEnabled()).toBe(false); }); it("should return false when OIDC_ENABLED is '0'", () => { process.env.OIDC_ENABLED = "0"; expect(isOidcEnabled()).toBe(false); }); it("should return false when OIDC_ENABLED is empty string", () => { process.env.OIDC_ENABLED = ""; expect(isOidcEnabled()).toBe(false); }); it("should return true when OIDC_ENABLED is 'true'", () => { process.env.OIDC_ENABLED = "true"; expect(isOidcEnabled()).toBe(true); }); it("should return true when OIDC_ENABLED is '1'", () => { process.env.OIDC_ENABLED = "1"; expect(isOidcEnabled()).toBe(true); }); }); describe("validateOidcConfig", () => { describe("when OIDC is disabled", () => { it("should not throw when OIDC_ENABLED is not set", () => { expect(() => validateOidcConfig()).not.toThrow(); }); it("should not throw when OIDC_ENABLED is false even if vars are missing", () => { process.env.OIDC_ENABLED = "false"; // Intentionally not setting any OIDC vars expect(() => validateOidcConfig()).not.toThrow(); }); }); describe("when OIDC is enabled", () => { beforeEach(() => { process.env.OIDC_ENABLED = "true"; }); it("should throw when OIDC_ISSUER is missing", () => { process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; expect(() => validateOidcConfig()).toThrow("OIDC_ISSUER"); expect(() => validateOidcConfig()).toThrow("OIDC authentication is enabled"); }); it("should throw when OIDC_CLIENT_ID is missing", () => { process.env.OIDC_ISSUER = "https://auth.example.com/"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; expect(() => validateOidcConfig()).toThrow("OIDC_CLIENT_ID"); }); it("should throw when OIDC_CLIENT_SECRET is missing", () => { process.env.OIDC_ISSUER = "https://auth.example.com/"; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; expect(() => validateOidcConfig()).toThrow("OIDC_CLIENT_SECRET"); }); it("should throw when OIDC_REDIRECT_URI is missing", () => { process.env.OIDC_ISSUER = "https://auth.example.com/"; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; expect(() => validateOidcConfig()).toThrow("OIDC_REDIRECT_URI"); }); it("should throw when all required vars are missing", () => { expect(() => validateOidcConfig()).toThrow( "OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI" ); }); it("should throw when vars are empty strings", () => { process.env.OIDC_ISSUER = ""; process.env.OIDC_CLIENT_ID = ""; process.env.OIDC_CLIENT_SECRET = ""; process.env.OIDC_REDIRECT_URI = ""; expect(() => validateOidcConfig()).toThrow( "OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI" ); }); it("should throw when vars are whitespace only", () => { process.env.OIDC_ISSUER = " "; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; expect(() => validateOidcConfig()).toThrow("OIDC_ISSUER"); }); it("should throw when OIDC_ISSUER does not end with trailing slash", () => { process.env.OIDC_ISSUER = "https://auth.example.com/application/o/mosaic"; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; expect(() => validateOidcConfig()).toThrow("OIDC_ISSUER must end with a trailing slash"); expect(() => validateOidcConfig()).toThrow("https://auth.example.com/application/o/mosaic"); }); it("should not throw with valid complete configuration", () => { process.env.OIDC_ISSUER = "https://auth.example.com/application/o/mosaic-stack/"; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; expect(() => validateOidcConfig()).not.toThrow(); }); it("should suggest disabling OIDC in error message", () => { expect(() => validateOidcConfig()).toThrow("OIDC_ENABLED=false"); }); describe("OIDC_REDIRECT_URI validation", () => { beforeEach(() => { process.env.OIDC_ISSUER = "https://auth.example.com/application/o/mosaic-stack/"; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; }); it("should throw when OIDC_REDIRECT_URI is not a valid URL", () => { process.env.OIDC_REDIRECT_URI = "not-a-url"; expect(() => validateOidcConfig()).toThrow("OIDC_REDIRECT_URI must be a valid URL"); expect(() => validateOidcConfig()).toThrow("not-a-url"); expect(() => validateOidcConfig()).toThrow("Parse error:"); }); it("should throw when OIDC_REDIRECT_URI path does not start with /auth/oauth2/callback", () => { process.env.OIDC_REDIRECT_URI = "https://app.example.com/oauth/callback"; expect(() => validateOidcConfig()).toThrow( 'OIDC_REDIRECT_URI path must start with "/auth/oauth2/callback"' ); expect(() => validateOidcConfig()).toThrow("/oauth/callback"); }); it("should accept a valid OIDC_REDIRECT_URI with /auth/oauth2/callback path", () => { process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; expect(() => validateOidcConfig()).not.toThrow(); }); it("should accept OIDC_REDIRECT_URI with exactly /auth/oauth2/callback path", () => { process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback"; expect(() => validateOidcConfig()).not.toThrow(); }); it("should warn but not throw when using localhost in production", () => { process.env.NODE_ENV = "production"; process.env.OIDC_REDIRECT_URI = "http://localhost:3000/auth/oauth2/callback/authentik"; const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); expect(() => validateOidcConfig()).not.toThrow(); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining("OIDC_REDIRECT_URI uses localhost") ); warnSpy.mockRestore(); }); it("should warn but not throw when using 127.0.0.1 in production", () => { process.env.NODE_ENV = "production"; process.env.OIDC_REDIRECT_URI = "http://127.0.0.1:3000/auth/oauth2/callback/authentik"; const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); expect(() => validateOidcConfig()).not.toThrow(); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining("OIDC_REDIRECT_URI uses localhost") ); warnSpy.mockRestore(); }); it("should not warn about localhost when not in production", () => { process.env.NODE_ENV = "development"; process.env.OIDC_REDIRECT_URI = "http://localhost:3000/auth/oauth2/callback/authentik"; const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); expect(() => validateOidcConfig()).not.toThrow(); expect(warnSpy).not.toHaveBeenCalled(); warnSpy.mockRestore(); }); }); }); }); describe("createAuth - genericOAuth PKCE configuration", () => { beforeEach(() => { mockGenericOAuth.mockClear(); mockBetterAuth.mockClear(); mockPrismaAdapter.mockClear(); }); it("should enable PKCE in the genericOAuth provider config when OIDC is enabled", () => { process.env.OIDC_ENABLED = "true"; process.env.OIDC_ISSUER = "https://auth.example.com/application/o/mosaic-stack/"; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockGenericOAuth).toHaveBeenCalledOnce(); const callArgs = mockGenericOAuth.mock.calls[0][0] as { config: Array<{ pkce?: boolean; redirectURI?: string }>; }; expect(callArgs.config[0].pkce).toBe(true); expect(callArgs.config[0].redirectURI).toBe( "https://app.example.com/auth/oauth2/callback/authentik" ); }); it("should not call genericOAuth when OIDC is disabled", () => { process.env.OIDC_ENABLED = "false"; const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockGenericOAuth).not.toHaveBeenCalled(); }); it("should throw if OIDC_CLIENT_ID is missing when OIDC is enabled", () => { process.env.OIDC_ENABLED = "true"; process.env.OIDC_ISSUER = "https://auth.example.com/application/o/mosaic-stack/"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; // OIDC_CLIENT_ID deliberately not set // validateOidcConfig will throw first, so we need to bypass it // by setting the var then deleting it after validation // Instead, test via the validation path which is fine — but let's // verify the plugin-level guard by using a direct approach: // Set env to pass validateOidcConfig, then delete OIDC_CLIENT_ID // The validateOidcConfig will catch this first, which is correct behavior const mockPrisma = {} as PrismaClient; expect(() => createAuth(mockPrisma)).toThrow("OIDC_CLIENT_ID"); }); it("should throw if OIDC_CLIENT_SECRET is missing when OIDC is enabled", () => { process.env.OIDC_ENABLED = "true"; process.env.OIDC_ISSUER = "https://auth.example.com/application/o/mosaic-stack/"; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; // OIDC_CLIENT_SECRET deliberately not set const mockPrisma = {} as PrismaClient; expect(() => createAuth(mockPrisma)).toThrow("OIDC_CLIENT_SECRET"); }); it("should throw if OIDC_ISSUER is missing when OIDC is enabled", () => { process.env.OIDC_ENABLED = "true"; process.env.OIDC_CLIENT_ID = "test-client-id"; process.env.OIDC_CLIENT_SECRET = "test-client-secret"; process.env.OIDC_REDIRECT_URI = "https://app.example.com/auth/oauth2/callback/authentik"; // OIDC_ISSUER deliberately not set const mockPrisma = {} as PrismaClient; expect(() => createAuth(mockPrisma)).toThrow("OIDC_ISSUER"); }); }); describe("getTrustedOrigins", () => { it("should return localhost URLs when NODE_ENV is not production", () => { process.env.NODE_ENV = "development"; const origins = getTrustedOrigins(); expect(origins).toContain("http://localhost:3000"); expect(origins).toContain("http://localhost:3001"); }); it("should return localhost URLs when NODE_ENV is not set", () => { // NODE_ENV is deleted in beforeEach, so it's undefined here const origins = getTrustedOrigins(); expect(origins).toContain("http://localhost:3000"); expect(origins).toContain("http://localhost:3001"); }); it("should exclude localhost URLs in production", () => { process.env.NODE_ENV = "production"; const origins = getTrustedOrigins(); expect(origins).not.toContain("http://localhost:3000"); expect(origins).not.toContain("http://localhost:3001"); }); it("should parse TRUSTED_ORIGINS comma-separated values", () => { process.env.TRUSTED_ORIGINS = "https://app.mosaicstack.dev,https://api.mosaicstack.dev"; const origins = getTrustedOrigins(); expect(origins).toContain("https://app.mosaicstack.dev"); expect(origins).toContain("https://api.mosaicstack.dev"); }); it("should trim whitespace from TRUSTED_ORIGINS entries", () => { process.env.TRUSTED_ORIGINS = " https://app.mosaicstack.dev , https://api.mosaicstack.dev "; const origins = getTrustedOrigins(); expect(origins).toContain("https://app.mosaicstack.dev"); expect(origins).toContain("https://api.mosaicstack.dev"); }); it("should filter out empty strings from TRUSTED_ORIGINS", () => { process.env.TRUSTED_ORIGINS = "https://app.mosaicstack.dev,,, ,"; const origins = getTrustedOrigins(); expect(origins).toContain("https://app.mosaicstack.dev"); // No empty strings in the result origins.forEach((o) => expect(o).not.toBe("")); }); it("should include NEXT_PUBLIC_APP_URL", () => { process.env.NEXT_PUBLIC_APP_URL = "https://my-app.example.com"; const origins = getTrustedOrigins(); expect(origins).toContain("https://my-app.example.com"); }); it("should include NEXT_PUBLIC_API_URL", () => { process.env.NEXT_PUBLIC_API_URL = "https://my-api.example.com"; const origins = getTrustedOrigins(); expect(origins).toContain("https://my-api.example.com"); }); it("should deduplicate origins", () => { process.env.NEXT_PUBLIC_APP_URL = "http://localhost:3000"; process.env.TRUSTED_ORIGINS = "http://localhost:3000,http://localhost:3001"; // NODE_ENV not set, so localhost fallbacks are also added const origins = getTrustedOrigins(); const countLocalhost3000 = origins.filter((o) => o === "http://localhost:3000").length; const countLocalhost3001 = origins.filter((o) => o === "http://localhost:3001").length; expect(countLocalhost3000).toBe(1); expect(countLocalhost3001).toBe(1); }); it("should handle all env vars missing gracefully", () => { // All env vars deleted in beforeEach; NODE_ENV is also deleted (not production) const origins = getTrustedOrigins(); // Should still return localhost fallbacks since not in production expect(origins).toContain("http://localhost:3000"); expect(origins).toContain("http://localhost:3001"); expect(origins).toHaveLength(2); }); it("should return empty array when all env vars missing in production", () => { process.env.NODE_ENV = "production"; const origins = getTrustedOrigins(); expect(origins).toHaveLength(0); }); it("should combine all sources correctly", () => { process.env.NEXT_PUBLIC_APP_URL = "https://app.mosaicstack.dev"; process.env.NEXT_PUBLIC_API_URL = "https://api.mosaicstack.dev"; process.env.TRUSTED_ORIGINS = "https://extra.example.com"; process.env.NODE_ENV = "development"; const origins = getTrustedOrigins(); expect(origins).toContain("https://app.mosaicstack.dev"); expect(origins).toContain("https://api.mosaicstack.dev"); expect(origins).toContain("https://extra.example.com"); expect(origins).toContain("http://localhost:3000"); expect(origins).toContain("http://localhost:3001"); expect(origins).toHaveLength(5); }); it("should reject invalid URLs in TRUSTED_ORIGINS with a warning including error details", () => { process.env.TRUSTED_ORIGINS = "not-a-url,https://valid.example.com"; process.env.NODE_ENV = "production"; const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const origins = getTrustedOrigins(); expect(origins).toContain("https://valid.example.com"); expect(origins).not.toContain("not-a-url"); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining('Ignoring invalid URL in TRUSTED_ORIGINS: "not-a-url"') ); // Verify that error detail is included in the warning const warnCall = warnSpy.mock.calls.find( (call) => typeof call[0] === "string" && call[0].includes("not-a-url") ); expect(warnCall).toBeDefined(); expect(warnCall![0]).toMatch(/\(.*\)$/); warnSpy.mockRestore(); }); it("should reject non-HTTP origins in TRUSTED_ORIGINS with a warning", () => { process.env.TRUSTED_ORIGINS = "ftp://files.example.com,https://valid.example.com"; process.env.NODE_ENV = "production"; const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); const origins = getTrustedOrigins(); expect(origins).toContain("https://valid.example.com"); expect(origins).not.toContain("ftp://files.example.com"); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining("Ignoring non-HTTP origin in TRUSTED_ORIGINS") ); warnSpy.mockRestore(); }); }); describe("createAuth - session and cookie configuration", () => { beforeEach(() => { mockGenericOAuth.mockClear(); mockBetterAuth.mockClear(); mockPrismaAdapter.mockClear(); }); it("should configure session expiresIn to 7 days (604800 seconds)", () => { const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { session: { expiresIn: number; updateAge: number }; }; expect(config.session.expiresIn).toBe(604800); }); it("should configure session updateAge to 2 hours (7200 seconds)", () => { const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { session: { expiresIn: number; updateAge: number }; }; expect(config.session.updateAge).toBe(7200); }); it("should configure BetterAuth database ID generation as UUID", () => { const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { advanced: { database: { generateId: string; }; }; }; expect(config.advanced.database.generateId).toBe("uuid"); }); it("should set httpOnly cookie attribute to true", () => { const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { advanced: { defaultCookieAttributes: { httpOnly: boolean; secure: boolean; sameSite: string; }; }; }; expect(config.advanced.defaultCookieAttributes.httpOnly).toBe(true); }); it("should set sameSite cookie attribute to lax", () => { const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { advanced: { defaultCookieAttributes: { httpOnly: boolean; secure: boolean; sameSite: string; }; }; }; expect(config.advanced.defaultCookieAttributes.sameSite).toBe("lax"); }); it("should set secure cookie attribute to true in production", () => { process.env.NODE_ENV = "production"; process.env.NEXT_PUBLIC_API_URL = "https://api.example.com"; const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { advanced: { defaultCookieAttributes: { httpOnly: boolean; secure: boolean; sameSite: string; }; }; }; expect(config.advanced.defaultCookieAttributes.secure).toBe(true); }); it("should set secure cookie attribute to false in non-production", () => { process.env.NODE_ENV = "development"; const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { advanced: { defaultCookieAttributes: { httpOnly: boolean; secure: boolean; sameSite: string; }; }; }; expect(config.advanced.defaultCookieAttributes.secure).toBe(false); }); it("should set cookie domain when COOKIE_DOMAIN env var is present", () => { process.env.COOKIE_DOMAIN = ".mosaicstack.dev"; const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { advanced: { defaultCookieAttributes: { httpOnly: boolean; secure: boolean; sameSite: string; domain?: string; }; }; }; expect(config.advanced.defaultCookieAttributes.domain).toBe(".mosaicstack.dev"); }); it("should not set cookie domain when COOKIE_DOMAIN env var is absent", () => { delete process.env.COOKIE_DOMAIN; const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { advanced: { defaultCookieAttributes: { httpOnly: boolean; secure: boolean; sameSite: string; domain?: string; }; }; }; expect(config.advanced.defaultCookieAttributes.domain).toBeUndefined(); }); }); describe("getBetterAuthBaseUrl", () => { it("should prefer BETTER_AUTH_URL when set", () => { process.env.BETTER_AUTH_URL = "https://auth-base.example.com"; process.env.NEXT_PUBLIC_API_URL = "https://api.example.com"; expect(getBetterAuthBaseUrl()).toBe("https://auth-base.example.com"); }); it("should fall back to NEXT_PUBLIC_API_URL when BETTER_AUTH_URL is not set", () => { process.env.NEXT_PUBLIC_API_URL = "https://api.example.com"; expect(getBetterAuthBaseUrl()).toBe("https://api.example.com"); }); it("should throw when base URL is invalid", () => { process.env.BETTER_AUTH_URL = "not-a-url"; expect(() => getBetterAuthBaseUrl()).toThrow("BetterAuth base URL must be a valid URL"); }); it("should throw when base URL is missing in production", () => { process.env.NODE_ENV = "production"; expect(() => getBetterAuthBaseUrl()).toThrow("Missing BetterAuth base URL in production"); }); it("should throw when base URL is not https in production", () => { process.env.NODE_ENV = "production"; process.env.BETTER_AUTH_URL = "http://api.example.com"; expect(() => getBetterAuthBaseUrl()).toThrow( "BetterAuth base URL must use https in production" ); }); }); describe("createAuth - baseURL wiring", () => { beforeEach(() => { mockBetterAuth.mockClear(); mockPrismaAdapter.mockClear(); }); it("should pass BETTER_AUTH_URL into BetterAuth config", () => { process.env.BETTER_AUTH_URL = "https://api.mosaicstack.dev"; const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { baseURL?: string }; expect(config.baseURL).toBe("https://api.mosaicstack.dev"); }); it("should pass NEXT_PUBLIC_API_URL into BetterAuth config when BETTER_AUTH_URL is absent", () => { process.env.NEXT_PUBLIC_API_URL = "https://api.fallback.dev"; const mockPrisma = {} as PrismaClient; createAuth(mockPrisma); expect(mockBetterAuth).toHaveBeenCalledOnce(); const config = mockBetterAuth.mock.calls[0][0] as { baseURL?: string }; expect(config.baseURL).toBe("https://api.fallback.dev"); }); }); });