/** * Frontend Integration Tests - Authentication Flow * * Tests for complete authentication workflows: * - Registration flow * - Login flow with MFA * - Password reset flow * - Session management * - Protected route access * * Author: Claude */ import React from 'react'; import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import '@testing-library/jest-dom'; import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom'; import userEvent from '@testing-library/user-event'; // Mock API service const mockApiService = { register: jest.fn(), login: jest.fn(), verifyMfa: jest.fn(), requestPasswordReset: jest.fn(), resetPassword: jest.fn(), refreshToken: jest.fn(), logout: jest.fn(), getCurrentUser: jest.fn(), }; // Mock components const LoginForm = ({ onSuccess, onMfaRequired, loading, error }) => (
{error &&
{error}
}
); const RegisterForm = ({ onSuccess, loading, error }) => (
{error &&
{error}
}
); const MFAVerificationForm = ({ onSuccess, onResendCode, loading, error }) => (
Enter the 6-digit code sent to your device
{error &&
{error}
}
); const ForgotPasswordForm = ({ onSuccess, loading, error }) => (
Enter your email to receive a reset link
{error &&
{error}
}
); const ResetPasswordForm = ({ onSuccess, loading, error }) => (
{error &&
{error}
}
); const ProtectedRoute = ({ children, requiredRole = 'user' }) => { const [isAuthenticated, setIsAuthenticated] = React.useState(false); const [userRole, setUserRole] = React.useState('user'); React.useEffect(() => { // Mock authentication check const checkAuth = async () => { try { const user = await mockApiService.getCurrentUser(); setIsAuthenticated(true); setUserRole(user.role); } catch { setIsAuthenticated(false); } }; checkAuth(); }, []); if (!isAuthenticated) { return
Redirecting to login...
; } if (requiredRole !== 'user' && userRole !== requiredRole) { return
Access Denied
; } return
{children}
; }; const Dashboard = () => (

Welcome to Dashboard

Protected Content
); // Test wrapper with router const TestApp = () => { const navigate = useNavigate(); return (
{ if (data.mfaRequired) { navigate('/mfa'); } else { navigate('/dashboard'); } }} onMfaRequired={() => navigate('/mfa')} /> } /> navigate('/login')} /> } /> navigate('/dashboard')} /> } /> navigate('/login')} /> } /> navigate('/login')} /> } /> } /> Home Page
} /> ); }; describe('Authentication Flow Integration Tests', () => { const user = userEvent.setup(); beforeEach(() => { jest.clearAllMocks(); mockApiService.getCurrentUser.mockResolvedValue({ id: 1, email: 'test@example.com', role: 'user' }); }); describe('Registration Flow', () => { test('complete registration flow', async () => { render( ); // Navigate to register page await act(async () => { window.location.assign = jest.fn(); window.location.href = '/register'; }); render( ); // Fill registration form const emailInput = screen.getByTestId('email-input'); const passwordInput = screen.getByTestId('password-input'); const confirmPasswordInput = screen.getByTestId('confirm-password-input'); const businessNameInput = screen.getByTestId('business-name-input'); const registerButton = screen.getByTestId('register-button'); await user.type(emailInput, 'newuser@example.com'); await user.type(passwordInput, 'SecurePass123!'); await user.type(confirmPasswordInput, 'SecurePass123!'); await user.type(businessNameInput, 'Test Business Sdn Bhd'); // Mock successful registration mockApiService.register.mockResolvedValueOnce({ success: true, message: 'Registration successful' }); await user.click(registerButton); await waitFor(() => { expect(mockApiService.register).toHaveBeenCalledWith({ email: 'newuser@example.com', password: 'SecurePass123!', confirmPassword: 'SecurePass123!', businessName: 'Test Business Sdn Bhd' }); }); // Should redirect to login after successful registration await waitFor(() => { expect(screen.getByTestId('login-form')).toBeInTheDocument(); }); }); test('registration with password mismatch', async () => { render( ); const emailInput = screen.getByTestId('email-input'); const passwordInput = screen.getByTestId('password-input'); const confirmPasswordInput = screen.getByTestId('confirm-password-input'); const registerButton = screen.getByTestId('register-button'); await user.type(emailInput, 'newuser@example.com'); await user.type(passwordInput, 'SecurePass123!'); await user.type(confirmPasswordInput, 'DifferentPass123!'); await user.click(registerButton); // Should show validation error await waitFor(() => { expect(screen.getByTestId('error-message')).toBeInTheDocument(); expect(screen.getByTestId('error-message')).toHaveTextContent('Passwords do not match'); }); }); test('registration with weak password', async () => { render( ); const emailInput = screen.getByTestId('email-input'); const passwordInput = screen.getByTestId('password-input'); const confirmPasswordInput = screen.getByTestId('confirm-password-input'); const registerButton = screen.getByTestId('register-button'); await user.type(emailInput, 'newuser@example.com'); await user.type(passwordInput, 'weak'); await user.type(confirmPasswordInput, 'weak'); await user.click(registerButton); // Should show validation error await waitFor(() => { expect(screen.getByTestId('error-message')).toBeInTheDocument(); expect(screen.getByTestId('error-message')).toHaveTextContent('Password is too weak'); }); }); }); describe('Login Flow with MFA', () => { test('successful login without MFA', async () => { render( ); const emailInput = screen.getByTestId('email-input'); const passwordInput = screen.getByTestId('password-input'); const loginButton = screen.getByTestId('login-button'); await user.type(emailInput, 'test@example.com'); await user.type(passwordInput, 'SecurePass123!'); // Mock successful login without MFA mockApiService.login.mockResolvedValueOnce({ success: true, user: { id: 1, email: 'test@example.com', role: 'user' }, accessToken: 'mock-token', refreshToken: 'mock-refresh-token', mfaRequired: false }); await user.click(loginButton); await waitFor(() => { expect(mockApiService.login).toHaveBeenCalledWith({ email: 'test@example.com', password: 'SecurePass123!' }); }); // Should redirect to dashboard await waitFor(() => { expect(screen.getByTestId('dashboard')).toBeInTheDocument(); }); }); test('login requiring MFA verification', async () => { render( ); const emailInput = screen.getByTestId('email-input'); const passwordInput = screen.getByTestId('password-input'); const loginButton = screen.getByTestId('login-button'); await user.type(emailInput, 'test@example.com'); await user.type(passwordInput, 'SecurePass123!'); // Mock login requiring MFA mockApiService.login.mockResolvedValueOnce({ success: true, mfaRequired: true, tempToken: 'mock-temp-token' }); await user.click(loginButton); // Should redirect to MFA verification await waitFor(() => { expect(screen.getByTestId('mfa-form')).toBeInTheDocument(); }); // Complete MFA verification const mfaCodeInput = screen.getByTestId('mfa-code-input'); const verifyButton = screen.getByTestId('verify-button'); await user.type(mfaCodeInput, '123456'); mockApiService.verifyMfa.mockResolvedValueOnce({ success: true, user: { id: 1, email: 'test@example.com', role: 'user' }, accessToken: 'mock-token', refreshToken: 'mock-refresh-token' }); await user.click(verifyButton); await waitFor(() => { expect(mockApiService.verifyMfa).toHaveBeenCalledWith({ code: '123456', tempToken: 'mock-temp-token' }); }); // Should redirect to dashboard await waitFor(() => { expect(screen.getByTestId('dashboard')).toBeInTheDocument(); }); }); test('login with invalid credentials', async () => { render( ); const emailInput = screen.getByTestId('email-input'); const passwordInput = screen.getByTestId('password-input'); const loginButton = screen.getByTestId('login-button'); await user.type(emailInput, 'invalid@example.com'); await user.type(passwordInput, 'wrongpassword'); // Mock failed login mockApiService.login.mockRejectedValueOnce({ response: { data: { message: 'Invalid credentials' } } }); await user.click(loginButton); await waitFor(() => { expect(screen.getByTestId('error-message')).toBeInTheDocument(); expect(screen.getByTestId('error-message')).toHaveTextContent('Invalid credentials'); }); // Should not redirect expect(screen.queryByTestId('dashboard')).not.toBeInTheDocument(); }); }); describe('Password Reset Flow', () => { test('complete password reset flow', async () => { render( ); // Navigate to forgot password const forgotPasswordLink = screen.getByTestId('forgot-password-link'); await user.click(forgotPasswordLink); await waitFor(() => { expect(screen.getByTestId('forgot-password-form')).toBeInTheDocument(); }); // Request password reset const emailInput = screen.getByTestId('email-input'); const submitButton = screen.getByTestId('submit-button'); await user.type(emailInput, 'test@example.com'); mockApiService.requestPasswordReset.mockResolvedValueOnce({ success: true, message: 'Reset link sent' }); await user.click(submitButton); await waitFor(() => { expect(mockApiService.requestPasswordReset).toHaveBeenCalledWith({ email: 'test@example.com' }); }); // Should show success message and redirect to login await waitFor(() => { expect(screen.getByTestId('login-form')).toBeInTheDocument(); }); }); test('password reset with new password', async () => { render( ); const newPasswordInput = screen.getByTestId('new-password-input'); const confirmPasswordInput = screen.getByTestId('confirm-password-input'); const resetButton = screen.getByTestId('reset-button'); await user.type(newPasswordInput, 'NewSecurePass123!'); await user.type(confirmPasswordInput, 'NewSecurePass123!'); mockApiService.resetPassword.mockResolvedValueOnce({ success: true, message: 'Password reset successful' }); await user.click(resetButton); await waitFor(() => { expect(mockApiService.resetPassword).toHaveBeenCalledWith({ token: 'mock-token', newPassword: 'NewSecurePass123!', confirmPassword: 'NewSecurePass123!' }); }); // Should redirect to login await waitFor(() => { expect(screen.getByTestId('login-form')).toBeInTheDocument(); }); }); }); describe('Session Management', () => { test('protected route access when authenticated', async () => { render( ); // Navigate to dashboard await act(async () => { window.location.href = '/dashboard'; }); render( ); // Should show protected content when authenticated await waitFor(() => { expect(screen.getByTestId('protected-content')).toBeInTheDocument(); expect(screen.getByTestId('dashboard')).toBeInTheDocument(); }); }); test('protected route redirect when not authenticated', async () => { // Mock unauthenticated state mockApiService.getCurrentUser.mockRejectedValueOnce(new Error('Not authenticated')); render( ); // Should redirect to login await waitFor(() => { expect(screen.getByTestId('redirecting-to-login')).toBeInTheDocument(); }); }); test('token refresh on expiration', async () => { render( ); const emailInput = screen.getByTestId('email-input'); const passwordInput = screen.getByTestId('password-input'); const loginButton = screen.getByTestId('login-button'); await user.type(emailInput, 'test@example.com'); await user.type(passwordInput, 'SecurePass123!'); // Mock successful login mockApiService.login.mockResolvedValueOnce({ success: true, user: { id: 1, email: 'test@example.com', role: 'user' }, accessToken: 'mock-token', refreshToken: 'mock-refresh-token', mfaRequired: false }); await user.click(loginButton); await waitFor(() => { expect(screen.getByTestId('dashboard')).toBeInTheDocument(); }); // Mock token refresh mockApiService.refreshToken.mockResolvedValueOnce({ success: true, accessToken: 'new-mock-token', refreshToken: 'new-mock-refresh-token' }); // Simulate token refresh (this would happen automatically in real app) await act(async () => { await mockApiService.refreshToken(); }); expect(mockApiService.refreshToken).toHaveBeenCalledWith({ refreshToken: 'mock-refresh-token' }); }); test('logout functionality', async () => { render( ); await waitFor(() => { expect(screen.getByTestId('dashboard')).toBeInTheDocument(); }); // Mock logout mockApiService.logout.mockResolvedValueOnce({ success: true }); // Simulate logout action await act(async () => { await mockApiService.logout(); }); expect(mockApiService.logout).toHaveBeenCalled(); // Should redirect to login after logout await waitFor(() => { expect(screen.getByTestId('login-form')).toBeInTheDocument(); }); }); }); describe('Error Handling and Edge Cases', () => { test('network error during login', async () => { render( ); const emailInput = screen.getByTestId('email-input'); const passwordInput = screen.getByTestId('password-input'); const loginButton = screen.getByTestId('login-button'); await user.type(emailInput, 'test@example.com'); await user.type(passwordInput, 'SecurePass123!'); // Mock network error mockApiService.login.mockRejectedValueOnce(new Error('Network error')); await user.click(loginButton); await waitFor(() => { expect(screen.getByTestId('error-message')).toBeInTheDocument(); expect(screen.getByTestId('error-message')).toHaveTextContent('Network error'); }); }); test('MFA code expiration', async () => { render( ); const mfaCodeInput = screen.getByTestId('mfa-code-input'); const verifyButton = screen.getByTestId('verify-button'); await user.type(mfaCodeInput, '123456'); // Mock expired code error mockApiService.verifyMfa.mockRejectedValueOnce({ response: { data: { message: 'Code expired' } } }); await user.click(verifyButton); await waitFor(() => { expect(screen.getByTestId('error-message')).toBeInTheDocument(); expect(screen.getByTestId('error-message')).toHaveTextContent('Code expired'); }); }); test('invalid reset password token', async () => { render( ); const newPasswordInput = screen.getByTestId('new-password-input'); const confirmPasswordInput = screen.getByTestId('confirm-password-input'); const resetButton = screen.getByTestId('reset-button'); await user.type(newPasswordInput, 'NewSecurePass123!'); await user.type(confirmPasswordInput, 'NewSecurePass123!'); // Mock invalid token error mockApiService.resetPassword.mockRejectedValueOnce({ response: { data: { message: 'Invalid or expired token' } } }); await user.click(resetButton); await waitFor(() => { expect(screen.getByTestId('error-message')).toBeInTheDocument(); expect(screen.getByTestId('error-message')).toHaveTextContent('Invalid or expired token'); }); }); }); describe('Cross-Browser Compatibility', () => { test('handles different browsers', () => { // Mock different browser environments const mockUserAgent = (agent) => { Object.defineProperty(navigator, 'userAgent', { writable: true, value: agent }); }; const browsers = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' ]; browsers.forEach(agent => { mockUserAgent(agent); render( ); expect(screen.getByTestId('login-form')).toBeInTheDocument(); }); }); }); });