import React from 'react'; import { act, renderHook } from '@testing-library/react'; import { AuthProvider, useAuth } from './auth-context'; import { getCurrentUser, login } from '../api/auth'; const mockPush = jest.fn(); jest.mock('next/navigation', () => ({ useRouter: () => ({ push: mockPush }) })); jest.mock('../api/auth', () => ({ login: jest.fn(), getCurrentUser: jest.fn(), register: jest.fn(), logout: jest.fn(), })); jest.mock('../api/client', () => ({ hasSession: () => false, clearAuthTokens: jest.fn() })); describe('authenticated navigation', () => { beforeEach(() => { jest.clearAllMocks(); jest .mocked(getCurrentUser) .mockResolvedValue({ id: 'test-user' } as Awaited>); }); it.each(['javascript:alert(1)', '//example.org', '/\\example.org', '/\n/example.org'])( 'does not navigate to attacker destination %j', async destination => { const { result } = renderHook(useAuth, { wrapper: AuthProvider }); await act(async () => { await result.current.login('user@example.org', 'password', destination); }); expect(login).toHaveBeenCalledWith({ email: 'user@example.org', password: 'password', rememberMe: false, }); expect(mockPush).toHaveBeenCalledWith('/dashboard'); expect(result.current.isAuthenticated).toBe(true); } ); it('preserves localized navigation after successful login', async () => { const { result } = renderHook(useAuth, { wrapper: AuthProvider }); await act(async () => { await result.current.login( 'user@example.org', 'password', '/fr/dashboard?tab=1#bookings', true ); }); expect(mockPush).toHaveBeenCalledWith('/fr/dashboard?tab=1#bookings'); }); });