Ctrl K
ComunidadGuías…

frontend/tests/pages/login.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { mountSuspended, mockNuxtImport } from '@nuxt/test-utils/runtime'
import { ref } from 'vue'
import LoginPage from '../../app/pages/login.vue'

const { mockUseAuth } = vi.hoisted(() => ({
  mockUseAuth: vi.fn()
}))

mockNuxtImport('useAuth', () => mockUseAuth)

describe('Login Page', () => {
  const mockLoginWithCredentials = vi.fn()
  const mockLoginWithOIDC = vi.fn()
  const mockFetchSession = vi.fn()
  const mockNavigateTo = vi.fn()

  let mockIsAuthenticated = ref(false)
  let mockIsOIDCEnabled = ref(false)
  let mockIsOIDCAutoRedirectEnabled = ref(false)
  let mockLoading = ref(false)

  beforeEach(() => {
    vi.clearAllMocks()
    mockIsAuthenticated = ref(false)
    mockIsOIDCEnabled = ref(false)
    mockIsOIDCAutoRedirectEnabled = ref(false)
    mockLoading = ref(false)
    mockLoginWithCredentials.mockReset()
    mockLoginWithOIDC.mockReset()
    mockFetchSession.mockReset()
    mockUseAuth.mockReturnValue({
      isAuthenticated: mockIsAuthenticated,
      isOIDCEnabled: mockIsOIDCEnabled,
      isOIDCAutoRedirectEnabled: mockIsOIDCAutoRedirectEnabled,
      loginWithCredentials: mockLoginWithCredentials,
      loginWithOIDC: mockLoginWithOIDC,
      fetchSession: mockFetchSession,
      loading: mockLoading
    })
  })

  describe('initial state', () => {
    it('shows loading spinner while checking session', () => {
      mockLoading.value = true

      expect(mockLoading.value).toBe(true)
    })

    it('redirects to home if already authenticated', async () => {
      mockIsAuthenticated.value = true

      if (mockIsAuthenticated.value) {
        mockNavigateTo('/')
      }

      expect(mockNavigateTo).toHaveBeenCalledWith('/')
    })

    it('shows login form when not authenticated', () => {
      mockLoading.value = false
      mockIsAuthenticated.value = false

      expect(mockLoading.value).toBe(false)
      expect(mockIsAuthenticated.value).toBe(false)
    })
  })

  describe('credential login', () => {
    it('calls loginWithCredentials with email and password', async () => {
      const credentials = { email: '[email protected]', password: 'password123' }
      mockLoginWithCredentials.mockResolvedValueOnce({ success: true })

      await mockLoginWithCredentials(credentials)

      expect(mockLoginWithCredentials).toHaveBeenCalledWith({
        email: '[email protected]',
        password: 'password123'
      })
    })

    it('shows error message on failed login', async () => {
      mockLoginWithCredentials.mockResolvedValueOnce({
        success: false,
        error: 'Invalid credentials'
      })

      const result = await mockLoginWithCredentials({
        email: '[email protected]',
        password: 'wrong'
      })

      expect(result.success).toBe(false)
      expect(result.error).toBe('Invalid credentials')
    })

    it('navigates to home on successful login', async () => {
      mockLoginWithCredentials.mockResolvedValueOnce({ success: true })

      const result = await mockLoginWithCredentials({
        email: '[email protected]',
        password: 'password123'
      })

      if (result.success) {
        mockIsAuthenticated.value = true
        mockNavigateTo('/')
      }

      expect(mockNavigateTo).toHaveBeenCalledWith('/')
    })

    it('disables submit button when email is empty', () => {
      const email = ''
      const password = 'password123'
      const isLoading = false

      const isDisabled = isLoading || !email || !password

      expect(isDisabled).toBe(true)
    })

    it('disables submit button when password is empty', () => {
      const email = '[email protected]'
      const password = ''
      const isLoading = false

      const isDisabled = isLoading || !email || !password

      expect(isDisabled).toBe(true)
    })

    it('disables submit button while loading', () => {
      const email = '[email protected]'
      const password = 'password123'
      const isLoading = true

      const isDisabled = isLoading || !email || !password

      expect(isDisabled).toBe(true)
    })

    it('enables submit button with valid credentials', () => {
      const email = '[email protected]'
      const password = 'password123'
      const isLoading = false

      const isDisabled = isLoading || !email || !password

      expect(isDisabled).toBe(false)
    })
  })

  describe('OIDC login', () => {
    it('shows SSO button when OIDC is enabled', () => {
      mockIsOIDCEnabled.value = true

      expect(mockIsOIDCEnabled.value).toBe(true)
    })

    it('hides credential form when OIDC is enabled', () => {
      mockIsOIDCEnabled.value = true

      const showCredentialForm = !mockIsOIDCEnabled.value

      expect(showCredentialForm).toBe(false)
    })

    it('shows credential form when OIDC is disabled', () => {
      mockIsOIDCEnabled.value = false

      const showCredentialForm = !mockIsOIDCEnabled.value

      expect(showCredentialForm).toBe(true)
    })

    it('calls loginWithOIDC when SSO button is clicked', () => {
      mockIsOIDCEnabled.value = true

      mockLoginWithOIDC()

      expect(mockLoginWithOIDC).toHaveBeenCalled()
    })

    it('automatically starts OIDC login when forwarding is enabled', async () => {
      mockFetchSession.mockResolvedValueOnce(undefined)
      mockIsOIDCEnabled.value = true
      mockIsOIDCAutoRedirectEnabled.value = true

      await mountSuspended(LoginPage, { route: '/login' })
      await flushPromises()

      expect(mockFetchSession).toHaveBeenCalledOnce()
      expect(mockLoginWithOIDC).toHaveBeenCalledOnce()
    })

    it('does not automatically start OIDC login after logout', async () => {
      mockFetchSession.mockResolvedValueOnce(undefined)
      mockIsOIDCEnabled.value = true
      mockIsOIDCAutoRedirectEnabled.value = true

      const wrapper = await mountSuspended(LoginPage, { route: '/login?logout=true' })
      await flushPromises()

      expect(mockLoginWithOIDC).not.toHaveBeenCalled()
      expect(wrapper.text()).toContain('Sign in with SSO')
    })

    it('keeps the ordinary OIDC login screen when forwarding is disabled', async () => {
      mockFetchSession.mockResolvedValueOnce(undefined)
      mockIsOIDCEnabled.value = true

      const wrapper = await mountSuspended(LoginPage, { route: '/login' })
      await flushPromises()

      expect(mockLoginWithOIDC).not.toHaveBeenCalled()
      expect(wrapper.text()).toContain('Sign in with SSO')
    })
  })

  describe('error handling', () => {
    it('clears error before new login attempt', async () => {
      let error = 'Previous error'
      expect(error).toBe('Previous error')

      error = ''
      mockLoginWithCredentials.mockResolvedValueOnce({ success: true })
      await mockLoginWithCredentials({ email: '[email protected]', password: 'pass' })

      expect(error).toBe('')
    })

    it('displays generic error when no error message provided', async () => {
      mockLoginWithCredentials.mockResolvedValueOnce({
        success: false,
        error: undefined
      })

      const result = await mockLoginWithCredentials({
        email: '[email protected]',
        password: 'wrong'
      })

      const errorMessage = result.error || 'Login failed'

      expect(errorMessage).toBe('Login failed')
    })

    it('handles network errors gracefully', async () => {
      mockLoginWithCredentials.mockRejectedValueOnce(new Error('Network error'))

      let error = ''
      try {
        await mockLoginWithCredentials({ email: '[email protected]', password: 'pass' })
      } catch (e: unknown) {
        const err = e as { message?: string }
        error = err.message || 'Login failed'
      }

      expect(error).toBe('Network error')
    })
  })

  describe('authentication state watching', () => {
    it('navigates to home when isAuthenticated becomes true', () => {
      mockIsAuthenticated.value = false

      const watchCallback = (authenticated: boolean) => {
        if (authenticated) {
          mockNavigateTo('/')
        }
      }

      mockIsAuthenticated.value = true
      watchCallback(mockIsAuthenticated.value)

      expect(mockNavigateTo).toHaveBeenCalledWith('/')
    })

    it('does not navigate when authentication fails', () => {
      mockIsAuthenticated.value = false

      const watchCallback = (authenticated: boolean) => {
        if (authenticated) {
          mockNavigateTo('/')
        }
      }

      watchCallback(mockIsAuthenticated.value)

      expect(mockNavigateTo).not.toHaveBeenCalled()
    })
  })

  describe('session checking on mount', () => {
    it('fetches session on component mount', async () => {
      await mockFetchSession()

      expect(mockFetchSession).toHaveBeenCalled()
    })

    it('redirects if session shows authenticated', async () => {
      mockFetchSession.mockImplementation(async () => {
        mockIsAuthenticated.value = true
      })

      await mockFetchSession()

      if (mockIsAuthenticated.value) {
        mockNavigateTo('/')
      }

      expect(mockNavigateTo).toHaveBeenCalledWith('/')
    })
  })
})