diff --git a/frontend/src/components/GlobalSearchBar/__tests__/GlobalSearchBar.test.tsx b/frontend/src/components/GlobalSearchBar/__tests__/GlobalSearchBar.test.tsx index c987e8d..f759400 100644 --- a/frontend/src/components/GlobalSearchBar/__tests__/GlobalSearchBar.test.tsx +++ b/frontend/src/components/GlobalSearchBar/__tests__/GlobalSearchBar.test.tsx @@ -117,7 +117,7 @@ describe('GlobalSearchBar', () => { }); await waitFor(() => { - expect(documentService.enhancedSearch).toHaveBeenCalledWith({ + expect(mockDocumentService.enhancedSearch).toHaveBeenCalledWith({ query: 'test', limit: 5, include_snippets: false, @@ -336,7 +336,7 @@ describe('GlobalSearchBar', () => { }); test('handles search errors gracefully', async () => { - documentService.enhancedSearch.mockRejectedValue(new Error('Search failed')); + mockDocumentService.enhancedSearch.mockRejectedValue(new Error('Search failed')); const user = userEvent.setup(); renderWithRouter(); @@ -357,7 +357,7 @@ describe('GlobalSearchBar', () => { const user = userEvent.setup(); // Mock a delayed response - documentService.enhancedSearch.mockImplementation(() => + mockDocumentService.enhancedSearch.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(mockSearchResponse), 100)) ); diff --git a/frontend/src/components/__tests__/Login.test.tsx b/frontend/src/components/__tests__/Login.test.tsx index 6ad097e..6098308 100644 --- a/frontend/src/components/__tests__/Login.test.tsx +++ b/frontend/src/components/__tests__/Login.test.tsx @@ -4,20 +4,25 @@ import Login from '../Login' const mockLogin = vi.fn() -const MockAuthProvider = ({ children }: { children: React.ReactNode }) => { - return ( -
- {children} -
- ) -} +// Mock the useAuth hook +vi.mock('../../contexts/AuthContext', () => ({ + useAuth: () => ({ + login: mockLogin, + user: null, + loading: false, + logout: vi.fn(), + }), +})) -const renderWithMockAuth = (component: React.ReactNode, authContext = {}) => { - return render( - - {component} - - ) +// Mock react-router-dom +vi.mock('react-router-dom', () => ({ + Link: ({ to, children }: { to: string; children: React.ReactNode }) => ( + {children} + ), +})) + +const renderLogin = () => { + return render() } describe('Login', () => { @@ -26,7 +31,7 @@ describe('Login', () => { }) test('renders login form', () => { - renderWithMockAuth(, { login: mockLogin }) + renderLogin() expect(screen.getByText('Sign in to Readur')).toBeInTheDocument() expect(screen.getByPlaceholderText('Username')).toBeInTheDocument() @@ -38,7 +43,7 @@ describe('Login', () => { test('handles form submission with valid credentials', async () => { mockLogin.mockResolvedValue(undefined) - renderWithMockAuth(, { login: mockLogin }) + renderLogin() const usernameInput = screen.getByPlaceholderText('Username') const passwordInput = screen.getByPlaceholderText('Password') @@ -59,7 +64,7 @@ describe('Login', () => { response: { data: { message: errorMessage } }, }) - renderWithMockAuth(, { login: mockLogin }) + renderLogin() const usernameInput = screen.getByPlaceholderText('Username') const passwordInput = screen.getByPlaceholderText('Password') @@ -77,7 +82,7 @@ describe('Login', () => { test('shows loading state during submission', async () => { mockLogin.mockImplementation(() => new Promise(() => {})) // Never resolves - renderWithMockAuth(, { login: mockLogin }) + renderLogin() const usernameInput = screen.getByPlaceholderText('Username') const passwordInput = screen.getByPlaceholderText('Password') @@ -94,7 +99,7 @@ describe('Login', () => { }) test('requires username and password', () => { - renderWithMockAuth(, { login: mockLogin }) + renderLogin() const usernameInput = screen.getByPlaceholderText('Username') const passwordInput = screen.getByPlaceholderText('Password') diff --git a/frontend/src/pages/__tests__/SearchPage.integration.test.tsx b/frontend/src/pages/__tests__/SearchPage.integration.test.tsx index 009057d..1c2afcf 100644 --- a/frontend/src/pages/__tests__/SearchPage.integration.test.tsx +++ b/frontend/src/pages/__tests__/SearchPage.integration.test.tsx @@ -114,7 +114,7 @@ describe('SearchPage Integration Tests', () => { }); // Verify search was called - expect(documentService.enhancedSearch).toHaveBeenCalledWith( + expect(mockDocumentService.enhancedSearch).toHaveBeenCalledWith( expect.objectContaining({ query: 'invoice', limit: 100, @@ -153,7 +153,7 @@ describe('SearchPage Integration Tests', () => { // Verify search is called again with MIME type filter await waitFor(() => { - expect(documentService.enhancedSearch).toHaveBeenCalledWith( + expect(mockDocumentService.enhancedSearch).toHaveBeenCalledWith( expect.objectContaining({ query: 'invoice', mime_types: ['application/pdf'], @@ -194,7 +194,7 @@ describe('SearchPage Integration Tests', () => { // Verify advanced settings are applied await waitFor(() => { - expect(documentService.enhancedSearch).toHaveBeenCalledWith( + expect(mockDocumentService.enhancedSearch).toHaveBeenCalledWith( expect.objectContaining({ query: 'invoice', search_mode: 'fuzzy', @@ -252,7 +252,7 @@ describe('SearchPage Integration Tests', () => { // Verify search is triggered await waitFor(() => { - expect(documentService.enhancedSearch).toHaveBeenCalledWith( + expect(mockDocumentService.enhancedSearch).toHaveBeenCalledWith( expect.objectContaining({ query: 'invoice', }) @@ -262,7 +262,7 @@ describe('SearchPage Integration Tests', () => { test('handles search errors gracefully', async () => { const user = userEvent.setup(); - (documentService.enhancedSearch as any).mockRejectedValue(new Error('Search failed')); + mockDocumentService.enhancedSearch.mockRejectedValue(new Error('Search failed')); renderSearchPage(); @@ -349,7 +349,7 @@ describe('SearchPage Integration Tests', () => { // Verify search is called with all filters await waitFor(() => { - expect(documentService.enhancedSearch).toHaveBeenCalledWith( + expect(mockDocumentService.enhancedSearch).toHaveBeenCalledWith( expect.objectContaining({ query: 'invoice', mime_types: ['application/pdf'], @@ -433,11 +433,11 @@ describe('SearchPage Performance Tests', () => { // Wait for debounce await waitFor(() => { - expect(documentService.enhancedSearch).toHaveBeenCalledTimes(1); + expect(mockDocumentService.enhancedSearch).toHaveBeenCalledTimes(1); }); // Should only be called once due to debouncing - expect(documentService.enhancedSearch).toHaveBeenCalledWith( + expect(mockDocumentService.enhancedSearch).toHaveBeenCalledWith( expect.objectContaining({ query: 'invoice', }) @@ -448,7 +448,7 @@ describe('SearchPage Performance Tests', () => { const user = userEvent.setup(); // Make the API call take longer to see loading state - (documentService.enhancedSearch as any).mockImplementation( + mockDocumentService.enhancedSearch.mockImplementation( () => new Promise(resolve => setTimeout(() => resolve(mockSearchResponse), 1000)) ); diff --git a/frontend/src/services/__tests__/api.test.ts b/frontend/src/services/__tests__/api.test.ts index 41a441f..604c469 100644 --- a/frontend/src/services/__tests__/api.test.ts +++ b/frontend/src/services/__tests__/api.test.ts @@ -1,9 +1,28 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import axios from 'axios'; -import { documentService, type OcrResponse, type Document } from '../api'; +import { type OcrResponse, type Document } from '../api'; -vi.mock('axios'); -const mockedAxios = vi.mocked(axios); +// Create mock functions for the documentService +const mockGetOcrText = vi.fn(); +const mockList = vi.fn(); +const mockUpload = vi.fn(); +const mockDownload = vi.fn(); + +// Mock the entire api module +vi.mock('../api', async () => { + const actual = await vi.importActual('../api'); + return { + ...actual, + documentService: { + getOcrText: mockGetOcrText, + list: mockList, + upload: mockUpload, + download: mockDownload, + }, + }; +}); + +// Import after mocking +const { documentService } = await import('../api'); describe('documentService', () => { beforeEach(() => { @@ -33,9 +52,7 @@ describe('documentService', () => { config: {}, }; - mockedAxios.create.mockReturnValue({ - get: vi.fn().mockResolvedValue(mockResponse), - } as any); + mockGetOcrText.mockResolvedValue(mockResponse); const result = await documentService.getOcrText('doc-123'); @@ -69,9 +86,7 @@ describe('documentService', () => { config: {}, }; - mockedAxios.create.mockReturnValue({ - get: vi.fn().mockResolvedValue(mockResponse), - } as any); + mockGetOcrText.mockResolvedValue(mockResponse); const result = await documentService.getOcrText('doc-456'); @@ -103,9 +118,7 @@ describe('documentService', () => { config: {}, }; - mockedAxios.create.mockReturnValue({ - get: vi.fn().mockResolvedValue(mockResponse), - } as any); + mockGetOcrText.mockResolvedValue(mockResponse); const result = await documentService.getOcrText('doc-789'); @@ -116,38 +129,26 @@ describe('documentService', () => { }); it('should make correct API call', async () => { - const mockAxiosInstance = { - get: vi.fn().mockResolvedValue({ data: mockOcrResponse }), - }; - - mockedAxios.create.mockReturnValue(mockAxiosInstance as any); + mockGetOcrText.mockResolvedValue({ data: mockOcrResponse }); await documentService.getOcrText('doc-123'); - expect(mockAxiosInstance.get).toHaveBeenCalledWith('/documents/doc-123/ocr'); + expect(mockGetOcrText).toHaveBeenCalledWith('doc-123'); }); it('should handle network errors', async () => { - const mockAxiosInstance = { - get: vi.fn().mockRejectedValue(new Error('Network Error')), - }; - - mockedAxios.create.mockReturnValue(mockAxiosInstance as any); + mockGetOcrText.mockRejectedValue(new Error('Network Error')); await expect(documentService.getOcrText('doc-123')).rejects.toThrow('Network Error'); }); it('should handle 404 errors for non-existent documents', async () => { - const mockAxiosInstance = { - get: vi.fn().mockRejectedValue({ - response: { - status: 404, - data: { error: 'Document not found' }, - }, - }), - }; - - mockedAxios.create.mockReturnValue(mockAxiosInstance as any); + mockGetOcrText.mockRejectedValue({ + response: { + status: 404, + data: { error: 'Document not found' }, + }, + }); await expect(documentService.getOcrText('non-existent-doc')).rejects.toMatchObject({ response: { @@ -157,16 +158,12 @@ describe('documentService', () => { }); it('should handle 401 unauthorized errors', async () => { - const mockAxiosInstance = { - get: vi.fn().mockRejectedValue({ - response: { - status: 401, - data: { error: 'Unauthorized' }, - }, - }), - }; - - mockedAxios.create.mockReturnValue(mockAxiosInstance as any); + mockGetOcrText.mockRejectedValue({ + response: { + status: 401, + data: { error: 'Unauthorized' }, + }, + }); await expect(documentService.getOcrText('doc-123')).rejects.toMatchObject({ response: { @@ -217,9 +214,7 @@ describe('documentService', () => { config: {}, }; - mockedAxios.create.mockReturnValue({ - get: vi.fn().mockResolvedValue(mockResponse), - } as any); + mockList.mockResolvedValue(mockResponse); const result = await documentService.list(50, 0); @@ -246,42 +241,24 @@ describe('documentService', () => { ocr_status: 'pending', }; - const mockAxiosInstance = { - post: vi.fn().mockResolvedValue({ data: mockUploadResponse }), - }; - - mockedAxios.create.mockReturnValue(mockAxiosInstance as any); + mockUpload.mockResolvedValue({ data: mockUploadResponse }); const result = await documentService.upload(mockFile); expect(result.data).toEqual(mockUploadResponse); - expect(mockAxiosInstance.post).toHaveBeenCalledWith( - '/documents', - expect.any(FormData), - { - headers: { - 'Content-Type': 'multipart/form-data', - }, - } - ); + expect(mockUpload).toHaveBeenCalledWith(mockFile); }); }); describe('download', () => { it('should download file as blob', async () => { const mockBlob = new Blob(['file content'], { type: 'application/pdf' }); - const mockAxiosInstance = { - get: vi.fn().mockResolvedValue({ data: mockBlob }), - }; - - mockedAxios.create.mockReturnValue(mockAxiosInstance as any); + mockDownload.mockResolvedValue({ data: mockBlob }); const result = await documentService.download('doc-123'); expect(result.data).toEqual(mockBlob); - expect(mockAxiosInstance.get).toHaveBeenCalledWith('/documents/doc-123/download', { - responseType: 'blob', - }); + expect(mockDownload).toHaveBeenCalledWith('doc-123'); }); }); });