/** * Tests for AuthContext - auth-context.tsx * * TDD RED phase: Write failing tests first. */ import { describe, it, expect, vi } from 'vitest' import { render, screen, waitFor, act } from '@testing-library/react' import { AuthProvider, useAuth, type AuthContextValue } from '../../context/auth-context' import type { AuthProvider as AuthProviderInterface } from '../../types' // Helper component to consume auth context function AuthConsumer({ onAuth }: { onAuth: (auth: AuthContextValue) => void }) { const auth = useAuth() onAuth(auth) return (
{auth.isAuthenticated.toString()} {auth.isLoading.toString()} {auth.identity?.fullName ?? 'none'}
) } function createMockAuthProvider(overrides: Partial = {}): AuthProviderInterface { return { login: vi.fn().mockResolvedValue(undefined), logout: vi.fn().mockResolvedValue(undefined), checkAuth: vi.fn().mockResolvedValue(undefined), checkError: vi.fn().mockResolvedValue(undefined), getIdentity: vi.fn().mockResolvedValue({ id: '1', fullName: 'Test User' }), getPermissions: vi.fn().mockResolvedValue(['admin']), ...overrides, } } describe('AuthContext', () => { describe('AuthProvider', () => { it('should check auth on mount and set authenticated state', async () => { const mockProvider = createMockAuthProvider() render( {}} /> ) // Initially loading expect(screen.getByTestId('loading').textContent).toBe('true') // Wait for auth check to complete await waitFor(() => { expect(screen.getByTestId('loading').textContent).toBe('false') }) expect(mockProvider.checkAuth).toHaveBeenCalled() expect(mockProvider.getIdentity).toHaveBeenCalled() expect(mockProvider.getPermissions).toHaveBeenCalled() expect(screen.getByTestId('authenticated').textContent).toBe('true') expect(screen.getByTestId('identity').textContent).toBe('Test User') }) it('should set unauthenticated state when checkAuth fails', async () => { const mockProvider = createMockAuthProvider({ checkAuth: vi.fn().mockRejectedValue(new Error('Not authenticated')), }) render( {}} /> ) await waitFor(() => { expect(screen.getByTestId('loading').textContent).toBe('false') }) expect(screen.getByTestId('authenticated').textContent).toBe('false') expect(screen.getByTestId('identity').textContent).toBe('none') }) it('should call authProvider.login and refresh auth on login', async () => { const mockProvider = createMockAuthProvider() let _authValue: AuthContextValue | undefined render( { _authValue = auth }} /> ) await waitFor(() => { expect(screen.getByTestId('loading').textContent).toBe('false') }) // Call login await act(async () => { await _authValue!.login({ username: 'test', password: 'pass' }) }) expect(mockProvider.login).toHaveBeenCalledWith({ username: 'test', password: 'pass' }) // checkAuth called twice: once on mount, once after login expect(mockProvider.checkAuth).toHaveBeenCalledTimes(2) }) it('should call authProvider.logout and clear state on logout', async () => { const mockProvider = createMockAuthProvider() let _authValue: AuthContextValue | undefined render( { _authValue = auth }} /> ) await waitFor(() => { expect(screen.getByTestId('authenticated').textContent).toBe('true') }) // Call logout await act(async () => { await _authValue!.logout() }) expect(mockProvider.logout).toHaveBeenCalled() expect(screen.getByTestId('authenticated').textContent).toBe('false') expect(screen.getByTestId('identity').textContent).toBe('none') }) it('should provide permissions from authProvider', async () => { const mockProvider = createMockAuthProvider({ getPermissions: vi.fn().mockResolvedValue(['admin', 'user', 'editor']), }) let _authValue: AuthContextValue | undefined render( { _authValue = auth }} /> ) await waitFor(() => { expect(screen.getByTestId('loading').textContent).toBe('false') }) expect(_authValue!.permissions).toEqual(['admin', 'user', 'editor']) }) }) describe('useAuth', () => { it('should throw error when used outside AuthProvider', () => { const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) expect(() => { render( {}} />) }).toThrow('useAuth must be used within an AuthProvider') consoleError.mockRestore() }) }) })