import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { render, screen, waitFor } from '@testing-library/react' import { MemoryRouter, Routes, Route } from 'react-router-dom' import { HelmetProvider } from 'react-helmet-async' import { OAuthCallbackPage } from '../OAuthCallbackPage' import { authApi } from '@/api/auth' vi.mock('@/api/auth', () => ({ authApi: { googleCallback: vi.fn(), microsoftCallback: vi.fn(), }, })) vi.mock('@/store/authStore', () => ({ useAuthStore: () => ({ setTokens: vi.fn(), fetchUser: vi.fn().mockResolvedValue(undefined), }), })) function renderAt(path: string) { return render( } /> } /> , ) } describe('OAuthCallbackPage CSRF state validation', () => { beforeEach(() => { sessionStorage.clear() vi.clearAllMocks() }) afterEach(() => { sessionStorage.clear() }) it('shows error and does NOT call googleCallback when state in URL does not match sessionStorage', async () => { sessionStorage.setItem('rf-oauth-state', 'expected-state-value') renderAt('/auth/google/callback?code=auth-code-123&state=attacker-state') await waitFor(() => { expect( screen.getByText(/Invalid OAuth state/i), ).toBeInTheDocument() }) expect(authApi.googleCallback).not.toHaveBeenCalled() expect(authApi.microsoftCallback).not.toHaveBeenCalled() // Stored value must be cleared regardless of outcome. expect(sessionStorage.getItem('rf-oauth-state')).toBeNull() }) it('shows error and does NOT call googleCallback when stored state is missing', async () => { // No sessionStorage entry set. renderAt('/auth/google/callback?code=auth-code-123&state=any-state') await waitFor(() => { expect( screen.getByText(/Invalid OAuth state/i), ).toBeInTheDocument() }) expect(authApi.googleCallback).not.toHaveBeenCalled() }) })