import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { getAuthProvider, isAnonymousMode, resolveAuthProviderName, withShareOptIn } from '@/lib/auth/provider' const AUTH_ENV_KEYS = [ 'AUTH_PROVIDER', 'ENABLE_AUTH', 'MORPHIC_CLOUD_DEPLOYMENT', 'NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY' ] as const function clearAuthEnv() { for (const key of AUTH_ENV_KEYS) { delete process.env[key] } } function configureSupabase() { process.env.NEXT_PUBLIC_SUPABASE_URL = 'https://example.supabase.co' process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY = 'sb_publishable_key' } describe('auth provider dispatch', () => { const originalEnv: Record = {} beforeEach(() => { for (const key of AUTH_ENV_KEYS) { originalEnv[key] = process.env[key] } clearAuthEnv() }) afterEach(() => { for (const key of AUTH_ENV_KEYS) { if (originalEnv[key] === undefined) { delete process.env[key] } else { process.env[key] = originalEnv[key] } } }) describe('explicit AUTH_PROVIDER configuration', () => { it('resolves supabase', () => { process.env.AUTH_PROVIDER = 'supabase' expect(resolveAuthProviderName()).toBe('supabase') expect(getAuthProvider().name).toBe('supabase') }) it('resolves none', () => { process.env.AUTH_PROVIDER = 'none' expect(resolveAuthProviderName()).toBe('none') expect(getAuthProvider().name).toBe('none') }) it('trims surrounding whitespace', () => { process.env.AUTH_PROVIDER = ' none ' expect(resolveAuthProviderName()).toBe('none') }) it('rejects unknown values', () => { process.env.AUTH_PROVIDER = 'auth0' expect(() => resolveAuthProviderName()).toThrow( 'Invalid AUTH_PROVIDER "auth0"' ) }) it('resolves better-auth to the better-auth provider', () => { process.env.AUTH_PROVIDER = 'better-auth' expect(resolveAuthProviderName()).toBe('better-auth') expect(getAuthProvider().name).toBe('better-auth') }) }) describe('isAnonymousMode', () => { it('returns true when AUTH_PROVIDER=none and ENABLE_AUTH is unset', () => { process.env.AUTH_PROVIDER = 'none' expect(isAnonymousMode()).toBe(true) }) it('returns true when AUTH_PROVIDER=none even if ENABLE_AUTH=true', () => { process.env.AUTH_PROVIDER = 'none' process.env.ENABLE_AUTH = 'true' expect(isAnonymousMode()).toBe(true) }) it('returns true when ENABLE_AUTH=false', () => { process.env.ENABLE_AUTH = 'false' expect(isAnonymousMode()).toBe(true) }) it('returns false when unset (supabase derived)', () => { expect(isAnonymousMode()).toBe(false) }) }) describe('backward-compatible derivation when AUTH_PROVIDER is unset', () => { it('derives none when ENABLE_AUTH=false', () => { process.env.ENABLE_AUTH = 'false' expect(resolveAuthProviderName()).toBe('none') }) it('derives supabase when ENABLE_AUTH=true and Supabase is configured', () => { process.env.ENABLE_AUTH = 'true' configureSupabase() expect(resolveAuthProviderName()).toBe('supabase') }) it('derives supabase when ENABLE_AUTH is unset and Supabase is configured', () => { configureSupabase() expect(resolveAuthProviderName()).toBe('supabase') }) it('derives supabase when ENABLE_AUTH is unset and Supabase is not configured', () => { // Guest behavior: the supabase provider resolves no user when unconfigured expect(resolveAuthProviderName()).toBe('supabase') }) it('derives supabase when ENABLE_AUTH=true and Supabase is not configured', () => { process.env.ENABLE_AUTH = 'true' expect(resolveAuthProviderName()).toBe('supabase') }) it('prefers none over Supabase configuration when ENABLE_AUTH=false', () => { process.env.ENABLE_AUTH = 'false' configureSupabase() expect(resolveAuthProviderName()).toBe('none') }) }) describe('Morphic Cloud deployment guard', () => { beforeEach(() => { process.env.MORPHIC_CLOUD_DEPLOYMENT = 'true' }) it('allows supabase', () => { configureSupabase() expect(resolveAuthProviderName()).toBe('supabase') }) it('refuses derived none (ENABLE_AUTH=false) with the historical error', () => { process.env.ENABLE_AUTH = 'false' expect(() => resolveAuthProviderName()).toThrow( 'ENABLE_AUTH=false is not allowed in MORPHIC_CLOUD_DEPLOYMENT' ) }) it('refuses explicit AUTH_PROVIDER=none', () => { process.env.AUTH_PROVIDER = 'none' expect(() => resolveAuthProviderName()).toThrow( 'AUTH_PROVIDER=none is not allowed in MORPHIC_CLOUD_DEPLOYMENT' ) }) it('refuses AUTH_PROVIDER=better-auth', () => { process.env.AUTH_PROVIDER = 'better-auth' expect(() => resolveAuthProviderName()).toThrow( 'AUTH_PROVIDER=better-auth is not allowed in MORPHIC_CLOUD_DEPLOYMENT' ) }) }) }) describe('supabase provider without configuration', () => { beforeEach(() => { delete process.env.NEXT_PUBLIC_SUPABASE_URL delete process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY delete process.env.AUTH_PROVIDER delete process.env.ENABLE_AUTH }) it('resolves no user when Supabase is not configured', async () => { const provider = getAuthProvider() expect(provider.name).toBe('supabase') await expect(provider.getCurrentUser()).resolves.toBeNull() }) it('passes the request through when Supabase is not configured', async () => { const { NextRequest } = await import('next/server') const provider = getAuthProvider() const request = new NextRequest('http://localhost:3000/search/abc') const response = await provider.handleSession(request) expect(response.status).toBe(200) expect(response.headers.get('location')).toBeNull() }) }) describe('withShareOptIn', () => { const base = { signUp: true, passwordReset: true, deleteUser: true, oauth: false, emailVerification: false, share: true } it('keeps sharing only when an opt-in flag is true (better-auth)', () => { const originalPublic = process.env.NEXT_PUBLIC_ENABLE_SHARE const originalRuntime = process.env.ENABLE_SHARE try { delete process.env.NEXT_PUBLIC_ENABLE_SHARE delete process.env.ENABLE_SHARE expect(withShareOptIn(base, 'better-auth').share).toBe(false) // The runtime flag is the one that works in prebuilt images, where // the public flag was inlined at build time. process.env.ENABLE_SHARE = 'true' expect(withShareOptIn(base, 'better-auth').share).toBe(true) process.env.ENABLE_SHARE = 'false' expect(withShareOptIn(base, 'better-auth').share).toBe(false) process.env.NEXT_PUBLIC_ENABLE_SHARE = 'true' expect(withShareOptIn(base, 'better-auth').share).toBe(true) process.env.NEXT_PUBLIC_ENABLE_SHARE = 'false' expect(withShareOptIn(base, 'better-auth').share).toBe(false) } finally { if (originalPublic === undefined) { delete process.env.NEXT_PUBLIC_ENABLE_SHARE } else { process.env.NEXT_PUBLIC_ENABLE_SHARE = originalPublic } if (originalRuntime === undefined) { delete process.env.ENABLE_SHARE } else { process.env.ENABLE_SHARE = originalRuntime } } }) it('leaves supabase sharing untouched by the opt-in flag', () => { // The default Supabase path must behave as it always has: sharing // with no env var configured. const originalPublic = process.env.NEXT_PUBLIC_ENABLE_SHARE const originalRuntime = process.env.ENABLE_SHARE try { delete process.env.NEXT_PUBLIC_ENABLE_SHARE delete process.env.ENABLE_SHARE expect(withShareOptIn(base, 'supabase').share).toBe(true) } finally { if (originalPublic === undefined) { delete process.env.NEXT_PUBLIC_ENABLE_SHARE } else { process.env.NEXT_PUBLIC_ENABLE_SHARE = originalPublic } if (originalRuntime === undefined) { delete process.env.ENABLE_SHARE } else { process.env.ENABLE_SHARE = originalRuntime } } }) it('never enables sharing for a provider without the capability', () => { process.env.NEXT_PUBLIC_ENABLE_SHARE = 'true' expect(withShareOptIn({ ...base, share: false }, 'better-auth').share).toBe( false ) }) })