/** * Tests de sincronización de rol desde DB. * Verifica que buildAuthUser retorna el valor optimista correcto y que * syncProfileFromDB sobrescribe la heurística de email con el rol real de DB. */ import { describe, it, expect, vi, beforeEach } from 'vitest' import { SupabaseAuthService } from '@/auth/SupabaseAuthService' // ─── Mocks con vi.hoisted para evitar el TDZ al ser elevados al tope del archivo ── const { mockGetSession, mockMaybeSingle } = vi.hoisted(() => ({ mockGetSession: vi.fn(), mockMaybeSingle: vi.fn(), })) vi.mock('@/lib/supabase', () => { const buildChain = () => { const chain: Record = {} chain.select = vi.fn().mockReturnValue(chain) chain.eq = vi.fn().mockReturnValue(chain) chain.abortSignal = vi.fn().mockReturnValue(chain) chain.maybeSingle = mockMaybeSingle return chain } return { supabase: { auth: { getSession: mockGetSession, onAuthStateChange: vi.fn(() => ({ data: { subscription: { unsubscribe: vi.fn() } } })), signInWithPassword: vi.fn(), }, from: vi.fn().mockImplementation(() => buildChain()), }, SUPABASE_URL: 'https://test.supabase.co', SUPABASE_ANON_KEY: 'test-key', } }) // ─── Tests ─────────────────────────────────────────────────────────────────── describe('SupabaseAuthService', () => { const service = new SupabaseAuthService() beforeEach(() => { vi.clearAllMocks() mockMaybeSingle.mockResolvedValue({ data: null }) }) describe('buildAuthUser (vía getCurrentUser)', () => { it('devuelve platform_admin optimista para email @inquality.com.py', async () => { mockGetSession.mockResolvedValue({ data: { session: { user: { id: 'user-1', email: 'test@inquality.com.py', user_metadata: { full_name: 'Test InQ' }, }, }, }, }) const user = await service.getCurrentUser() expect(user?.platformRole).toBe('platform_admin') expect(user?.email).toBe('test@inquality.com.py') }) it('devuelve guest optimista para email externo', async () => { mockGetSession.mockResolvedValue({ data: { session: { user: { id: 'user-2', email: 'cliente@empresa.com', user_metadata: { full_name: 'Cliente Externo' }, }, }, }, }) const user = await service.getCurrentUser() expect(user?.platformRole).toBe('guest') }) }) describe('syncProfileFromDB', () => { it('retorna client_editor desde DB (sobrescribe heurística guest para email externo)', async () => { mockMaybeSingle.mockResolvedValue({ data: { name: 'Lucia García', company: 'Banco Solar', platform_role: 'client_editor', org_id: 'org-abc-123', }, }) const profile = await service.syncProfileFromDB('user-2') expect(profile?.platformRole).toBe('client_editor') expect(profile?.orgId).toBe('org-abc-123') expect(profile?.company).toBe('Banco Solar') }) it('retorna platform_admin desde DB incluso para email sin dominio @inquality', async () => { mockMaybeSingle.mockResolvedValue({ data: { name: 'Admin Externo', company: null, platform_role: 'platform_admin', org_id: 'org-inq-456', }, }) const profile = await service.syncProfileFromDB('user-3') expect(profile?.platformRole).toBe('platform_admin') expect(profile?.orgId).toBe('org-inq-456') }) it('retiene orgId correctamente tras sync con client_viewer', async () => { const orgId = 'b1c2d3e4-f5a6-7890-abcd-ef1234567890' mockMaybeSingle.mockResolvedValue({ data: { name: 'Viewer Test', company: 'Empresa SA', platform_role: 'client_viewer', org_id: orgId, }, }) const profile = await service.syncProfileFromDB('user-4') expect(profile?.orgId).toBe(orgId) expect(profile?.platformRole).toBe('client_viewer') }) it('retorna null cuando el usuario no existe en DB', async () => { mockMaybeSingle.mockResolvedValue({ data: null }) const profile = await service.syncProfileFromDB('nonexistent-user') expect(profile).toBeNull() }) }) })