import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import IdentityCheckOverlay from '@/components/IdentityCheckOverlay' import { inventoryApi } from '@/lib/api' import * as toast from 'react-hot-toast' vi.mock('@/lib/api') vi.mock('react-hot-toast') // ============================================================================ // HELPER: Render with Props // ============================================================================ const renderIdentityCheckOverlay = (props = {}) => { const defaultProps = { show: true, users: [ { id: 1, username: 'admin', email: 'admin@test.com' }, { id: 2, username: 'operator', email: 'op@test.com' }, ], onAuthenticated: vi.fn(), ...props, } return render() } describe('IdentityCheckOverlay Component', () => { beforeEach(() => { vi.clearAllMocks() }) // ============================================================================ // UNIT TESTS: Rendering & Visibility // ============================================================================ describe('Rendering', () => { it('should render overlay when show is true', () => { const { container } = renderIdentityCheckOverlay({ show: true }) expect(container).toBeInTheDocument() }) it('should not render when show is false', () => { const { container } = renderIdentityCheckOverlay({ show: false }) expect(container.querySelector('.fixed')).not.toBeInTheDocument() }) it('should display overlay title', () => { const { container } = renderIdentityCheckOverlay() expect(container.textContent).toContain('Protocol Access') }) it('should display overlay subtitle', () => { const { container } = renderIdentityCheckOverlay() expect(container.textContent).toContain('Select operator') }) }) // ============================================================================ // UNIT TESTS: User List Rendering // ============================================================================ describe('User Selection', () => { it('should render user buttons', () => { const users = [ { id: 1, username: 'admin', email: 'admin@test.com' }, { id: 2, username: 'operator', email: 'op@test.com' }, ] const { container } = renderIdentityCheckOverlay({ users }) expect(container.textContent).toContain('admin') expect(container.textContent).toContain('operator') }) it('should display user names in buttons', () => { const { container } = renderIdentityCheckOverlay() expect(container.textContent).toContain('admin') }) it('should handle empty user list', () => { const { container } = renderIdentityCheckOverlay({ users: [] }) expect(container).toBeInTheDocument() }) it('should render with single user', () => { const { container } = renderIdentityCheckOverlay({ users: [{ id: 1, username: 'solo', email: 'solo@test.com' }], }) expect(container.textContent).toContain('solo') }) }) // ============================================================================ // UNIT TESTS: Login Form Rendering // ============================================================================ describe('Login Form', () => { it('should render overlay container with proper styling', () => { const { container } = renderIdentityCheckOverlay() const modal = container.querySelector('.rounded-\\[2\\.5rem\\]') expect(modal || container.querySelector('[style*="border"]')).toBeTruthy() }) it('should handle user selection for local login', async () => { const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should show password input after user selection', async () => { const { container } = renderIdentityCheckOverlay() const buttons = container.querySelectorAll('button') expect(buttons.length).toBeGreaterThan(0) }) it('should display enterprise login option', () => { const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) }) // ============================================================================ // UNIT TESTS: Form Validation // ============================================================================ describe('Form Validation', () => { it('should require username for enterprise login', async () => { const onAuthenticated = vi.fn() const { container } = renderIdentityCheckOverlay({ onAuthenticated }) expect(container).toBeInTheDocument() }) it('should require password for local user login', async () => { const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should show error message for empty username', async () => { const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should show error message for invalid credentials', async () => { vi.mocked(inventoryApi.login).mockRejectedValue( new Error('Invalid credentials') ) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) }) // ============================================================================ // UNIT TESTS: LDAP Authentication Flow // ============================================================================ describe('LDAP Authentication', () => { it('should handle LDAP login request', async () => { vi.mocked(inventoryApi.login).mockResolvedValue({ id: 1, username: 'enterprise_user', token: 'mock-token', }) const onAuthenticated = vi.fn() const { container } = renderIdentityCheckOverlay({ onAuthenticated }) expect(container).toBeInTheDocument() }) it('should handle LDAP authentication errors', async () => { vi.mocked(inventoryApi.login).mockRejectedValue( new Error('LDAP server unreachable') ) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should handle group membership validation', async () => { vi.mocked(inventoryApi.login).mockRejectedValue( new Error('User not in authorized group') ) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) }) // ============================================================================ // UNIT TESTS: Token Storage // ============================================================================ describe('Token Storage & Callback', () => { it('should call onAuthenticated with user data on successful login', async () => { const mockUser = { id: 1, username: 'testuser', token: 'mock-jwt-token', } vi.mocked(inventoryApi.login).mockResolvedValue(mockUser) const onAuthenticated = vi.fn() const { container } = renderIdentityCheckOverlay({ onAuthenticated }) expect(container).toBeInTheDocument() }) it('should clear selected user state after authentication', async () => { const mockUser = { id: 1, username: 'operator', token: 'token-123', } vi.mocked(inventoryApi.login).mockResolvedValue(mockUser) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should preserve token through authentication', async () => { const mockUser = { id: 1, username: 'admin', token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', } vi.mocked(inventoryApi.login).mockResolvedValue(mockUser) const onAuthenticated = vi.fn() const { container } = renderIdentityCheckOverlay({ onAuthenticated }) expect(container).toBeInTheDocument() }) }) // ============================================================================ // UNIT TESTS: Error Handling // ============================================================================ describe('Error Handling', () => { it('should display error toast on login failure', async () => { const toastErrorSpy = vi.spyOn(toast, 'default').mockImplementation(vi.fn()) vi.mocked(inventoryApi.login).mockRejectedValue( new Error('Login failed') ) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should handle network errors during login', async () => { vi.mocked(inventoryApi.login).mockRejectedValue( new Error('Network error') ) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should handle invalid password for local user', async () => { vi.mocked(inventoryApi.login).mockRejectedValue( new Error('Invalid password') ) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should show different error message for enterprise vs local', async () => { vi.mocked(inventoryApi.login).mockRejectedValue( new Error('Authentication failed') ) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) it('should handle API timeout during login', async () => { vi.mocked(inventoryApi.login).mockImplementation( () => new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 100) ) ) const { container } = renderIdentityCheckOverlay() expect(container).toBeInTheDocument() }) }) // ============================================================================ // UNIT TESTS: Accessibility // ============================================================================ describe('Accessibility', () => { it('should have proper modal structure', () => { const { container } = renderIdentityCheckOverlay() const modal = container.querySelector('.fixed') expect(modal).toBeInTheDocument() }) it('should have button elements with proper semantics', () => { const { container } = renderIdentityCheckOverlay() const buttons = container.querySelectorAll('button') expect(buttons.length).toBeGreaterThan(0) }) it('should have focusable form inputs', () => { const { container } = renderIdentityCheckOverlay() const inputs = container.querySelectorAll('input') inputs.forEach(input => { expect(input).toBeInTheDocument() }) }) it('should have proper icon semantics', () => { const { container } = renderIdentityCheckOverlay() const svgs = container.querySelectorAll('svg') expect(svgs.length).toBeGreaterThan(0) }) }) // ============================================================================ // UNIT TESTS: State Management // ============================================================================ describe('State Management', () => { it('should reset form state after login', async () => { vi.mocked(inventoryApi.login).mockResolvedValue({ id: 1, username: 'user', token: 'token', }) const onAuthenticated = vi.fn() const { container } = renderIdentityCheckOverlay({ onAuthenticated }) expect(container).toBeInTheDocument() }) it('should maintain user list between renders', () => { const users = [ { id: 1, username: 'user1', email: 'user1@test.com' }, { id: 2, username: 'user2', email: 'user2@test.com' }, ] const { container, rerender } = renderIdentityCheckOverlay({ users }) expect(container.textContent).toContain('user1') expect(container.textContent).toContain('user2') }) }) afterEach(() => { vi.clearAllMocks() }) })