143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
/**
|
|
* 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<string, unknown> = {}
|
|
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()
|
|
})
|
|
})
|
|
})
|