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');
});
});
});