Some checks failed
System Monitoring / Health Checks (push) Has been cancelled
System Monitoring / Performance Monitoring (push) Has been cancelled
System Monitoring / Database Monitoring (push) Has been cancelled
System Monitoring / Cache Monitoring (push) Has been cancelled
System Monitoring / Log Monitoring (push) Has been cancelled
System Monitoring / Resource Monitoring (push) Has been cancelled
System Monitoring / Uptime Monitoring (push) Has been cancelled
System Monitoring / Backup Monitoring (push) Has been cancelled
System Monitoring / Security Monitoring (push) Has been cancelled
System Monitoring / Monitoring Dashboard (push) Has been cancelled
System Monitoring / Alerting (push) Has been cancelled
Security Scanning / Dependency Scanning (push) Has been cancelled
Security Scanning / Code Security Scanning (push) Has been cancelled
Security Scanning / Secrets Scanning (push) Has been cancelled
Security Scanning / Container Security Scanning (push) Has been cancelled
Security Scanning / Compliance Checking (push) Has been cancelled
Security Scanning / Security Dashboard (push) Has been cancelled
Security Scanning / Security Remediation (push) Has been cancelled
713 lines
22 KiB
TypeScript
713 lines
22 KiB
TypeScript
/**
|
|
* 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 }) => (
|
|
<div data-testid="login-form">
|
|
<input data-testid="email-input" placeholder="Email" type="email" />
|
|
<input data-testid="password-input" placeholder="Password" type="password" />
|
|
<button data-testid="login-button" disabled={loading}>
|
|
{loading ? 'Logging in...' : 'Login'}
|
|
</button>
|
|
<button data-testid="forgot-password-link">Forgot Password?</button>
|
|
{error && <div data-testid="error-message">{error}</div>}
|
|
</div>
|
|
);
|
|
|
|
const RegisterForm = ({ onSuccess, loading, error }) => (
|
|
<div data-testid="register-form">
|
|
<input data-testid="email-input" placeholder="Email" type="email" />
|
|
<input data-testid="password-input" placeholder="Password" type="password" />
|
|
<input data-testid="confirm-password-input" placeholder="Confirm Password" type="password" />
|
|
<input data-testid="business-name-input" placeholder="Business Name" />
|
|
<button data-testid="register-button" disabled={loading}>
|
|
{loading ? 'Registering...' : 'Register'}
|
|
</button>
|
|
{error && <div data-testid="error-message">{error}</div>}
|
|
</div>
|
|
);
|
|
|
|
const MFAVerificationForm = ({ onSuccess, onResendCode, loading, error }) => (
|
|
<div data-testid="mfa-form">
|
|
<div data-testid="mfa-instructions">Enter the 6-digit code sent to your device</div>
|
|
<input data-testid="mfa-code-input" placeholder="6-digit code" maxLength={6} />
|
|
<button data-testid="verify-button" disabled={loading}>
|
|
{loading ? 'Verifying...' : 'Verify Code'}
|
|
</button>
|
|
<button data-testid="resend-code-button">Resend Code</button>
|
|
{error && <div data-testid="error-message">{error}</div>}
|
|
</div>
|
|
);
|
|
|
|
const ForgotPasswordForm = ({ onSuccess, loading, error }) => (
|
|
<div data-testid="forgot-password-form">
|
|
<div data-testid="instructions">Enter your email to receive a reset link</div>
|
|
<input data-testid="email-input" placeholder="Email" type="email" />
|
|
<button data-testid="submit-button" disabled={loading}>
|
|
{loading ? 'Sending...' : 'Send Reset Link'}
|
|
</button>
|
|
{error && <div data-testid="error-message">{error}</div>}
|
|
</div>
|
|
);
|
|
|
|
const ResetPasswordForm = ({ onSuccess, loading, error }) => (
|
|
<div data-testid="reset-password-form">
|
|
<input data-testid="new-password-input" placeholder="New Password" type="password" />
|
|
<input data-testid="confirm-password-input" placeholder="Confirm New Password" type="password" />
|
|
<button data-testid="reset-button" disabled={loading}>
|
|
{loading ? 'Resetting...' : 'Reset Password'}
|
|
</button>
|
|
{error && <div data-testid="error-message">{error}</div>}
|
|
</div>
|
|
);
|
|
|
|
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 <div data-testid="redirecting-to-login">Redirecting to login...</div>;
|
|
}
|
|
|
|
if (requiredRole !== 'user' && userRole !== requiredRole) {
|
|
return <div data-testid="access-denied">Access Denied</div>;
|
|
}
|
|
|
|
return <div data-testid="protected-content">{children}</div>;
|
|
};
|
|
|
|
const Dashboard = () => (
|
|
<div data-testid="dashboard">
|
|
<h1>Welcome to Dashboard</h1>
|
|
<ProtectedRoute>
|
|
<div>Protected Content</div>
|
|
</ProtectedRoute>
|
|
</div>
|
|
);
|
|
|
|
// Test wrapper with router
|
|
const TestApp = () => {
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<div>
|
|
<Routes>
|
|
<Route path="/login" element={
|
|
<LoginForm
|
|
onSuccess={(data) => {
|
|
if (data.mfaRequired) {
|
|
navigate('/mfa');
|
|
} else {
|
|
navigate('/dashboard');
|
|
}
|
|
}}
|
|
onMfaRequired={() => navigate('/mfa')}
|
|
/>
|
|
} />
|
|
<Route path="/register" element={
|
|
<RegisterForm onSuccess={() => navigate('/login')} />
|
|
} />
|
|
<Route path="/mfa" element={
|
|
<MFAVerificationForm onSuccess={() => navigate('/dashboard')} />
|
|
} />
|
|
<Route path="/forgot-password" element={
|
|
<ForgotPasswordForm onSuccess={() => navigate('/login')} />
|
|
} />
|
|
<Route path="/reset-password/:token" element={
|
|
<ResetPasswordForm onSuccess={() => navigate('/login')} />
|
|
} />
|
|
<Route path="/dashboard" element={<Dashboard />} />
|
|
<Route path="/" element={<div data-testid="home-page">Home Page</div>} />
|
|
</Routes>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
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(
|
|
<BrowserRouter>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
// Navigate to register page
|
|
await act(async () => {
|
|
window.location.assign = jest.fn();
|
|
window.location.href = '/register';
|
|
});
|
|
|
|
render(
|
|
<BrowserRouter initialEntries={['/register']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
// 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(
|
|
<BrowserRouter initialEntries={['/register']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/register']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/login']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/login']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/login']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/login']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
// 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(
|
|
<BrowserRouter initialEntries={['/reset-password/mock-token']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
// Navigate to dashboard
|
|
await act(async () => {
|
|
window.location.href = '/dashboard';
|
|
});
|
|
|
|
render(
|
|
<BrowserRouter initialEntries={['/dashboard']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
// 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(
|
|
<BrowserRouter initialEntries={['/dashboard']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
// Should redirect to login
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('redirecting-to-login')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
test('token refresh on expiration', async () => {
|
|
render(
|
|
<BrowserRouter initialEntries={['/login']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/dashboard']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/login']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/mfa']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/reset-password/invalid-token']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
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(
|
|
<BrowserRouter initialEntries={['/login']}>
|
|
<TestApp />
|
|
</BrowserRouter>
|
|
);
|
|
|
|
expect(screen.getByTestId('login-form')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
}); |