/**
* 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 }) => (
);
const RegisterForm = ({ onSuccess, loading, 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 }) => (
);
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();
});
});
});
});