diff --git a/frontend/src/components/Auth/__tests__/Login.oidc.test.tsx b/frontend/src/components/Auth/__tests__/Login.oidc.test.tsx index 8e48bd5..20c3a65 100644 --- a/frontend/src/components/Auth/__tests__/Login.oidc.test.tsx +++ b/frontend/src/components/Auth/__tests__/Login.oidc.test.tsx @@ -1,7 +1,23 @@ -import { screen, fireEvent, waitFor } from '@testing-library/react'; import { vi } from 'vitest'; -import Login from '../Login'; -import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils'; + +// Mock AuthContext to work with the test setup +vi.mock('../../../contexts/AuthContext', () => ({ + useAuth: vi.fn(() => ({ + user: null, + loading: false, + login: vi.fn().mockResolvedValue({}), + register: vi.fn().mockResolvedValue({}), + logout: vi.fn(), + })), +})); + +// Mock ThemeContext +vi.mock('../../../contexts/ThemeContext', () => ({ + useTheme: () => ({ + darkMode: false, + toggleDarkMode: vi.fn() + }), +})); // Mock the API vi.mock('../../../services/api', () => ({ @@ -25,6 +41,11 @@ vi.mock('react-router-dom', async () => { }; }); +// Now import after all mocks are set up +import { screen, fireEvent, waitFor } from '@testing-library/react'; +import { renderWithProviders, createMockUser } from '../../../test/test-utils'; +import Login from '../Login'; + // Mock window.location Object.defineProperty(window, 'location', { value: { @@ -36,7 +57,6 @@ Object.defineProperty(window, 'location', { describe('Login - OIDC Features', () => { beforeEach(() => { vi.clearAllMocks(); - setupTestEnvironment(); }); const renderLogin = () => { diff --git a/frontend/src/components/Auth/__tests__/OidcCallback.test.tsx b/frontend/src/components/Auth/__tests__/OidcCallback.test.tsx index f54375a..5761384 100644 --- a/frontend/src/components/Auth/__tests__/OidcCallback.test.tsx +++ b/frontend/src/components/Auth/__tests__/OidcCallback.test.tsx @@ -1,20 +1,46 @@ -import { screen, waitFor, fireEvent } from '@testing-library/react'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { vi } from 'vitest'; -import OidcCallback from '../OidcCallback'; -import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils'; -import { api } from '../../../services/api'; +import React from 'react'; -// Mock the API -vi.mock('../../../services/api', () => ({ - api: { - get: vi.fn(), - defaults: { - headers: { - common: {} - } +// Create stable mock functions +const mockLogin = vi.fn().mockResolvedValue({}); +const mockRegister = vi.fn().mockResolvedValue({}); +const mockLogout = vi.fn(); + +// Mock the auth context module completely +vi.mock('../../../contexts/AuthContext', () => ({ + useAuth: vi.fn(() => ({ + user: null, + loading: false, + login: mockLogin, + register: mockRegister, + logout: mockLogout, + })), + AuthProvider: ({ children }: { children: React.ReactNode }) => React.createElement('div', null, children), +})); + +// Mock axios comprehensively to prevent any real HTTP requests +import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../../test/comprehensive-mocks'; + +vi.mock('axios', () => createComprehensiveAxiosMock()); + +// Create the mock API object +const mockApi = { + get: vi.fn().mockResolvedValue({ data: { token: 'default-token' } }), + post: vi.fn().mockResolvedValue({ data: { success: true } }), + put: vi.fn().mockResolvedValue({ data: { success: true } }), + delete: vi.fn().mockResolvedValue({ data: { success: true } }), + patch: vi.fn().mockResolvedValue({ data: { success: true } }), + defaults: { + headers: { + common: {} } } +}; + +// Mock the services/api file +vi.mock('../../../services/api', () => ({ + api: mockApi, + default: mockApi, })); // Mock useNavigate @@ -28,6 +54,11 @@ vi.mock('react-router-dom', async () => { }; }); +// Now import after mocks +import { screen, waitFor, fireEvent } from '@testing-library/react'; +import { renderWithProviders } from '../../../test/test-utils'; +import OidcCallback from '../OidcCallback'; + // Mock window.location Object.defineProperty(window, 'location', { value: { @@ -39,28 +70,29 @@ Object.defineProperty(window, 'location', { describe('OidcCallback', () => { beforeEach(() => { vi.clearAllMocks(); - setupTestEnvironment(); + vi.resetModules(); window.location.href = ''; // Clear API mocks - (api.get as any).mockClear(); + mockApi.get.mockClear(); // Reset API mocks to default implementation - (api.get as any).mockResolvedValue({ data: { token: 'default-token' } }); + mockApi.get.mockResolvedValue({ data: { token: 'default-token' } }); }); const renderOidcCallback = (search = '') => { - return renderWithProviders( - - - } /> - Login Page} /> - - - ); + // Mock the URL search params for the component + const url = new URL(`http://localhost/auth/oidc/callback${search}`); + Object.defineProperty(window, 'location', { + value: { search: url.search }, + writable: true + }); + + // Use renderWithProviders to get auth context + return renderWithProviders(); }; it('shows loading state initially', async () => { // Mock the API call to delay so we can see the loading state - (api.get as any).mockImplementation(() => new Promise(() => {})); // Never resolves + mockApi.get.mockImplementation(() => new Promise(() => {})); // Never resolves renderOidcCallback('?code=test-code&state=test-state'); @@ -80,12 +112,12 @@ describe('OidcCallback', () => { } }; - (api.get as any).mockResolvedValueOnce(mockResponse); + mockApi.get.mockResolvedValueOnce(mockResponse); renderOidcCallback('?code=test-code&state=test-state'); await waitFor(() => { - expect(api.get).toHaveBeenCalledWith('/auth/oidc/callback?code=test-code&state=test-state'); + expect(mockApi.get).toHaveBeenCalledWith('/auth/oidc/callback?code=test-code&state=test-state'); }); expect(localStorage.setItem).toHaveBeenCalledWith('token', 'test-jwt-token'); @@ -114,7 +146,7 @@ describe('OidcCallback', () => { } } }; - (api.get as any).mockRejectedValueOnce(error); + mockApi.get.mockRejectedValueOnce(error); renderOidcCallback('?code=test-code&state=test-state'); @@ -125,7 +157,7 @@ describe('OidcCallback', () => { }); it('handles invalid response from server', async () => { - (api.get as any).mockResolvedValueOnce({ + mockApi.get.mockResolvedValueOnce({ data: { // Missing token user: { id: '123' } @@ -141,7 +173,7 @@ describe('OidcCallback', () => { }); it('provides return to login button on error', async () => { - (api.get as any).mockRejectedValueOnce(new Error('Network error')); + mockApi.get.mockRejectedValueOnce(new Error('Network error')); renderOidcCallback('?code=test-code&state=test-state'); diff --git a/frontend/src/components/GlobalSearchBar/__tests__/GlobalSearchBar.test.tsx b/frontend/src/components/GlobalSearchBar/__tests__/GlobalSearchBar.test.tsx index 16d9e9e..8ffc886 100644 --- a/frontend/src/components/GlobalSearchBar/__tests__/GlobalSearchBar.test.tsx +++ b/frontend/src/components/GlobalSearchBar/__tests__/GlobalSearchBar.test.tsx @@ -1,14 +1,19 @@ -import React from 'react'; -import { screen, fireEvent, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import { vi } from 'vitest'; -import GlobalSearchBar from '../GlobalSearchBar'; -import { renderWithProviders, createMockApiServices, setupTestEnvironment, createMockLocalStorage } from '../../../test/test-utils'; +import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../../test/comprehensive-mocks'; -// Use centralized API mocking -const mockServices = createMockApiServices(); -const mockDocumentService = mockServices.documentService; -const localStorageMock = createMockLocalStorage(); +// Mock axios comprehensively to prevent any real HTTP requests +vi.mock('axios', () => createComprehensiveAxiosMock()); + +// Mock API services comprehensively +vi.mock('../../../services/api', async () => { + const actual = await vi.importActual('../../../services/api'); + const apiMocks = createComprehensiveApiMocks(); + + return { + ...actual, + ...apiMocks, + }; +}); // Mock useNavigate const mockNavigate = vi.fn(); @@ -20,6 +25,18 @@ vi.mock('react-router-dom', async () => { }; }); +// Import after mocking +import React from 'react'; +import { screen, fireEvent, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import GlobalSearchBar from '../GlobalSearchBar'; +import { renderWithProviders, createMockApiServices, createMockLocalStorage } from '../../../test/test-utils'; + +// Use centralized API mocking +const mockServices = createMockApiServices(); +const mockDocumentService = mockServices.documentService; +const localStorageMock = createMockLocalStorage(); + // Mock data const mockSearchResponse = { data: { @@ -45,7 +62,6 @@ const mockSearchResponse = { describe('GlobalSearchBar', () => { beforeEach(() => { vi.clearAllMocks(); - setupTestEnvironment(); localStorageMock.getItem.mockReturnValue(null); mockDocumentService.enhancedSearch.mockResolvedValue(mockSearchResponse); }); diff --git a/frontend/src/components/Labels/__tests__/Label.test.tsx b/frontend/src/components/Labels/__tests__/Label.test.tsx index ab82d83..48adfcc 100644 --- a/frontend/src/components/Labels/__tests__/Label.test.tsx +++ b/frontend/src/components/Labels/__tests__/Label.test.tsx @@ -4,8 +4,7 @@ import Label, { type LabelData } from '../Label'; import { renderWithProviders } from '../../../test/test-utils'; import { createMockLabel, - createMockSystemLabel, - setupTestEnvironment + createMockSystemLabel } from '../../../test/label-test-utils'; const mockLabel = createMockLabel({ @@ -31,7 +30,7 @@ const renderLabel = (props: Partial> = {}) => describe('Label Component', () => { beforeEach(() => { - setupTestEnvironment(); + // Test setup is handled globally }); describe('Basic Rendering', () => { diff --git a/frontend/src/components/Labels/__tests__/LabelCreateDialog.test.tsx b/frontend/src/components/Labels/__tests__/LabelCreateDialog.test.tsx index 6aebc27..9c9bd11 100644 --- a/frontend/src/components/Labels/__tests__/LabelCreateDialog.test.tsx +++ b/frontend/src/components/Labels/__tests__/LabelCreateDialog.test.tsx @@ -1,15 +1,17 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; import { screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; import LabelCreateDialog from '../LabelCreateDialog'; import { type LabelData } from '../Label'; import { renderWithProviders } from '../../../test/test-utils'; import { createMockLabel, - setupTestEnvironment, labelValidationScenarios } from '../../../test/label-test-utils'; +const theme = createTheme(); + const mockEditingLabel = createMockLabel({ name: 'Existing Label', description: 'An existing label', @@ -33,7 +35,6 @@ describe('LabelCreateDialog Component', () => { let user: ReturnType; beforeEach(() => { - setupTestEnvironment(); user = userEvent.setup(); }); diff --git a/frontend/src/components/Labels/__tests__/LabelSelector.test.tsx b/frontend/src/components/Labels/__tests__/LabelSelector.test.tsx index d41c7f2..98b3245 100644 --- a/frontend/src/components/Labels/__tests__/LabelSelector.test.tsx +++ b/frontend/src/components/Labels/__tests__/LabelSelector.test.tsx @@ -5,7 +5,6 @@ import LabelSelector from '../LabelSelector'; import { type LabelData } from '../Label'; import { renderWithProviders } from '../../../test/test-utils'; import { - setupTestEnvironment, testDataBuilders } from '../../../test/label-test-utils'; @@ -26,7 +25,6 @@ describe('LabelSelector Component', () => { let user: ReturnType; beforeEach(() => { - setupTestEnvironment(); user = userEvent.setup(); }); @@ -81,7 +79,7 @@ describe('LabelSelector Component', () => { await waitFor(() => { expect(screen.getByText('Work')).toBeInTheDocument(); - expect(screen.getByText('Personal Project')).toBeInTheDocument(); + expect(screen.getByText('Project Alpha')).toBeInTheDocument(); }); // Important should not appear in the dropdown options (but may appear in selected tags) @@ -141,7 +139,7 @@ describe('LabelSelector Component', () => { test('should remove label when delete button is clicked', async () => { const onLabelsChange = vi.fn(); // Use only non-system labels since system labels don't have delete buttons - const selectedLabels = [mockLabels[2]]; // Personal Project (non-system) + const selectedLabels = [mockLabels[3]]; // Project Alpha (non-system) renderLabelSelector({ selectedLabels, @@ -149,7 +147,7 @@ describe('LabelSelector Component', () => { }); // Find the chip with the delete button - const personalProjectChip = screen.getByText('Personal Project').closest('.MuiChip-root'); + const personalProjectChip = screen.getByText('Project Alpha').closest('.MuiChip-root'); expect(personalProjectChip).toBeInTheDocument(); // Find the delete button within that specific chip @@ -164,7 +162,7 @@ describe('LabelSelector Component', () => { }); test('should not show delete buttons when disabled', () => { - const selectedLabels = [mockLabels[2]]; // Non-system label + const selectedLabels = [mockLabels[3]]; // Non-system label renderLabelSelector({ selectedLabels, @@ -186,7 +184,7 @@ describe('LabelSelector Component', () => { // Check that labels appear in the dropdown expect(screen.getByText('Important')).toBeInTheDocument(); expect(screen.getByText('Work')).toBeInTheDocument(); - expect(screen.getByText('Personal Project')).toBeInTheDocument(); + expect(screen.getByText('Project Alpha')).toBeInTheDocument(); }); }); @@ -200,7 +198,7 @@ describe('LabelSelector Component', () => { await waitFor(() => { expect(screen.getByText('Important')).toBeInTheDocument(); expect(screen.getByText('Work')).toBeInTheDocument(); - expect(screen.queryByText('Personal Project')).not.toBeInTheDocument(); + expect(screen.queryByText('Project Alpha')).not.toBeInTheDocument(); }); }); }); @@ -215,7 +213,7 @@ describe('LabelSelector Component', () => { await waitFor(() => { expect(screen.getByText('Work')).toBeInTheDocument(); expect(screen.queryByText('Important')).not.toBeInTheDocument(); - expect(screen.queryByText('Personal Project')).not.toBeInTheDocument(); + expect(screen.queryByText('Project Alpha')).not.toBeInTheDocument(); }); }); @@ -376,10 +374,10 @@ describe('LabelSelector Component', () => { await user.click(input); await waitFor(() => { - expect(screen.getByText('Personal Project')).toBeInTheDocument(); + expect(screen.getByText('Project Alpha')).toBeInTheDocument(); }); - await user.click(screen.getByText('Personal Project')); + await user.click(screen.getByText('Project Alpha')); // Should not add the third label due to maxTags limit expect(onLabelsChange).not.toHaveBeenCalled(); diff --git a/frontend/src/components/Notifications/__tests__/NotificationPanel.simple.test.tsx b/frontend/src/components/Notifications/__tests__/NotificationPanel.simple.test.tsx index 081f7ab..b6aec47 100644 --- a/frontend/src/components/Notifications/__tests__/NotificationPanel.simple.test.tsx +++ b/frontend/src/components/Notifications/__tests__/NotificationPanel.simple.test.tsx @@ -1,8 +1,8 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; import { screen } from '@testing-library/react'; +import { renderWithProviders } from '../../../test/test-utils'; import NotificationPanel from '../NotificationPanel'; import { NotificationProvider } from '../../../contexts/NotificationContext'; -import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils'; import React from 'react'; // Mock date-fns @@ -28,7 +28,6 @@ const createMockAnchorEl = () => { describe('NotificationPanel - Simple Tests', () => { beforeEach(() => { - setupTestEnvironment(); }); test('should not render when anchorEl is null', () => { const { container } = renderWithProviders( diff --git a/frontend/src/components/Upload/__tests__/UploadZone.test.tsx b/frontend/src/components/Upload/__tests__/UploadZone.test.tsx index d861c34..a03c7f5 100644 --- a/frontend/src/components/Upload/__tests__/UploadZone.test.tsx +++ b/frontend/src/components/Upload/__tests__/UploadZone.test.tsx @@ -1,33 +1,30 @@ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'; +import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../../test/comprehensive-mocks'; + +// Mock axios comprehensively to prevent any real HTTP requests +vi.mock('axios', () => createComprehensiveAxiosMock()); + +// Mock API services comprehensively +vi.mock('../../../services/api', async () => { + const actual = await vi.importActual('../../../services/api'); + const apiMocks = createComprehensiveApiMocks(); + + return { + ...actual, + ...apiMocks, + }; +}); + +// Import after mocking import { screen, fireEvent, act, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import UploadZone from '../UploadZone'; -import { renderWithProviders, setupTestEnvironment, createMockApiServices } from '../../../test/test-utils'; +import { renderWithProviders, createMockApiServices } from '../../../test/test-utils'; import { createMockLabel } from '../../../test/label-test-utils'; // Setup centralized API mocks for this component const mockApiServices = createMockApiServices(); -// Mock axios directly with our mock labels -vi.mock('axios', () => ({ - default: { - create: vi.fn(() => ({ - get: vi.fn().mockResolvedValue({ - status: 200, - data: [createMockLabel({ - name: 'Test Label', - color: '#0969da', - document_count: 0, - source_count: 0, - })] - }), - post: vi.fn().mockResolvedValue({ status: 201, data: {} }), - put: vi.fn().mockResolvedValue({ status: 200, data: {} }), - delete: vi.fn().mockResolvedValue({ status: 204 }), - })), - }, -})); - const mockProps = { onUploadComplete: vi.fn(), }; @@ -37,7 +34,6 @@ describe('UploadZone', () => { beforeEach(() => { vi.clearAllMocks(); - setupTestEnvironment(); // Suppress console.error for "Failed to fetch labels" during tests originalConsoleError = console.error; console.error = vi.fn().mockImplementation((message, ...args) => { @@ -67,7 +63,7 @@ describe('UploadZone', () => { }); test('shows accepted file types in UI', async () => { - await renderWithProvider(); + await renderWithProviders(); // Wait for component to load await waitFor(() => { @@ -79,7 +75,7 @@ describe('UploadZone', () => { }); test('displays max file size limit', async () => { - await renderWithProvider(); + await renderWithProviders(); await waitFor(() => { expect(screen.getByText(/maximum file size/i)).toBeInTheDocument(); @@ -89,7 +85,7 @@ describe('UploadZone', () => { }); test('shows browse files button', async () => { - await renderWithProvider(); + await renderWithProviders(); await waitFor(() => { const browseButton = screen.getByRole('button', { name: /choose files/i }); @@ -161,7 +157,7 @@ describe('UploadZone', () => { test('handles click to browse files', async () => { const user = userEvent.setup(); - await renderWithProvider(); + await renderWithProviders(); await waitFor(() => { const browseButton = screen.getByRole('button', { name: /choose files/i }); @@ -178,7 +174,7 @@ describe('UploadZone', () => { }); test('renders upload zone structure correctly', async () => { - await renderWithProvider(); + await renderWithProviders(); // Wait for component to load await waitFor(() => { diff --git a/frontend/src/components/__tests__/FailedDocumentViewer.test.tsx b/frontend/src/components/__tests__/FailedDocumentViewer.test.tsx index 28a5e3e..51d7aba 100644 --- a/frontend/src/components/__tests__/FailedDocumentViewer.test.tsx +++ b/frontend/src/components/__tests__/FailedDocumentViewer.test.tsx @@ -1,16 +1,48 @@ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'; -import { screen, waitFor } from '@testing-library/react'; -import FailedDocumentViewer from '../FailedDocumentViewer'; -import { api } from '../../services/api'; -import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils'; -// Mock the API +// Create mock function before imports +const mockApiGet = vi.fn(); + +// Mock the api module before importing anything else vi.mock('../../services/api', () => ({ api: { - get: vi.fn(), - }, + get: mockApiGet, + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), + patch: vi.fn(), + defaults: { headers: { common: {} } }, + create: vi.fn(), + interceptors: { + request: { use: vi.fn(), eject: vi.fn() }, + response: { use: vi.fn(), eject: vi.fn() } + } + } })); +// Import after mocking +import { screen, waitFor } from '@testing-library/react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import FailedDocumentViewer from '../FailedDocumentViewer'; +import { renderWithProviders } from '../../test/test-utils'; +const theme = createTheme(); + +// Mock URL constructor with static methods +const mockCreateObjectURL = vi.fn(() => 'mock-object-url'); +const mockRevokeObjectURL = vi.fn(); + +global.URL = class URL { + constructor(url) { + this.href = url; + this.protocol = 'http:'; + this.hostname = 'localhost'; + this.pathname = '/'; + this.search = ''; + } + + static createObjectURL = mockCreateObjectURL; + static revokeObjectURL = mockRevokeObjectURL; +} as any; const defaultProps = { failedDocumentId: 'test-failed-doc-id', @@ -26,23 +58,19 @@ const renderFailedDocumentViewer = (props = {}) => { ); }; -// Mock Blob and URL.createObjectURL +// Mock Blob const mockBlob = vi.fn(() => ({ text: () => Promise.resolve('mock text content'), })); global.Blob = mockBlob as any; -const mockCreateObjectURL = vi.fn(() => 'mock-object-url'); -const mockRevokeObjectURL = vi.fn(); -global.URL = { - createObjectURL: mockCreateObjectURL, - revokeObjectURL: mockRevokeObjectURL, -} as any; - describe('FailedDocumentViewer', () => { beforeEach(() => { - setupTestEnvironment(); vi.clearAllMocks(); + // Set default mock response + mockApiGet.mockResolvedValue({ + data: new Blob(['mock document content'], { type: 'application/pdf' }) + }); }); afterEach(() => { @@ -52,7 +80,7 @@ describe('FailedDocumentViewer', () => { describe('Loading State', () => { test('should show loading spinner initially', () => { // Mock API to never resolve - vi.mocked(api.get).mockImplementation(() => new Promise(() => {})); + mockApiGet.mockImplementation(() => new Promise(() => {})); renderFailedDocumentViewer(); @@ -60,7 +88,7 @@ describe('FailedDocumentViewer', () => { }); test('should show loading spinner with correct styling', () => { - vi.mocked(api.get).mockImplementation(() => new Promise(() => {})); + mockApiGet.mockImplementation(() => new Promise(() => {})); renderFailedDocumentViewer(); @@ -79,12 +107,12 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock pdf content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer(); await waitFor(() => { - expect(api.get).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', { + expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', { responseType: 'blob' }); }); @@ -102,7 +130,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock image content'], { type: 'image/jpeg' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ filename: 'test-image.jpg', @@ -125,7 +153,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock text content'], { type: 'text/plain' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ filename: 'test-file.txt', @@ -143,7 +171,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/unknown' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ filename: 'test-file.unknown', @@ -163,7 +191,7 @@ describe('FailedDocumentViewer', () => { const error = { response: { status: 404 } }; - vi.mocked(api.get).mockRejectedValueOnce(error); + mockApiGet.mockRejectedValueOnce(error); renderFailedDocumentViewer(); @@ -175,7 +203,7 @@ describe('FailedDocumentViewer', () => { test('should show generic error for other failures', async () => { const error = new Error('Network error'); - vi.mocked(api.get).mockRejectedValueOnce(error); + mockApiGet.mockRejectedValueOnce(error); renderFailedDocumentViewer(); @@ -189,7 +217,7 @@ describe('FailedDocumentViewer', () => { const error = { response: { status: 500 } }; - vi.mocked(api.get).mockRejectedValueOnce(error); + mockApiGet.mockRejectedValueOnce(error); renderFailedDocumentViewer(); @@ -204,7 +232,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer(); @@ -222,34 +250,29 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValue(mockResponse); + mockApiGet.mockResolvedValue(mockResponse); const { rerender } = renderFailedDocumentViewer(); await waitFor(() => { - expect(api.get).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', { + expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', { responseType: 'blob' }); }); // Change the failedDocumentId + const newProps = { ...defaultProps, failedDocumentId: "new-doc-id" }; rerender( - - - + ); await waitFor(() => { - expect(api.get).toHaveBeenCalledWith('/documents/failed/new-doc-id/view', { + expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/new-doc-id/view', { responseType: 'blob' }); }); - expect(api.get).toHaveBeenCalledTimes(2); + expect(mockApiGet).toHaveBeenCalledTimes(2); }); }); @@ -258,7 +281,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock pdf content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ mimeType: 'application/pdf' @@ -278,7 +301,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock image content'], { type: mimeType }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); const filename = `test.${mimeType.split('/')[1]}`; renderFailedDocumentViewer({ @@ -304,7 +327,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock text content'], { type: mimeType }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); const filename = `test.${mimeType.split('/')[1]}`; renderFailedDocumentViewer({ @@ -329,7 +352,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer(); @@ -343,7 +366,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock image content'], { type: 'image/jpeg' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ mimeType: 'image/jpeg' @@ -363,12 +386,12 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer(); await waitFor(() => { - expect(api.get).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', { + expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', { responseType: 'blob' }); }); @@ -378,14 +401,14 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ failedDocumentId: 'different-doc-id' }); await waitFor(() => { - expect(api.get).toHaveBeenCalledWith('/documents/failed/different-doc-id/view', { + expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/different-doc-id/view', { responseType: 'blob' }); }); @@ -397,7 +420,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob([], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer(); @@ -413,7 +436,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ filename: longFilename @@ -429,7 +452,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ filename: specialFilename @@ -444,7 +467,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: '' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ mimeType: undefined as any @@ -462,7 +485,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock content'], { type: 'application/pdf' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer(); @@ -476,7 +499,7 @@ describe('FailedDocumentViewer', () => { const mockResponse = { data: new Blob(['mock image content'], { type: 'image/jpeg' }), }; - vi.mocked(api.get).mockResolvedValueOnce(mockResponse); + mockApiGet.mockResolvedValueOnce(mockResponse); renderFailedDocumentViewer({ mimeType: 'image/jpeg' diff --git a/frontend/src/components/__tests__/RetryRecommendations.test.tsx b/frontend/src/components/__tests__/RetryRecommendations.test.tsx index 2423a70..6a5f487 100644 --- a/frontend/src/components/__tests__/RetryRecommendations.test.tsx +++ b/frontend/src/components/__tests__/RetryRecommendations.test.tsx @@ -1,19 +1,33 @@ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { RetryRecommendations } from '../RetryRecommendations'; +import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../test/comprehensive-mocks'; -// Create unique mock functions for this test file +// Mock axios comprehensively to prevent any real HTTP requests +vi.mock('axios', () => createComprehensiveAxiosMock()); + +// Create mock functions for this specific test const mockGetRetryRecommendations = vi.fn(); const mockBulkRetryOcr = vi.fn(); -// Mock the API module with a unique namespace for this test -vi.mock('../../services/api', () => ({ - documentService: { - getRetryRecommendations: mockGetRetryRecommendations, - bulkRetryOcr: mockBulkRetryOcr, - }, -})); +// Mock the API module with comprehensive mocking +vi.mock('../../services/api', async () => { + const actual = await vi.importActual('../../services/api'); + const apiMocks = createComprehensiveApiMocks(); + + return { + ...actual, + ...apiMocks, + documentService: { + ...apiMocks.documentService, + getRetryRecommendations: mockGetRetryRecommendations, + bulkRetryOcr: mockBulkRetryOcr, + }, + }; +}); + +// Import after mocking +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { RetryRecommendations } from '../RetryRecommendations'; describe('RetryRecommendations', () => { const mockProps = { diff --git a/frontend/src/components/__tests__/TestNotification.test.tsx b/frontend/src/components/__tests__/TestNotification.test.tsx index da62250..279f916 100644 --- a/frontend/src/components/__tests__/TestNotification.test.tsx +++ b/frontend/src/components/__tests__/TestNotification.test.tsx @@ -1,8 +1,8 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; import { screen, fireEvent } from '@testing-library/react'; +import { renderWithProviders } from '../../test/test-utils'; import TestNotification from '../TestNotification'; import { NotificationProvider } from '../../contexts/NotificationContext'; -import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils'; import React from 'react'; const renderTestNotification = () => { @@ -15,7 +15,6 @@ const renderTestNotification = () => { describe('TestNotification', () => { beforeEach(() => { - setupTestEnvironment(); vi.clearAllMocks(); }); diff --git a/frontend/src/contexts/__tests__/NotificationContext.simple.test.tsx b/frontend/src/contexts/__tests__/NotificationContext.simple.test.tsx index ef321b9..bead910 100644 --- a/frontend/src/contexts/__tests__/NotificationContext.simple.test.tsx +++ b/frontend/src/contexts/__tests__/NotificationContext.simple.test.tsx @@ -1,7 +1,7 @@ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest'; -import { screen, act } from '@testing-library/react'; +import { screen, act, render } from '@testing-library/react'; import { NotificationProvider, useNotifications } from '../NotificationContext'; -import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils'; +import { renderWithProviders } from '../../test/test-utils'; import React from 'react'; // Simple test component @@ -62,7 +62,6 @@ const renderWithProvider = () => { describe('NotificationContext - Simple Tests', () => { beforeEach(() => { - setupTestEnvironment(); vi.useFakeTimers(); }); @@ -164,7 +163,7 @@ describe('NotificationContext - Simple Tests', () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); expect(() => { - renderWithProviders(); + render(); }).toThrow('useNotifications must be used within NotificationProvider'); consoleSpy.mockRestore(); @@ -198,7 +197,6 @@ describe('NotificationContext - Types', () => { }; beforeEach(() => { - setupTestEnvironment(); vi.useFakeTimers(); }); diff --git a/frontend/src/pages/__tests__/DocumentManagementPage.runtime-errors.test.tsx b/frontend/src/pages/__tests__/DocumentManagementPage.runtime-errors.test.tsx index 4203137..7fc33f3 100644 --- a/frontend/src/pages/__tests__/DocumentManagementPage.runtime-errors.test.tsx +++ b/frontend/src/pages/__tests__/DocumentManagementPage.runtime-errors.test.tsx @@ -1,40 +1,31 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; +import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../test/comprehensive-mocks'; + +// Mock axios comprehensively to prevent any real HTTP requests +vi.mock('axios', () => createComprehensiveAxiosMock()); + +// Mock API services comprehensively +vi.mock('../../services/api', async () => { + const actual = await vi.importActual('../../services/api'); + const apiMocks = createComprehensiveApiMocks(); + + return { + ...actual, + ...apiMocks, + }; +}); + +// Import components AFTER the mocks are set up import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import { ThemeProvider, createTheme } from '@mui/material/styles'; -import DocumentManagementPage from '../DocumentManagementPage'; import userEvent from '@testing-library/user-event'; +import DocumentManagementPage from '../DocumentManagementPage'; -// Create mock objects first -const mockDocumentService = { - getFailedDocuments: vi.fn(), - getFailedOcrDocuments: vi.fn(), - getDuplicates: vi.fn(), - retryOcr: vi.fn(), - deleteLowConfidence: vi.fn(), - deleteFailedOcr: vi.fn(), - downloadFile: vi.fn(), - getRetryRecommendations: vi.fn(), - getRetryStats: vi.fn(), - getDocumentRetryHistory: vi.fn(), -}; - -const mockQueueService = { - requeueFailed: vi.fn(), -}; - -const mockApi = { - get: vi.fn(), - delete: vi.fn(), - bulkRetryOcr: vi.fn(), -}; - -// Mock API with comprehensive responses -vi.mock('../../services/api', () => ({ - api: mockApi, - documentService: mockDocumentService, - queueService: mockQueueService, -})); +// Get references to the mocked modules using dynamic import +const { api, documentService } = await import('../../services/api'); +const mockApi = api; +const mockDocumentService = documentService; const theme = createTheme(); @@ -51,40 +42,11 @@ const DocumentManagementPageWrapper = ({ children }: { children: React.ReactNode describe('DocumentManagementPage - Runtime Error Prevention', () => { beforeEach(() => { vi.clearAllMocks(); - mockDocumentService.getFailedDocuments.mockClear(); - mockDocumentService.getFailedOcrDocuments.mockClear(); - mockDocumentService.getDuplicates.mockClear(); - mockQueueService.requeueFailed.mockClear(); - - // Setup default mock returns for retry functionality - mockDocumentService.getRetryRecommendations.mockResolvedValue({ - data: { recommendations: [], total_recommendations: 0 } - }); - mockDocumentService.getRetryStats.mockResolvedValue({ - data: { failure_reasons: [], file_types: [], total_failed: 0 } - }); - mockDocumentService.getDocumentRetryHistory.mockResolvedValue({ - data: { document_id: 'test', retry_history: [], total_retries: 0 } - }); - mockApi.bulkRetryOcr.mockResolvedValue({ - data: { success: true, queued_count: 0, matched_count: 0, documents: [] } - }); }); describe('OCR Confidence Display - Null Safety', () => { test('basic rendering test', async () => { - // Very basic test first - wait for loading to complete - mockDocumentService.getFailedDocuments.mockResolvedValue({ - data: { - documents: [], - pagination: { total: 0, limit: 25, offset: 0, total_pages: 0 }, - statistics: { total_failed: 0, by_reason: {}, by_stage: {} }, - }, - }); - - mockApi.get.mockResolvedValue({ - data: { total_count: 0, total_size: 0 }, - }); + // With axios mocked directly, all API calls should return empty data by default render( @@ -559,9 +521,8 @@ describe('DocumentManagementPage - Runtime Error Prevention', () => { describe('Ignored Files Tab Functionality', () => { test('should render ignored files tab without errors', async () => { - // Mock ignored files API responses - const { api } = await import('../../services/api'); - vi.mocked(api.get).mockImplementation((url) => { + // Setup mock responses using our already defined mocks + mockApi.get.mockImplementation((url) => { if (url.includes('/ignored-files/stats')) { return Promise.resolve({ data: { @@ -582,8 +543,7 @@ describe('DocumentManagementPage - Runtime Error Prevention', () => { return Promise.resolve({ data: {} }); }); - const { documentService } = await import('../../services/api'); - vi.mocked(documentService.getFailedDocuments).mockResolvedValueOnce({ + mockDocumentService.getFailedDocuments.mockResolvedValueOnce({ data: { documents: [], pagination: { total: 0, limit: 25, offset: 0, total_pages: 1 }, diff --git a/frontend/src/pages/__tests__/LabelsPage.test.tsx b/frontend/src/pages/__tests__/LabelsPage.test.tsx index 8fdc173..d81f159 100644 --- a/frontend/src/pages/__tests__/LabelsPage.test.tsx +++ b/frontend/src/pages/__tests__/LabelsPage.test.tsx @@ -3,7 +3,8 @@ import { screen, waitFor, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import LabelsPage from '../LabelsPage'; import * as useApiModule from '../../hooks/useApi'; -import { renderWithAuthenticatedUser, setupTestEnvironment } from '../../test/test-utils';\nimport { createMockLabel } from '../../test/label-test-utils'; +import { renderWithAuthenticatedUser } from '../../test/test-utils'; +import { createMockLabel } from '../../test/label-test-utils'; const mockLabels = [ createMockLabel({ @@ -66,7 +67,6 @@ describe('LabelsPage Component', () => { }; beforeEach(() => { - setupTestEnvironment(); user = userEvent.setup(); mockApi = { diff --git a/frontend/src/pages/__tests__/SearchPage.test.tsx b/frontend/src/pages/__tests__/SearchPage.test.tsx index 6411270..a6c514f 100644 --- a/frontend/src/pages/__tests__/SearchPage.test.tsx +++ b/frontend/src/pages/__tests__/SearchPage.test.tsx @@ -1,23 +1,26 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; import { screen } from '@testing-library/react'; +import { renderWithAuthenticatedUser } from '../../test/test-utils'; +import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../test/comprehensive-mocks'; import SearchPage from '../SearchPage'; -import { renderWithAuthenticatedUser, createMockUser, setupTestEnvironment } from '../../test/test-utils'; -// Mock API functions -vi.mock('../../services/api', () => ({ - searchDocuments: vi.fn(() => Promise.resolve({ - results: [], - total: 0, - page: 1, - page_size: 20 - })), - getSettings: vi.fn(() => Promise.resolve({})), -})); +// Mock axios comprehensively to prevent any real HTTP requests +vi.mock('axios', () => createComprehensiveAxiosMock()); + +// Mock API services comprehensively +vi.mock('../../services/api', async () => { + const actual = await vi.importActual('../../services/api'); + const apiMocks = createComprehensiveApiMocks(); + + return { + ...actual, + ...apiMocks, + }; +}); describe('SearchPage', () => { beforeEach(() => { vi.clearAllMocks(); - setupTestEnvironment(); }); test('renders search page structure', () => { diff --git a/frontend/src/pages/__tests__/WebDAVTab.test.tsx b/frontend/src/pages/__tests__/WebDAVTab.test.tsx index f0ae25c..eaeadf4 100644 --- a/frontend/src/pages/__tests__/WebDAVTab.test.tsx +++ b/frontend/src/pages/__tests__/WebDAVTab.test.tsx @@ -2,11 +2,26 @@ import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { vi, describe, it, expect, beforeEach } from 'vitest'; -import api from '../../services/api'; +import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../test/comprehensive-mocks'; -// Mock the API -vi.mock('../../services/api'); -const mockedApi = vi.mocked(api); +// Mock axios comprehensively to prevent any real HTTP requests +vi.mock('axios', () => createComprehensiveAxiosMock()); + +// Mock API services comprehensively +vi.mock('../../services/api', async () => { + const actual = await vi.importActual('../../services/api'); + const apiMocks = createComprehensiveApiMocks(); + + return { + ...actual, + default: apiMocks.api, // Since this file imports `api` as default + ...apiMocks, + }; +}); + +// Get references to the mocked modules using dynamic import +const { default: api } = await import('../../services/api'); +const mockedApi = api; // Mock settings with WebDAV configuration const mockSettings = { diff --git a/frontend/src/services/__tests__/api.test.ts b/frontend/src/services/__tests__/api.test.ts index ceef64a..b732ad6 100644 --- a/frontend/src/services/__tests__/api.test.ts +++ b/frontend/src/services/__tests__/api.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { type OcrResponse, type Document } from '../api'; -import { createMockApiServices, setupTestEnvironment } from '../../test/test-utils'; +import { createMockApiServices } from '../../test/test-utils'; // Use centralized API mocking const mockServices = createMockApiServices(); @@ -21,7 +21,6 @@ const { documentService } = await import('../api'); describe('documentService', () => { beforeEach(() => { vi.clearAllMocks(); - setupTestEnvironment(); }); describe('getOcrText', () => { @@ -124,21 +123,21 @@ describe('documentService', () => { }); it('should make correct API call', async () => { - mockGetOcrText.mockResolvedValue({ data: mockOcrResponse }); + mockDocumentService.getOcrText.mockResolvedValue({ data: mockOcrResponse }); await documentService.getOcrText('doc-123'); - expect(mockGetOcrText).toHaveBeenCalledWith('doc-123'); + expect(mockDocumentService.getOcrText).toHaveBeenCalledWith('doc-123'); }); it('should handle network errors', async () => { - mockGetOcrText.mockRejectedValue(new Error('Network Error')); + mockDocumentService.getOcrText.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 () => { - mockGetOcrText.mockRejectedValue({ + mockDocumentService.getOcrText.mockRejectedValue({ response: { status: 404, data: { error: 'Document not found' }, @@ -153,7 +152,7 @@ describe('documentService', () => { }); it('should handle 401 unauthorized errors', async () => { - mockGetOcrText.mockRejectedValue({ + mockDocumentService.getOcrText.mockRejectedValue({ response: { status: 401, data: { error: 'Unauthorized' }, @@ -209,7 +208,7 @@ describe('documentService', () => { config: {}, }; - mockList.mockResolvedValue(mockResponse); + mockDocumentService.list.mockResolvedValue(mockResponse); const result = await documentService.list(50, 0); @@ -236,24 +235,24 @@ describe('documentService', () => { ocr_status: 'pending', }; - mockUpload.mockResolvedValue({ data: mockUploadResponse }); + mockDocumentService.upload.mockResolvedValue({ data: mockUploadResponse }); const result = await documentService.upload(mockFile); expect(result.data).toEqual(mockUploadResponse); - expect(mockUpload).toHaveBeenCalledWith(mockFile); + expect(mockDocumentService.upload).toHaveBeenCalledWith(mockFile); }); }); describe('download', () => { it('should download file as blob', async () => { const mockBlob = new Blob(['file content'], { type: 'application/pdf' }); - mockDownload.mockResolvedValue({ data: mockBlob }); + mockDocumentService.download.mockResolvedValue({ data: mockBlob }); const result = await documentService.download('doc-123'); expect(result.data).toEqual(mockBlob); - expect(mockDownload).toHaveBeenCalledWith('doc-123'); + expect(mockDocumentService.download).toHaveBeenCalledWith('doc-123'); }); }); }); @@ -325,11 +324,11 @@ describe('documentService.deleteLowConfidence', () => { config: {}, }; - mockDeleteLowConfidence.mockResolvedValue(mockDeleteResponse); + mockDocumentService.deleteLowConfidence.mockResolvedValue(mockDeleteResponse); const result = await documentService.deleteLowConfidence(30.0, false); - expect(mockDeleteLowConfidence).toHaveBeenCalledWith(30.0, false); + expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(30.0, false); expect(result.data.success).toBe(true); expect(result.data.deleted_count).toBe(3); expect(result.data.matched_count).toBe(3); @@ -351,11 +350,11 @@ describe('documentService.deleteLowConfidence', () => { config: {}, }; - mockDeleteLowConfidence.mockResolvedValue(mockPreviewResponse); + mockDocumentService.deleteLowConfidence.mockResolvedValue(mockPreviewResponse); const result = await documentService.deleteLowConfidence(50.0, true); - expect(mockDeleteLowConfidence).toHaveBeenCalledWith(50.0, true); + expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(50.0, true); expect(result.data.success).toBe(true); expect(result.data.preview).toBe(true); expect(result.data.matched_count).toBe(5); @@ -376,11 +375,11 @@ describe('documentService.deleteLowConfidence', () => { config: {}, }; - mockDeleteLowConfidence.mockResolvedValue(mockEmptyResponse); + mockDocumentService.deleteLowConfidence.mockResolvedValue(mockEmptyResponse); const result = await documentService.deleteLowConfidence(10.0, false); - expect(mockDeleteLowConfidence).toHaveBeenCalledWith(10.0, false); + expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(10.0, false); expect(result.data.success).toBe(true); expect(result.data.deleted_count).toBe(0); }); @@ -398,23 +397,23 @@ describe('documentService.deleteLowConfidence', () => { config: {}, }; - mockDeleteLowConfidence.mockResolvedValue(mockErrorResponse); + mockDocumentService.deleteLowConfidence.mockResolvedValue(mockErrorResponse); const result = await documentService.deleteLowConfidence(-10.0, false); - expect(mockDeleteLowConfidence).toHaveBeenCalledWith(-10.0, false); + expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(-10.0, false); expect(result.data.success).toBe(false); expect(result.data.message).toContain('must be between 0.0 and 100.0'); }); it('should handle API errors gracefully', async () => { const mockError = new Error('Network error'); - mockDeleteLowConfidence.mockRejectedValue(mockError); + mockDocumentService.deleteLowConfidence.mockRejectedValue(mockError); await expect(documentService.deleteLowConfidence(30.0, false)) .rejects.toThrow('Network error'); - expect(mockDeleteLowConfidence).toHaveBeenCalledWith(30.0, false); + expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(30.0, false); }); it('should use correct default values', async () => { @@ -426,12 +425,12 @@ describe('documentService.deleteLowConfidence', () => { config: {}, }; - mockDeleteLowConfidence.mockResolvedValue(mockResponse); + mockDocumentService.deleteLowConfidence.mockResolvedValue(mockResponse); // Test with explicit false value (the default) await documentService.deleteLowConfidence(40.0, false); - expect(mockDeleteLowConfidence).toHaveBeenCalledWith(40.0, false); + expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(40.0, false); }); it('should handle partial deletion failures', async () => { @@ -452,7 +451,7 @@ describe('documentService.deleteLowConfidence', () => { config: {}, }; - mockDeleteLowConfidence.mockResolvedValue(mockPartialFailureResponse); + mockDocumentService.deleteLowConfidence.mockResolvedValue(mockPartialFailureResponse); const result = await documentService.deleteLowConfidence(25.0, false); @@ -472,15 +471,15 @@ describe('documentService.deleteLowConfidence', () => { config: {}, }; - mockDeleteLowConfidence.mockResolvedValue(mockResponse); + mockDocumentService.deleteLowConfidence.mockResolvedValue(mockResponse); // Test various confidence values const testValues = [0.0, 0.1, 30.5, 50.0, 99.9, 100.0]; for (const confidence of testValues) { - mockDeleteLowConfidence.mockClear(); + mockDocumentService.deleteLowConfidence.mockClear(); await documentService.deleteLowConfidence(confidence, true); - expect(mockDeleteLowConfidence).toHaveBeenCalledWith(confidence, true); + expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(confidence, true); } }); }); @@ -529,11 +528,11 @@ describe('documentService.getFailedOcrDocuments', () => { config: {}, }; - mockGetFailedOcrDocuments.mockResolvedValue(mockResponse); + mockDocumentService.getFailedOcrDocuments.mockResolvedValue(mockResponse); const result = await documentService.getFailedOcrDocuments(50, 0); - expect(mockGetFailedOcrDocuments).toHaveBeenCalledWith(50, 0); + expect(mockDocumentService.getFailedOcrDocuments).toHaveBeenCalledWith(50, 0); expect(result.data).toEqual(mockFailedOcrResponse); expect(result.data.documents).toHaveLength(2); expect(result.data.documents[0].failure_stage).toBe('ocr'); @@ -541,19 +540,19 @@ describe('documentService.getFailedOcrDocuments', () => { }); it('should handle pagination parameters correctly', async () => { - mockGetFailedOcrDocuments.mockResolvedValue({ data: mockFailedOcrResponse }); + mockDocumentService.getFailedOcrDocuments.mockResolvedValue({ data: mockFailedOcrResponse }); await documentService.getFailedOcrDocuments(25, 10); - expect(mockGetFailedOcrDocuments).toHaveBeenCalledWith(25, 10); + expect(mockDocumentService.getFailedOcrDocuments).toHaveBeenCalledWith(25, 10); }); it('should use default pagination when not specified', async () => { - mockGetFailedOcrDocuments.mockResolvedValue({ data: mockFailedOcrResponse }); + mockDocumentService.getFailedOcrDocuments.mockResolvedValue({ data: mockFailedOcrResponse }); await documentService.getFailedOcrDocuments(); - expect(mockGetFailedOcrDocuments).toHaveBeenCalledWith(); + expect(mockDocumentService.getFailedOcrDocuments).toHaveBeenCalledWith(); }); it('should handle empty results', async () => { @@ -563,7 +562,7 @@ describe('documentService.getFailedOcrDocuments', () => { statistics: { total_failed: 0, failure_categories: [] } }; - mockGetFailedOcrDocuments.mockResolvedValue({ data: emptyResponse }); + mockDocumentService.getFailedOcrDocuments.mockResolvedValue({ data: emptyResponse }); const result = await documentService.getFailedOcrDocuments(); @@ -574,7 +573,7 @@ describe('documentService.getFailedOcrDocuments', () => { it('should handle API errors', async () => { const mockError = new Error('Network error'); - mockGetFailedOcrDocuments.mockRejectedValue(mockError); + mockDocumentService.getFailedOcrDocuments.mockRejectedValue(mockError); await expect(documentService.getFailedOcrDocuments()).rejects.toThrow('Network error'); }); @@ -625,11 +624,11 @@ describe('documentService.getFailedDocuments', () => { }; it('should fetch failed documents with default parameters', async () => { - mockGetFailedDocuments.mockResolvedValue({ data: mockFailedDocumentsResponse }); + mockDocumentService.getFailedDocuments.mockResolvedValue({ data: mockFailedDocumentsResponse }); const result = await documentService.getFailedDocuments(); - expect(mockGetFailedDocuments).toHaveBeenCalledWith(); + expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(); expect(result.data).toEqual(mockFailedDocumentsResponse); expect(result.data.documents).toHaveLength(3); }); @@ -642,11 +641,11 @@ describe('documentService.getFailedDocuments', () => { statistics: { total_failed: 1, failure_categories: [{ reason: 'low_ocr_confidence', display_name: 'Low OCR Confidence', count: 1 }] } }; - mockGetFailedDocuments.mockResolvedValue({ data: ocrOnlyResponse }); + mockDocumentService.getFailedDocuments.mockResolvedValue({ data: ocrOnlyResponse }); const result = await documentService.getFailedDocuments(25, 0, 'ocr'); - expect(mockGetFailedDocuments).toHaveBeenCalledWith(25, 0, 'ocr'); + expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(25, 0, 'ocr'); expect(result.data.documents).toHaveLength(1); expect(result.data.documents[0].failure_stage).toBe('ocr'); }); @@ -659,11 +658,11 @@ describe('documentService.getFailedDocuments', () => { statistics: { total_failed: 1, failure_categories: [{ reason: 'duplicate_content', display_name: 'Duplicate Content', count: 1 }] } }; - mockGetFailedDocuments.mockResolvedValue({ data: duplicateOnlyResponse }); + mockDocumentService.getFailedDocuments.mockResolvedValue({ data: duplicateOnlyResponse }); const result = await documentService.getFailedDocuments(25, 0, undefined, 'duplicate_content'); - expect(mockGetFailedDocuments).toHaveBeenCalledWith(25, 0, undefined, 'duplicate_content'); + expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(25, 0, undefined, 'duplicate_content'); expect(result.data.documents).toHaveLength(1); expect(result.data.documents[0].failure_reason).toBe('duplicate_content'); }); @@ -676,22 +675,22 @@ describe('documentService.getFailedDocuments', () => { statistics: { total_failed: 1, failure_categories: [{ reason: 'low_ocr_confidence', display_name: 'Low OCR Confidence', count: 1 }] } }; - mockGetFailedDocuments.mockResolvedValue({ data: filteredResponse }); + mockDocumentService.getFailedDocuments.mockResolvedValue({ data: filteredResponse }); const result = await documentService.getFailedDocuments(25, 0, 'ocr', 'low_ocr_confidence'); - expect(mockGetFailedDocuments).toHaveBeenCalledWith(25, 0, 'ocr', 'low_ocr_confidence'); + expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(25, 0, 'ocr', 'low_ocr_confidence'); expect(result.data.documents).toHaveLength(1); expect(result.data.documents[0].failure_stage).toBe('ocr'); expect(result.data.documents[0].failure_reason).toBe('low_ocr_confidence'); }); it('should handle custom pagination', async () => { - mockGetFailedDocuments.mockResolvedValue({ data: mockFailedDocumentsResponse }); + mockDocumentService.getFailedDocuments.mockResolvedValue({ data: mockFailedDocumentsResponse }); await documentService.getFailedDocuments(10, 20); - expect(mockGetFailedDocuments).toHaveBeenCalledWith(10, 20); + expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(10, 20); }); it('should handle empty results', async () => { @@ -701,7 +700,7 @@ describe('documentService.getFailedDocuments', () => { statistics: { total_failed: 0, failure_categories: [] } }; - mockGetFailedDocuments.mockResolvedValue({ data: emptyResponse }); + mockDocumentService.getFailedDocuments.mockResolvedValue({ data: emptyResponse }); const result = await documentService.getFailedDocuments(); @@ -724,11 +723,11 @@ describe('documentService.retryOcr', () => { config: {}, }; - mockRetryOcr.mockResolvedValue(mockRetryResponse); + mockDocumentService.retryOcr.mockResolvedValue(mockRetryResponse); const result = await documentService.retryOcr('doc-123'); - expect(mockRetryOcr).toHaveBeenCalledWith('doc-123'); + expect(mockDocumentService.retryOcr).toHaveBeenCalledWith('doc-123'); expect(result.data.success).toBe(true); expect(result.data.document_id).toBe('doc-123'); }); @@ -741,7 +740,7 @@ describe('documentService.retryOcr', () => { } }; - mockRetryOcr.mockRejectedValue(mockError); + mockDocumentService.retryOcr.mockRejectedValue(mockError); await expect(documentService.retryOcr('non-existent-doc')).rejects.toMatchObject({ response: { status: 404 } @@ -749,7 +748,7 @@ describe('documentService.retryOcr', () => { }); it('should handle network errors', async () => { - mockRetryOcr.mockRejectedValue(new Error('Network error')); + mockDocumentService.retryOcr.mockRejectedValue(new Error('Network error')); await expect(documentService.retryOcr('doc-123')).rejects.toThrow('Network error'); }); diff --git a/frontend/src/test/comprehensive-mocks.ts b/frontend/src/test/comprehensive-mocks.ts new file mode 100644 index 0000000..95d6feb --- /dev/null +++ b/frontend/src/test/comprehensive-mocks.ts @@ -0,0 +1,129 @@ +// Comprehensive mocking utilities to prevent HTTP requests in tests +import { vi } from 'vitest'; + +/** + * Creates a comprehensive axios mock that prevents all HTTP requests + * This should be used in test files that have components making API calls + */ +export const createComprehensiveAxiosMock = () => { + const mockAxiosInstance = { + get: vi.fn().mockResolvedValue({ data: {} }), + post: vi.fn().mockResolvedValue({ data: { success: true } }), + put: vi.fn().mockResolvedValue({ data: { success: true } }), + delete: vi.fn().mockResolvedValue({ data: { success: true } }), + patch: vi.fn().mockResolvedValue({ data: { success: true } }), + request: vi.fn().mockResolvedValue({ data: { success: true } }), + head: vi.fn().mockResolvedValue({ data: {} }), + options: vi.fn().mockResolvedValue({ data: {} }), + defaults: { + headers: { + common: {}, + get: {}, + post: {}, + put: {}, + delete: {}, + patch: {}, + } + }, + interceptors: { + request: { use: vi.fn(), eject: vi.fn() }, + response: { use: vi.fn(), eject: vi.fn() }, + }, + }; + + return { + default: { + create: vi.fn(() => mockAxiosInstance), + ...mockAxiosInstance, + }, + }; +}; + +/** + * Creates comprehensive API service mocks + */ +export const createComprehensiveApiMocks = () => ({ + api: { + get: vi.fn().mockResolvedValue({ data: {} }), + post: vi.fn().mockResolvedValue({ data: { success: true } }), + put: vi.fn().mockResolvedValue({ data: { success: true } }), + delete: vi.fn().mockResolvedValue({ data: { success: true } }), + patch: vi.fn().mockResolvedValue({ data: { success: true } }), + defaults: { headers: { common: {} } }, + }, + documentService: { + getRetryRecommendations: vi.fn().mockResolvedValue({ + data: { recommendations: [], total_recommendations: 0 } + }), + bulkRetryOcr: vi.fn().mockResolvedValue({ data: { success: true } }), + getFailedDocuments: vi.fn().mockResolvedValue({ + data: { + documents: [], + pagination: { total: 0, limit: 25, offset: 0, total_pages: 1 }, + statistics: { total_failed: 0, by_reason: {}, by_stage: {} }, + }, + }), + getDuplicates: vi.fn().mockResolvedValue({ + data: { + duplicates: [], + pagination: { total: 0, limit: 25, offset: 0, has_more: false }, + statistics: { total_duplicate_groups: 0 }, + }, + }), + enhancedSearch: vi.fn().mockResolvedValue({ + data: { + documents: [], + total: 0, + query_time_ms: 0, + suggestions: [] + } + }), + search: vi.fn().mockResolvedValue({ + data: { + documents: [], + total: 0, + query_time_ms: 0, + suggestions: [] + } + }), + getOcrText: vi.fn().mockResolvedValue({ data: {} }), + upload: vi.fn().mockResolvedValue({ data: {} }), + list: vi.fn().mockResolvedValue({ data: [] }), + listWithPagination: vi.fn().mockResolvedValue({ + data: { + documents: [], + pagination: { total: 0, limit: 20, offset: 0, has_more: false } + } + }), + delete: vi.fn().mockResolvedValue({}), + bulkDelete: vi.fn().mockResolvedValue({}), + retryOcr: vi.fn().mockResolvedValue({}), + getFacets: vi.fn().mockResolvedValue({ data: { mime_types: [], tags: [] } }), + download: vi.fn().mockResolvedValue({ data: new Blob() }), + deleteLowConfidence: vi.fn().mockResolvedValue({ data: {} }), + deleteFailedOcr: vi.fn().mockResolvedValue({ data: {} }), + view: vi.fn().mockResolvedValue({ data: new Blob() }), + getThumbnail: vi.fn().mockResolvedValue({ data: new Blob() }), + getProcessedImage: vi.fn().mockResolvedValue({ data: new Blob() }), + downloadFile: vi.fn().mockResolvedValue(undefined), + getRetryStats: vi.fn().mockResolvedValue({ data: {} }), + getDocumentRetryHistory: vi.fn().mockResolvedValue({ data: [] }), + }, + queueService: { + getQueueStatus: vi.fn().mockResolvedValue({ data: { active: 0, waiting: 0 } }), + }, +}); + +/** + * Standard pattern for mocking both axios and API services + * Use this at the top of test files that have components making API calls + */ +export const setupHttpMocks = () => { + // Mock axios comprehensively + vi.mock('axios', () => createComprehensiveAxiosMock()); + + // Mock API services + const apiMocks = createComprehensiveApiMocks(); + + return apiMocks; +}; \ No newline at end of file diff --git a/frontend/src/test/label-test-utils.ts b/frontend/src/test/label-test-utils.ts index ffdd172..8c3ffcb 100644 --- a/frontend/src/test/label-test-utils.ts +++ b/frontend/src/test/label-test-utils.ts @@ -5,8 +5,11 @@ import { type LabelData } from '../components/Labels/Label'; * Test utilities for label-related tests */ +// Counter for generating unique IDs +let labelIdCounter = 1; + export const createMockLabel = (overrides: Partial = {}): LabelData => ({ - id: 'test-label-1', + id: `test-label-${labelIdCounter++}`, name: 'Test Label', description: 'A test label', color: '#0969da', @@ -21,12 +24,15 @@ export const createMockLabel = (overrides: Partial = {}): LabelData = }); export const createMockSystemLabel = (overrides: Partial = {}): LabelData => ({ - ...createMockLabel(), - id: 'system-label-1', + id: `system-label-${labelIdCounter++}`, name: 'Important', + description: 'A test label', color: '#d73a49', + background_color: undefined, icon: 'star', is_system: true, + created_at: '2024-01-01T00:00:00Z', + updated_at: '2024-01-01T00:00:00Z', document_count: 10, source_count: 2, ...overrides, @@ -213,12 +219,12 @@ export const testDataBuilders = { * Creates a set of labels that represent typical usage patterns */ createTypicalLabelSet: (): LabelData[] => [ - createMockSystemLabel({ name: 'Important', color: '#d73a49', icon: 'star' }), - createMockSystemLabel({ name: 'Work', color: '#0969da', icon: 'work' }), - createMockSystemLabel({ name: 'Personal', color: '#28a745', icon: 'person' }), - createMockLabel({ name: 'Project Alpha', color: '#8250df', icon: 'folder' }), - createMockLabel({ name: 'Invoices', color: '#fb8500', icon: 'receipt' }), - createMockLabel({ name: 'Archive', color: '#6e7781', icon: 'archive', document_count: 0 }), + createMockSystemLabel({ name: 'Important', description: 'High priority items', color: '#d73a49', icon: 'star' }), + createMockSystemLabel({ name: 'Work', description: 'Work-related documents', color: '#0969da', icon: 'work' }), + createMockSystemLabel({ name: 'Personal', description: 'Personal documents', color: '#28a745', icon: 'person' }), + createMockLabel({ name: 'Project Alpha', description: 'My personal project files', color: '#8250df', icon: 'folder' }), + createMockLabel({ name: 'Invoices', description: 'Financial documents', color: '#fb8500', icon: 'receipt' }), + createMockLabel({ name: 'Archive', description: 'Archived items', color: '#6e7781', icon: 'archive', document_count: 0 }), ], /** diff --git a/frontend/src/test/setup.ts b/frontend/src/test/setup.ts index 6518e4f..6bd4095 100644 --- a/frontend/src/test/setup.ts +++ b/frontend/src/test/setup.ts @@ -3,7 +3,7 @@ import '@testing-library/jest-dom' import { vi } from 'vitest' -import { setupTestEnvironment } from './test-utils' +import { setupTestEnvironment } from './test-utils.tsx' // Setup global test environment setupTestEnvironment() diff --git a/frontend/src/test/test-utils.tsx b/frontend/src/test/test-utils.tsx index bd854e5..643e21f 100644 --- a/frontend/src/test/test-utils.tsx +++ b/frontend/src/test/test-utils.tsx @@ -2,6 +2,7 @@ import React from 'react' import { render, RenderOptions } from '@testing-library/react' import { BrowserRouter } from 'react-router-dom' import { vi } from 'vitest' +import { NotificationProvider } from '../contexts/NotificationContext' interface User { id: string @@ -38,12 +39,46 @@ export const createMockAdminUser = (overrides: Partial = {}): User => ({ // Centralized API mocking to eliminate per-file duplication export const createMockApiServices = () => { const mockDocumentService = { - enhancedSearch: vi.fn().mockResolvedValue({ documents: [], total: 0 }), + enhancedSearch: vi.fn().mockResolvedValue({ + data: { + documents: [], + total: 0, + query_time_ms: 0, + suggestions: [] + } + }), + search: vi.fn().mockResolvedValue({ + data: { + documents: [], + total: 0, + query_time_ms: 0, + suggestions: [] + } + }), bulkRetryOcr: vi.fn().mockResolvedValue({ success: true }), getDocument: vi.fn().mockResolvedValue({}), - uploadDocument: vi.fn().mockResolvedValue({}), - deleteDocument: vi.fn().mockResolvedValue({}), - updateDocument: vi.fn().mockResolvedValue({}), + getById: vi.fn().mockResolvedValue({ data: {} }), + upload: vi.fn().mockResolvedValue({ data: {} }), + list: vi.fn().mockResolvedValue({ data: [] }), + listWithPagination: vi.fn().mockResolvedValue({ data: { documents: [], pagination: { total: 0, limit: 20, offset: 0, has_more: false } } }), + delete: vi.fn().mockResolvedValue({}), + bulkDelete: vi.fn().mockResolvedValue({}), + retryOcr: vi.fn().mockResolvedValue({}), + getFacets: vi.fn().mockResolvedValue({ data: { mime_types: [], tags: [] } }), + getOcrText: vi.fn().mockResolvedValue({ data: {} }), + download: vi.fn().mockResolvedValue({ data: new Blob() }), + getFailedOcrDocuments: vi.fn().mockResolvedValue({ data: [] }), + getFailedDocuments: vi.fn().mockResolvedValue({ data: [] }), + deleteLowConfidence: vi.fn().mockResolvedValue({ data: {} }), + deleteFailedOcr: vi.fn().mockResolvedValue({ data: {} }), + view: vi.fn().mockResolvedValue({ data: new Blob() }), + getThumbnail: vi.fn().mockResolvedValue({ data: new Blob() }), + getProcessedImage: vi.fn().mockResolvedValue({ data: new Blob() }), + downloadFile: vi.fn().mockResolvedValue(undefined), + getDuplicates: vi.fn().mockResolvedValue({ data: [] }), + getRetryStats: vi.fn().mockResolvedValue({ data: {} }), + getRetryRecommendations: vi.fn().mockResolvedValue({ data: [] }), + getDocumentRetryHistory: vi.fn().mockResolvedValue({ data: [] }), } const mockAuthService = { @@ -77,24 +112,11 @@ export const createMockApiServices = () => { } // Setup global API mocks (call this in setup files) +// Note: Individual test files should handle their own vi.mock() calls export const setupApiMocks = () => { - const mockServices = createMockApiServices() - - vi.mock('../../services/api', () => ({ - documentService: mockServices.documentService, - authService: mockServices.authService, - sourceService: mockServices.sourceService, - labelService: mockServices.labelService, - api: { - defaults: { - headers: { - common: {} - } - } - } - })) - - return mockServices + // Just return the mock services for use in tests + // The actual vi.mock() should be done in individual test files + return createMockApiServices() } // Create a mock AuthProvider for testing @@ -136,9 +158,11 @@ const AllTheProviders = ({ }) => { return ( - - {children} - + + + {children} + + ) }