feat(tests): try to deduplicate test code even more
This commit is contained in:
parent
7993786e18
commit
a3f49f9bd7
|
|
@ -1,12 +1,10 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
import { TEST_USERS, TIMEOUTS } from './utils/test-data';
|
||||
import { test, expect, AuthHelper, TEST_CREDENTIALS, TIMEOUTS } from './fixtures/auth';
|
||||
import { TestHelpers } from './utils/test-helpers';
|
||||
|
||||
test.describe('Authentication', () => {
|
||||
let helpers: TestHelpers;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
helpers = new TestHelpers(page);
|
||||
const authHelper = new AuthHelper(page);
|
||||
await authHelper.ensureLoggedOut();
|
||||
});
|
||||
|
||||
test('should display login form on initial visit', async ({ page }) => {
|
||||
|
|
@ -19,22 +17,12 @@ test.describe('Authentication', () => {
|
|||
});
|
||||
|
||||
test('should login with valid credentials', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const authHelper = new AuthHelper(page);
|
||||
|
||||
// Fill login form with demo credentials
|
||||
await page.fill('input[name="username"]', 'admin');
|
||||
await page.fill('input[name="password"]', 'readur2024');
|
||||
|
||||
// Wait for login API call
|
||||
const loginResponse = helpers.waitForApiCall('/auth/login');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Verify login was successful
|
||||
await loginResponse;
|
||||
await authHelper.loginAs(TEST_CREDENTIALS.admin);
|
||||
|
||||
// Should redirect to dashboard or main page
|
||||
await page.waitForURL(/\/dashboard|\//, { timeout: TIMEOUTS.medium });
|
||||
await page.waitForURL(/\/dashboard|\//, { timeout: TIMEOUTS.navigation });
|
||||
|
||||
// Verify we're no longer on login page
|
||||
await expect(page.locator('input[name="username"]')).not.toBeVisible();
|
||||
|
|
@ -49,20 +37,19 @@ test.describe('Authentication', () => {
|
|||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should show error message (Material-UI Alert)
|
||||
await expect(page.locator('.MuiAlert-root, [role="alert"]')).toBeVisible({ timeout: TIMEOUTS.short });
|
||||
await expect(page.locator('.MuiAlert-root, [role="alert"]')).toBeVisible({ timeout: TIMEOUTS.api });
|
||||
|
||||
// Should remain on login page
|
||||
await expect(page.locator('input[name="username"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test.skip('should logout successfully', async ({ page }) => {
|
||||
// First login
|
||||
await page.goto('/');
|
||||
await page.fill('input[name="username"]', 'admin');
|
||||
await page.fill('input[name="password"]', 'readur2024');
|
||||
await page.click('button[type="submit"]');
|
||||
const authHelper = new AuthHelper(page);
|
||||
|
||||
await page.waitForURL(/\/dashboard|\//, { timeout: TIMEOUTS.medium });
|
||||
// First login
|
||||
await authHelper.loginAs(TEST_CREDENTIALS.admin);
|
||||
|
||||
await page.waitForURL(/\/dashboard|\//, { timeout: TIMEOUTS.navigation });
|
||||
|
||||
// Find and click profile/account button in the top app bar (has AccountIcon)
|
||||
const profileButton = page.locator('button:has([data-testid="AccountCircleIcon"])');
|
||||
|
|
@ -73,18 +60,17 @@ test.describe('Authentication', () => {
|
|||
await logoutMenuItem.click();
|
||||
|
||||
// Should redirect back to login
|
||||
await page.waitForURL(/\/login|\//, { timeout: TIMEOUTS.medium });
|
||||
await page.waitForURL(/\/login|\//, { timeout: TIMEOUTS.navigation });
|
||||
await expect(page.locator('input[name="username"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test.skip('should persist session on page reload', async ({ page }) => {
|
||||
// Login first
|
||||
await page.goto('/');
|
||||
await page.fill('input[name="username"]', 'admin');
|
||||
await page.fill('input[name="password"]', 'readur2024');
|
||||
await page.click('button[type="submit"]');
|
||||
const authHelper = new AuthHelper(page);
|
||||
|
||||
await page.waitForURL(/\/dashboard|\//, { timeout: TIMEOUTS.medium });
|
||||
// Login first
|
||||
await authHelper.loginAs(TEST_CREDENTIALS.admin);
|
||||
|
||||
await page.waitForURL(/\/dashboard|\//, { timeout: TIMEOUTS.navigation });
|
||||
|
||||
// Reload the page
|
||||
await page.reload();
|
||||
|
|
@ -93,7 +79,7 @@ test.describe('Authentication', () => {
|
|||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Should still be logged in (either on dashboard or main page, but not login)
|
||||
await page.waitForURL(/\/dashboard|\/(?!login)/, { timeout: TIMEOUTS.medium });
|
||||
await page.waitForURL(/\/dashboard|\/(?!login)/, { timeout: TIMEOUTS.navigation });
|
||||
await expect(page.locator('input[name="username"]')).not.toBeVisible();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,18 +5,18 @@ import { TestHelpers } from './utils/test-helpers';
|
|||
test.describe('Source Management', () => {
|
||||
let helpers: TestHelpers;
|
||||
|
||||
test.beforeEach(async ({ authenticatedPage }) => {
|
||||
helpers = new TestHelpers(authenticatedPage);
|
||||
test.beforeEach(async ({ adminPage }) => {
|
||||
helpers = new TestHelpers(adminPage);
|
||||
await helpers.navigateToPage('/sources');
|
||||
});
|
||||
|
||||
test.skip('should display sources interface', async ({ authenticatedPage: page }) => {
|
||||
test.skip('should display sources interface', async ({ adminPage: page }) => {
|
||||
// Check for sources page components
|
||||
await expect(page.locator('[data-testid="sources-list"], .sources-list, .sources-container')).toBeVisible();
|
||||
await expect(page.locator('button:has-text("Add Source"), [data-testid="add-source"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test.skip('should create a new local folder source', async ({ authenticatedPage: page }) => {
|
||||
test.skip('should create a new local folder source', async ({ adminPage: page }) => {
|
||||
// Click add source button
|
||||
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ test.describe('Source Management', () => {
|
|||
await expect(page.locator(':has-text("Test Local Folder")')).toBeVisible({ timeout: TIMEOUTS.medium });
|
||||
});
|
||||
|
||||
test.skip('should create a new WebDAV source', async ({ authenticatedPage: page }) => {
|
||||
test.skip('should create a new WebDAV source', async ({ adminPage: page }) => {
|
||||
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
|
||||
|
||||
await expect(page.locator('[data-testid="add-source-form"], .add-source-modal, .source-form')).toBeVisible();
|
||||
|
|
@ -79,7 +79,7 @@ test.describe('Source Management', () => {
|
|||
await expect(page.locator(':has-text("Test WebDAV")')).toBeVisible({ timeout: TIMEOUTS.medium });
|
||||
});
|
||||
|
||||
test.skip('should create a new S3 source', async ({ authenticatedPage: page }) => {
|
||||
test.skip('should create a new S3 source', async ({ adminPage: page }) => {
|
||||
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
|
||||
|
||||
await expect(page.locator('[data-testid="add-source-form"], .add-source-modal, .source-form')).toBeVisible();
|
||||
|
|
@ -108,7 +108,7 @@ test.describe('Source Management', () => {
|
|||
await expect(page.locator(':has-text("Test S3 Bucket")')).toBeVisible({ timeout: TIMEOUTS.medium });
|
||||
});
|
||||
|
||||
test('should edit existing source', async ({ authenticatedPage: page }) => {
|
||||
test('should edit existing source', async ({ adminPage: page }) => {
|
||||
// Look for existing source to edit
|
||||
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('should delete source', async ({ authenticatedPage: page }) => {
|
||||
test('should delete source', async ({ adminPage: page }) => {
|
||||
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
|
||||
|
||||
if (await firstSource.isVisible()) {
|
||||
|
|
@ -168,7 +168,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test.skip('should start source sync', async ({ authenticatedPage: page }) => {
|
||||
test.skip('should start source sync', async ({ adminPage: page }) => {
|
||||
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
|
||||
|
||||
if (await firstSource.isVisible()) {
|
||||
|
|
@ -189,7 +189,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('should stop source sync', async ({ authenticatedPage: page }) => {
|
||||
test('should stop source sync', async ({ adminPage: page }) => {
|
||||
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
|
||||
|
||||
if (await firstSource.isVisible()) {
|
||||
|
|
@ -217,7 +217,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('should display source status and statistics', async ({ authenticatedPage: page }) => {
|
||||
test('should display source status and statistics', async ({ adminPage: page }) => {
|
||||
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
|
||||
|
||||
if (await firstSource.isVisible()) {
|
||||
|
|
@ -235,7 +235,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test.skip('should test source connection', async ({ authenticatedPage: page }) => {
|
||||
test.skip('should test source connection', async ({ adminPage: page }) => {
|
||||
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
|
||||
|
||||
await expect(page.locator('[data-testid="add-source-form"], .add-source-modal')).toBeVisible();
|
||||
|
|
@ -266,7 +266,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('should filter sources by type', async ({ authenticatedPage: page }) => {
|
||||
test('should filter sources by type', async ({ adminPage: page }) => {
|
||||
// Look for filter dropdown
|
||||
const filterDropdown = page.locator('[data-testid="source-filter"], select[name="filter"], .source-filter');
|
||||
if (await filterDropdown.isVisible()) {
|
||||
|
|
@ -282,7 +282,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('should display sync history', async ({ authenticatedPage: page }) => {
|
||||
test('should display sync history', async ({ adminPage: page }) => {
|
||||
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
|
||||
|
||||
if (await firstSource.isVisible()) {
|
||||
|
|
@ -297,7 +297,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test.skip('should validate required fields in source creation', async ({ authenticatedPage: page }) => {
|
||||
test.skip('should validate required fields in source creation', async ({ adminPage: page }) => {
|
||||
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
|
||||
|
||||
await expect(page.locator('[data-testid="add-source-form"], .add-source-modal')).toBeVisible();
|
||||
|
|
@ -316,7 +316,7 @@ test.describe('Source Management', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('should schedule automatic sync', async ({ authenticatedPage: page }) => {
|
||||
test('should schedule automatic sync', async ({ adminPage: page }) => {
|
||||
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
|
||||
|
||||
if (await firstSource.isVisible()) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { TEST_CREDENTIALS } from '../fixtures/auth';
|
||||
|
||||
export const TEST_USERS = {
|
||||
valid: {
|
||||
username: 'admin',
|
||||
password: 'readur2024'
|
||||
},
|
||||
valid: TEST_CREDENTIALS.admin,
|
||||
invalid: {
|
||||
username: 'invaliduser',
|
||||
password: 'wrongpassword'
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { vi } from 'vitest';
|
||||
import Login from '../Login';
|
||||
import { AuthProvider } from '../../../contexts/AuthContext';
|
||||
import { ThemeProvider } from '../../../contexts/ThemeContext';
|
||||
import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils';
|
||||
|
||||
// Mock the API
|
||||
vi.mock('../../../services/api', () => ({
|
||||
|
|
@ -27,15 +25,6 @@ vi.mock('react-router-dom', async () => {
|
|||
};
|
||||
});
|
||||
|
||||
// Mock localStorage
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: {
|
||||
setItem: vi.fn(),
|
||||
getItem: vi.fn(() => null),
|
||||
removeItem: vi.fn()
|
||||
}
|
||||
});
|
||||
|
||||
// Mock window.location
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
|
|
@ -44,58 +33,14 @@ Object.defineProperty(window, 'location', {
|
|||
writable: true
|
||||
});
|
||||
|
||||
|
||||
// Mock AuthContext
|
||||
const mockAuthContextValue = {
|
||||
user: null,
|
||||
loading: false,
|
||||
login: vi.fn(),
|
||||
register: vi.fn(),
|
||||
logout: vi.fn()
|
||||
};
|
||||
|
||||
const MockAuthProvider = ({ children }: { children: React.ReactNode }) => (
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
const MockThemeProvider = ({ children }: { children: React.ReactNode }) => (
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
describe('Login - OIDC Features', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Mock window.matchMedia
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
});
|
||||
setupTestEnvironment();
|
||||
});
|
||||
|
||||
const renderLogin = () => {
|
||||
return render(
|
||||
<BrowserRouter>
|
||||
<MockThemeProvider>
|
||||
<MockAuthProvider>
|
||||
<Login />
|
||||
</MockAuthProvider>
|
||||
</MockThemeProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
return renderWithProviders(<Login />);
|
||||
};
|
||||
|
||||
it('renders OIDC login button', () => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
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 { AuthProvider } from '../../../contexts/AuthContext';
|
||||
import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils';
|
||||
import { api } from '../../../services/api';
|
||||
|
||||
// Mock the API
|
||||
|
|
@ -28,15 +28,6 @@ vi.mock('react-router-dom', async () => {
|
|||
};
|
||||
});
|
||||
|
||||
// Mock localStorage
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: {
|
||||
setItem: vi.fn(),
|
||||
getItem: vi.fn(),
|
||||
removeItem: vi.fn()
|
||||
}
|
||||
});
|
||||
|
||||
// Mock window.location
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
|
|
@ -45,56 +36,24 @@ Object.defineProperty(window, 'location', {
|
|||
writable: true
|
||||
});
|
||||
|
||||
|
||||
// Mock AuthContext
|
||||
const mockAuthContextValue = {
|
||||
user: null,
|
||||
loading: false,
|
||||
login: vi.fn(),
|
||||
register: vi.fn(),
|
||||
logout: vi.fn()
|
||||
};
|
||||
|
||||
const MockAuthProvider = ({ children }: { children: React.ReactNode }) => (
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
describe('OidcCallback', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
setupTestEnvironment();
|
||||
window.location.href = '';
|
||||
// Clear API mocks
|
||||
(api.get as any).mockClear();
|
||||
// Reset API mocks to default implementation
|
||||
(api.get as any).mockResolvedValue({ data: { token: 'default-token' } });
|
||||
|
||||
// Mock window.matchMedia
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
const renderOidcCallback = (search = '') => {
|
||||
return render(
|
||||
return renderWithProviders(
|
||||
<MemoryRouter initialEntries={[`/auth/oidc/callback${search}`]}>
|
||||
<MockAuthProvider>
|
||||
<Routes>
|
||||
<Route path="/auth/oidc/callback" element={<OidcCallback />} />
|
||||
<Route path="/login" element={<div>Login Page</div>} />
|
||||
</Routes>
|
||||
</MockAuthProvider>
|
||||
<Routes>
|
||||
<Route path="/auth/oidc/callback" element={<OidcCallback />} />
|
||||
<Route path="/login" element={<div>Login Page</div>} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ 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 } from '../../../test/test-utils';
|
||||
import { renderWithProviders, createMockApiServices, setupTestEnvironment, createMockLocalStorage } from '../../../test/test-utils';
|
||||
|
||||
// Use centralized API mocking
|
||||
const mockServices = createMockApiServices();
|
||||
const mockDocumentService = mockServices.documentService;
|
||||
const localStorageMock = createMockLocalStorage();
|
||||
|
||||
// Mock useNavigate
|
||||
const mockNavigate = vi.fn();
|
||||
|
|
@ -43,6 +45,7 @@ const mockSearchResponse = {
|
|||
describe('GlobalSearchBar', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
setupTestEnvironment();
|
||||
localStorageMock.getItem.mockReturnValue(null);
|
||||
mockDocumentService.enhancedSearch.mockResolvedValue(mockSearchResponse);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,32 +1,24 @@
|
|||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import { screen, fireEvent } from '@testing-library/react';
|
||||
import Label, { type LabelData } from '../Label';
|
||||
import { renderWithProviders } from '../../../test/test-utils';
|
||||
import {
|
||||
createMockLabel,
|
||||
createMockSystemLabel,
|
||||
setupTestEnvironment
|
||||
} from '../../../test/label-test-utils';
|
||||
|
||||
const theme = createTheme();
|
||||
|
||||
const mockLabel: LabelData = {
|
||||
id: 'test-label-1',
|
||||
const mockLabel = createMockLabel({
|
||||
name: 'Test Label',
|
||||
description: 'A test label',
|
||||
color: '#ff0000',
|
||||
background_color: undefined,
|
||||
icon: 'star',
|
||||
is_system: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 5,
|
||||
source_count: 2,
|
||||
};
|
||||
});
|
||||
|
||||
const systemLabel: LabelData = {
|
||||
...mockLabel,
|
||||
id: 'system-label-1',
|
||||
const systemLabel = createMockSystemLabel({
|
||||
name: 'Important',
|
||||
color: '#d73a49',
|
||||
icon: 'star',
|
||||
is_system: true,
|
||||
};
|
||||
});
|
||||
|
||||
const renderLabel = (props: Partial<React.ComponentProps<typeof Label>> = {}) => {
|
||||
const defaultProps = {
|
||||
|
|
@ -34,14 +26,14 @@ const renderLabel = (props: Partial<React.ComponentProps<typeof Label>> = {}) =>
|
|||
...props,
|
||||
};
|
||||
|
||||
return render(
|
||||
<ThemeProvider theme={theme}>
|
||||
<Label {...defaultProps} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
return renderWithProviders(<Label {...defaultProps} />);
|
||||
};
|
||||
|
||||
describe('Label Component', () => {
|
||||
beforeEach(() => {
|
||||
setupTestEnvironment();
|
||||
});
|
||||
|
||||
describe('Basic Rendering', () => {
|
||||
test('should render label with name', () => {
|
||||
renderLabel();
|
||||
|
|
@ -116,7 +108,7 @@ describe('Label Component', () => {
|
|||
const labelElement = screen.getByText('Test Label').closest('.MuiChip-root');
|
||||
fireEvent.click(labelElement!);
|
||||
|
||||
expect(handleClick).toHaveBeenCalledWith('test-label-1');
|
||||
expect(handleClick).toHaveBeenCalledWith(mockLabel.id);
|
||||
});
|
||||
|
||||
test('should not call onClick when disabled', () => {
|
||||
|
|
@ -166,7 +158,7 @@ describe('Label Component', () => {
|
|||
const deleteButton = screen.getByTestId('CloseIcon');
|
||||
fireEvent.click(deleteButton);
|
||||
|
||||
expect(handleDelete).toHaveBeenCalledWith('test-label-1');
|
||||
expect(handleDelete).toHaveBeenCalledWith(mockLabel.id);
|
||||
});
|
||||
|
||||
test('should not call onDelete when disabled', () => {
|
||||
|
|
@ -196,7 +188,7 @@ describe('Label Component', () => {
|
|||
const deleteButton = screen.getByTestId('CloseIcon');
|
||||
fireEvent.click(deleteButton);
|
||||
|
||||
expect(handleDelete).toHaveBeenCalledWith('test-label-1');
|
||||
expect(handleDelete).toHaveBeenCalledWith(mockLabel.id);
|
||||
expect(handleClick).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -285,7 +277,7 @@ describe('Label Component', () => {
|
|||
|
||||
// Test that clicking still works (keyboard events are handled internally by Material-UI)
|
||||
fireEvent.click(labelElement!);
|
||||
expect(handleClick).toHaveBeenCalledWith('test-label-1');
|
||||
expect(handleClick).toHaveBeenCalledWith(mockLabel.id);
|
||||
});
|
||||
|
||||
test('should have proper disabled state attributes', () => {
|
||||
|
|
|
|||
|
|
@ -1,25 +1,22 @@
|
|||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
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: LabelData = {
|
||||
id: 'edit-label-1',
|
||||
const mockEditingLabel = createMockLabel({
|
||||
name: 'Existing Label',
|
||||
description: 'An existing label',
|
||||
color: '#ff0000',
|
||||
background_color: undefined,
|
||||
icon: 'star',
|
||||
is_system: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 5,
|
||||
source_count: 2,
|
||||
};
|
||||
});
|
||||
|
||||
const renderLabelCreateDialog = (props: Partial<React.ComponentProps<typeof LabelCreateDialog>> = {}) => {
|
||||
const defaultProps = {
|
||||
|
|
@ -29,17 +26,14 @@ const renderLabelCreateDialog = (props: Partial<React.ComponentProps<typeof Labe
|
|||
...props,
|
||||
};
|
||||
|
||||
return render(
|
||||
<ThemeProvider theme={theme}>
|
||||
<LabelCreateDialog {...defaultProps} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
return renderWithProviders(<LabelCreateDialog {...defaultProps} />);
|
||||
};
|
||||
|
||||
describe('LabelCreateDialog Component', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
setupTestEnvironment();
|
||||
user = userEvent.setup();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +1,15 @@
|
|||
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import LabelSelector from '../LabelSelector';
|
||||
import { type LabelData } from '../Label';
|
||||
import { renderWithProviders } from '../../../test/test-utils';
|
||||
import {
|
||||
setupTestEnvironment,
|
||||
testDataBuilders
|
||||
} from '../../../test/label-test-utils';
|
||||
|
||||
const theme = createTheme();
|
||||
|
||||
const mockLabels: LabelData[] = [
|
||||
{
|
||||
id: 'label-1',
|
||||
name: 'Important',
|
||||
description: 'High priority items',
|
||||
color: '#d73a49',
|
||||
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,
|
||||
},
|
||||
{
|
||||
id: 'label-2',
|
||||
name: 'Work',
|
||||
description: 'Work-related documents',
|
||||
color: '#0969da',
|
||||
icon: 'work',
|
||||
is_system: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 5,
|
||||
source_count: 1,
|
||||
},
|
||||
{
|
||||
id: 'label-3',
|
||||
name: 'Personal Project',
|
||||
description: 'My personal project files',
|
||||
color: '#28a745',
|
||||
icon: 'folder',
|
||||
is_system: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 3,
|
||||
source_count: 0,
|
||||
},
|
||||
];
|
||||
const mockLabels = testDataBuilders.createTypicalLabelSet();
|
||||
|
||||
const renderLabelSelector = (props: Partial<React.ComponentProps<typeof LabelSelector>> = {}) => {
|
||||
const defaultProps = {
|
||||
|
|
@ -54,17 +19,14 @@ const renderLabelSelector = (props: Partial<React.ComponentProps<typeof LabelSel
|
|||
...props,
|
||||
};
|
||||
|
||||
return render(
|
||||
<ThemeProvider theme={theme}>
|
||||
<LabelSelector {...defaultProps} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
return renderWithProviders(<LabelSelector {...defaultProps} />);
|
||||
};
|
||||
|
||||
describe('LabelSelector Component', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
setupTestEnvironment();
|
||||
user = userEvent.setup();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,25 @@
|
|||
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, fireEvent, act, waitFor } from '@testing-library/react';
|
||||
import { screen, fireEvent, act, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import UploadZone from '../UploadZone';
|
||||
import { NotificationProvider } from '../../../contexts/NotificationContext';
|
||||
import { renderWithProviders, setupTestEnvironment, createMockApiServices } from '../../../test/test-utils';
|
||||
import { createMockLabel } from '../../../test/label-test-utils';
|
||||
|
||||
// Mock axios directly
|
||||
// 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: [
|
||||
{
|
||||
id: 'mock-label-1',
|
||||
name: 'Test Label',
|
||||
description: 'A test label',
|
||||
color: '#0969da',
|
||||
icon: undefined,
|
||||
is_system: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 0,
|
||||
source_count: 0,
|
||||
}
|
||||
]
|
||||
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: {} }),
|
||||
|
|
@ -32,19 +28,6 @@ vi.mock('axios', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// Helper function to render with NotificationProvider
|
||||
const renderWithProvider = async (component: React.ReactElement) => {
|
||||
let renderResult;
|
||||
await act(async () => {
|
||||
renderResult = render(
|
||||
<NotificationProvider>
|
||||
{component}
|
||||
</NotificationProvider>
|
||||
);
|
||||
});
|
||||
return renderResult;
|
||||
};
|
||||
|
||||
const mockProps = {
|
||||
onUploadComplete: vi.fn(),
|
||||
};
|
||||
|
|
@ -54,6 +37,7 @@ 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) => {
|
||||
|
|
@ -70,7 +54,9 @@ describe('UploadZone', () => {
|
|||
});
|
||||
|
||||
test('renders upload zone with default text', async () => {
|
||||
await renderWithProvider(<UploadZone {...mockProps} />);
|
||||
await act(async () => {
|
||||
renderWithProviders(<UploadZone {...mockProps} />);
|
||||
});
|
||||
|
||||
// Wait for async operations to complete
|
||||
await waitFor(() => {
|
||||
|
|
|
|||
|
|
@ -1,74 +1,57 @@
|
|||
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { screen, waitFor, act } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import LabelsPage from '../LabelsPage';
|
||||
import * as useApiModule from '../../hooks/useApi';
|
||||
|
||||
const theme = createTheme();
|
||||
import { renderWithAuthenticatedUser, setupTestEnvironment } from '../../test/test-utils';\nimport { createMockLabel } from '../../test/label-test-utils';
|
||||
|
||||
const mockLabels = [
|
||||
{
|
||||
createMockLabel({
|
||||
id: 'label-1',
|
||||
name: 'Important',
|
||||
description: 'High priority items',
|
||||
color: '#d73a49',
|
||||
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,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createMockLabel({
|
||||
id: 'label-2',
|
||||
name: 'Work',
|
||||
description: 'Work-related documents',
|
||||
color: '#0969da',
|
||||
icon: 'work',
|
||||
is_system: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 5,
|
||||
source_count: 1,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createMockLabel({
|
||||
id: 'label-3',
|
||||
name: 'Personal Project',
|
||||
description: 'My personal project files',
|
||||
color: '#28a745',
|
||||
icon: 'folder',
|
||||
is_system: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 3,
|
||||
source_count: 0,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createMockLabel({
|
||||
id: 'label-4',
|
||||
name: 'Archive',
|
||||
description: 'Archived items',
|
||||
color: '#6e7781',
|
||||
icon: 'archive',
|
||||
is_system: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 0,
|
||||
source_count: 0,
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const renderLabelsPage = async () => {
|
||||
let renderResult;
|
||||
await act(async () => {
|
||||
renderResult = render(
|
||||
<BrowserRouter>
|
||||
<ThemeProvider theme={theme}>
|
||||
<LabelsPage />
|
||||
</ThemeProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
renderResult = renderWithAuthenticatedUser(<LabelsPage />);
|
||||
});
|
||||
return renderResult;
|
||||
};
|
||||
|
|
@ -83,6 +66,7 @@ describe('LabelsPage Component', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setupTestEnvironment();
|
||||
user = userEvent.setup();
|
||||
|
||||
mockApi = {
|
||||
|
|
@ -412,16 +396,14 @@ describe('LabelsPage Component', () => {
|
|||
});
|
||||
|
||||
test('should call API when creating new label', async () => {
|
||||
const newLabel = {
|
||||
const newLabel = createMockLabel({
|
||||
id: 'new-label',
|
||||
name: 'New Label',
|
||||
color: '#ff0000',
|
||||
is_system: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 0,
|
||||
source_count: 0,
|
||||
};
|
||||
});
|
||||
|
||||
mockApi.post.mockResolvedValue({ status: 201, data: newLabel });
|
||||
|
||||
|
|
@ -604,16 +586,14 @@ describe('LabelsPage Component', () => {
|
|||
|
||||
describe('Data Refresh', () => {
|
||||
test('should refresh labels after successful creation', async () => {
|
||||
const newLabel = {
|
||||
const newLabel = createMockLabel({
|
||||
id: 'new-label',
|
||||
name: 'New Label',
|
||||
color: '#ff0000',
|
||||
is_system: false,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
document_count: 0,
|
||||
source_count: 0,
|
||||
};
|
||||
});
|
||||
|
||||
mockApi.post.mockResolvedValue({ status: 201, data: newLabel });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { screen } from '@testing-library/react';
|
||||
import SearchPage from '../SearchPage';
|
||||
import { renderWithAuthenticatedUser, createMockUser, setupTestEnvironment } from '../../test/test-utils';
|
||||
|
||||
// Mock API functions
|
||||
vi.mock('../../services/api', () => ({
|
||||
|
|
@ -14,21 +14,14 @@ vi.mock('../../services/api', () => ({
|
|||
getSettings: vi.fn(() => Promise.resolve({})),
|
||||
}));
|
||||
|
||||
const SearchPageWrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
return <BrowserRouter>{children}</BrowserRouter>;
|
||||
};
|
||||
|
||||
describe('SearchPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
setupTestEnvironment();
|
||||
});
|
||||
|
||||
test('renders search page structure', () => {
|
||||
render(
|
||||
<SearchPageWrapper>
|
||||
<SearchPage />
|
||||
</SearchPageWrapper>
|
||||
);
|
||||
renderWithAuthenticatedUser(<SearchPage />);
|
||||
|
||||
// Check for page title
|
||||
expect(screen.getByText('Search Documents')).toBeInTheDocument();
|
||||
|
|
@ -38,11 +31,7 @@ describe('SearchPage', () => {
|
|||
});
|
||||
|
||||
test('renders search input', () => {
|
||||
render(
|
||||
<SearchPageWrapper>
|
||||
<SearchPage />
|
||||
</SearchPageWrapper>
|
||||
);
|
||||
renderWithAuthenticatedUser(<SearchPage />);
|
||||
|
||||
const searchInput = screen.getByPlaceholderText(/search/i);
|
||||
expect(searchInput).toBeInTheDocument();
|
||||
|
|
@ -142,11 +131,7 @@ describe('SearchPage', () => {
|
|||
// });
|
||||
|
||||
test('renders main search container', () => {
|
||||
const { container } = render(
|
||||
<SearchPageWrapper>
|
||||
<SearchPage />
|
||||
</SearchPageWrapper>
|
||||
);
|
||||
const { container } = renderWithAuthenticatedUser(<SearchPage />);
|
||||
|
||||
expect(container.firstChild).toBeInTheDocument();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,31 +1,17 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { type OcrResponse, type Document } from '../api';
|
||||
import { createMockApiServices, setupTestEnvironment } from '../../test/test-utils';
|
||||
|
||||
// Create mock functions for the documentService
|
||||
const mockGetOcrText = vi.fn();
|
||||
const mockList = vi.fn();
|
||||
const mockUpload = vi.fn();
|
||||
const mockDownload = vi.fn();
|
||||
const mockDeleteLowConfidence = vi.fn();
|
||||
const mockGetFailedOcrDocuments = vi.fn();
|
||||
const mockGetFailedDocuments = vi.fn();
|
||||
const mockRetryOcr = vi.fn();
|
||||
// Use centralized API mocking
|
||||
const mockServices = createMockApiServices();
|
||||
const mockDocumentService = mockServices.documentService;
|
||||
|
||||
// Mock the entire api module
|
||||
// Mock the entire api module with centralized mocks
|
||||
vi.mock('../api', async () => {
|
||||
const actual = await vi.importActual('../api');
|
||||
return {
|
||||
...actual,
|
||||
documentService: {
|
||||
getOcrText: mockGetOcrText,
|
||||
list: mockList,
|
||||
upload: mockUpload,
|
||||
download: mockDownload,
|
||||
deleteLowConfidence: mockDeleteLowConfidence,
|
||||
getFailedOcrDocuments: mockGetFailedOcrDocuments,
|
||||
getFailedDocuments: mockGetFailedDocuments,
|
||||
retryOcr: mockRetryOcr,
|
||||
},
|
||||
documentService: mockDocumentService,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -35,6 +21,7 @@ const { documentService } = await import('../api');
|
|||
describe('documentService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
setupTestEnvironment();
|
||||
});
|
||||
|
||||
describe('getOcrText', () => {
|
||||
|
|
@ -60,7 +47,7 @@ describe('documentService', () => {
|
|||
config: {},
|
||||
};
|
||||
|
||||
mockGetOcrText.mockResolvedValue(mockResponse);
|
||||
mockDocumentService.getOcrText.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await documentService.getOcrText('doc-123');
|
||||
|
||||
|
|
@ -94,7 +81,7 @@ describe('documentService', () => {
|
|||
config: {},
|
||||
};
|
||||
|
||||
mockGetOcrText.mockResolvedValue(mockResponse);
|
||||
mockDocumentService.getOcrText.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await documentService.getOcrText('doc-456');
|
||||
|
||||
|
|
@ -126,7 +113,7 @@ describe('documentService', () => {
|
|||
config: {},
|
||||
};
|
||||
|
||||
mockGetOcrText.mockResolvedValue(mockResponse);
|
||||
mockDocumentService.getOcrText.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await documentService.getOcrText('doc-789');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,10 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::db::Database;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::models::{CreateUser, Document, SearchRequest};
|
||||
use chrono::Utc;
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn create_test_db() -> Database {
|
||||
// Use an in-memory database URL for testing
|
||||
// This will require PostgreSQL to be running for integration tests
|
||||
let db_url = std::env::var("TEST_DATABASE_URL")
|
||||
.unwrap_or_else(|_| "postgresql://postgres:postgres@localhost:5432/readur_test".to_string());
|
||||
|
||||
let db = Database::new(&db_url).await.expect("Failed to connect to test database");
|
||||
|
||||
// Run migrations for test database
|
||||
db.migrate().await.expect("Failed to migrate test database");
|
||||
|
||||
db
|
||||
}
|
||||
|
||||
fn create_test_user_data(suffix: &str) -> CreateUser {
|
||||
CreateUser {
|
||||
username: format!("testuser_{}", suffix),
|
||||
|
|
@ -58,9 +44,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_create_user() {
|
||||
let db = create_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
let user_data = create_test_user_data("1");
|
||||
|
||||
let result = db.create_user(user_data).await;
|
||||
|
|
@ -68,15 +54,15 @@ mod tests {
|
|||
|
||||
let user = result.unwrap();
|
||||
assert_eq!(user.username, "testuser_1");
|
||||
assert_eq!(user.email, "test@example.com");
|
||||
assert_eq!(user.email, "test_1@example.com");
|
||||
assert!(user.password_hash.is_some());
|
||||
assert_ne!(user.password_hash.as_ref().unwrap(), "password123"); // Should be hashed
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_get_user_by_username() {
|
||||
let db = create_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
let user_data = create_test_user_data("1");
|
||||
|
||||
let created_user = db.create_user(user_data).await.unwrap();
|
||||
|
|
@ -93,9 +79,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_get_user_by_username_not_found() {
|
||||
let db = create_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
let result = db.get_user_by_username("nonexistent").await;
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -105,9 +91,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_create_document() {
|
||||
let db = create_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
let user_data = create_test_user_data("1");
|
||||
let user = db.create_user(user_data).await.unwrap();
|
||||
|
||||
|
|
@ -122,9 +108,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_get_documents_by_user() {
|
||||
let db = create_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
let user_data = create_test_user_data("1");
|
||||
let user = db.create_user(user_data).await.unwrap();
|
||||
|
||||
|
|
@ -142,9 +128,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_search_documents() {
|
||||
let db = create_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
let user_data = create_test_user_data("1");
|
||||
let user = db.create_user(user_data).await.unwrap();
|
||||
|
||||
|
|
@ -174,9 +160,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_update_document_ocr() {
|
||||
let db = create_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
let user_data = create_test_user_data("1");
|
||||
let user = db.create_user(user_data).await.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -330,100 +330,29 @@ mod tests {
|
|||
#[cfg(test)]
|
||||
mod document_deletion_tests {
|
||||
use super::*;
|
||||
use crate::db::Database;
|
||||
use crate::models::{UserRole, User, Document, AuthProvider};
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::models::{UserRole, User, Document, AuthProvider, CreateUser};
|
||||
use chrono::Utc;
|
||||
use sqlx::PgPool;
|
||||
use std::env;
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn create_test_db_pool() -> PgPool {
|
||||
let database_url = env::var("TEST_DATABASE_URL")
|
||||
.expect("TEST_DATABASE_URL must be set for database tests");
|
||||
PgPool::connect(&database_url)
|
||||
.await
|
||||
.expect("Failed to connect to test database")
|
||||
}
|
||||
|
||||
async fn create_test_user(pool: &PgPool, role: UserRole) -> User {
|
||||
let user_id = Uuid::new_v4();
|
||||
let user = User {
|
||||
id: user_id,
|
||||
username: format!("testuser_{}", user_id),
|
||||
email: format!("test_{}@example.com", user_id),
|
||||
password_hash: Some("hashed_password".to_string()),
|
||||
role,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
oidc_subject: None,
|
||||
oidc_issuer: None,
|
||||
oidc_email: None,
|
||||
auth_provider: AuthProvider::Local,
|
||||
};
|
||||
|
||||
// Insert user into database
|
||||
sqlx::query("INSERT INTO users (id, username, email, password_hash, role, created_at, updated_at, oidc_subject, oidc_issuer, oidc_email, auth_provider) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)")
|
||||
.bind(user.id)
|
||||
.bind(&user.username)
|
||||
.bind(&user.email)
|
||||
.bind(&user.password_hash)
|
||||
.bind(user.role.to_string())
|
||||
.bind(user.created_at)
|
||||
.bind(user.updated_at)
|
||||
.bind(&user.oidc_subject)
|
||||
.bind(&user.oidc_issuer)
|
||||
.bind(&user.oidc_email)
|
||||
.bind(user.auth_provider.to_string())
|
||||
.execute(pool)
|
||||
.await
|
||||
.expect("Failed to insert test user");
|
||||
|
||||
user
|
||||
}
|
||||
|
||||
async fn create_and_insert_test_document(pool: &PgPool, user_id: Uuid) -> Document {
|
||||
let document = super::create_test_document(user_id);
|
||||
|
||||
// Insert document into database
|
||||
sqlx::query("INSERT INTO documents (id, filename, original_filename, file_path, file_size, mime_type, content, ocr_text, ocr_confidence, ocr_word_count, ocr_processing_time_ms, ocr_status, ocr_error, ocr_completed_at, tags, created_at, updated_at, user_id, file_hash) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)")
|
||||
.bind(document.id)
|
||||
.bind(&document.filename)
|
||||
.bind(&document.original_filename)
|
||||
.bind(&document.file_path)
|
||||
.bind(document.file_size as i64)
|
||||
.bind(&document.mime_type)
|
||||
.bind(&document.content)
|
||||
.bind(&document.ocr_text)
|
||||
.bind(document.ocr_confidence)
|
||||
.bind(document.ocr_word_count.map(|x| x as i32))
|
||||
.bind(document.ocr_processing_time_ms.map(|x| x as i32))
|
||||
.bind(&document.ocr_status)
|
||||
.bind(&document.ocr_error)
|
||||
.bind(document.ocr_completed_at)
|
||||
.bind(&document.tags)
|
||||
.bind(document.created_at)
|
||||
.bind(document.updated_at)
|
||||
.bind(document.user_id)
|
||||
.bind(&document.file_hash)
|
||||
.execute(pool)
|
||||
.await
|
||||
.expect("Failed to insert test document");
|
||||
|
||||
document
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_delete_document_as_owner() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let documents_db = Database { pool: pool.clone() };
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
// Create test user and document
|
||||
let user = create_test_user(&pool, UserRole::User).await;
|
||||
let document = create_and_insert_test_document(&pool, user.id).await;
|
||||
let user_data = CreateUser {
|
||||
username: format!("testuser_{}", Uuid::new_v4()),
|
||||
email: format!("test_{}@example.com", Uuid::new_v4()),
|
||||
password: "password123".to_string(),
|
||||
role: Some(UserRole::User),
|
||||
};
|
||||
let user = db.create_user(user_data).await.expect("Failed to create user");
|
||||
let document = super::create_test_document(user.id);
|
||||
let document = db.create_document(document).await.expect("Failed to create document");
|
||||
|
||||
// Delete document as owner
|
||||
let result = documents_db
|
||||
let result = db
|
||||
.delete_document(document.id, user.id, user.role)
|
||||
.await
|
||||
.expect("Failed to delete document");
|
||||
|
|
@ -435,7 +364,7 @@ mod document_deletion_tests {
|
|||
assert_eq!(deleted_doc.user_id, user.id);
|
||||
|
||||
// Verify document no longer exists in database
|
||||
let found_doc = documents_db
|
||||
let found_doc = db
|
||||
.get_document_by_id(document.id, user.id, user.role)
|
||||
.await
|
||||
.expect("Database query failed");
|
||||
|
|
@ -443,20 +372,32 @@ mod document_deletion_tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_delete_document_as_admin() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let documents_db = Database { pool: pool.clone() };
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
// Create regular user and their document
|
||||
let user = create_test_user(&pool, UserRole::User).await;
|
||||
let document = create_and_insert_test_document(&pool, user.id).await;
|
||||
let user_data = CreateUser {
|
||||
username: format!("testuser_{}", Uuid::new_v4()),
|
||||
email: format!("test_{}@example.com", Uuid::new_v4()),
|
||||
password: "password123".to_string(),
|
||||
role: Some(UserRole::User),
|
||||
};
|
||||
let user = db.create_user(user_data).await.expect("Failed to create user");
|
||||
let document = super::create_test_document(user.id);
|
||||
let document = db.create_document(document).await.expect("Failed to create document");
|
||||
|
||||
// Create admin user
|
||||
let admin = create_test_user(&pool, UserRole::Admin).await;
|
||||
let admin_data = CreateUser {
|
||||
username: format!("adminuser_{}", Uuid::new_v4()),
|
||||
email: format!("admin_{}@example.com", Uuid::new_v4()),
|
||||
password: "adminpass123".to_string(),
|
||||
role: Some(UserRole::Admin),
|
||||
};
|
||||
let admin = db.create_user(admin_data).await.expect("Failed to create admin");
|
||||
|
||||
// Delete document as admin
|
||||
let result = documents_db
|
||||
let result = db
|
||||
.delete_document(document.id, admin.id, admin.role)
|
||||
.await
|
||||
.expect("Failed to delete document as admin");
|
||||
|
|
@ -469,20 +410,33 @@ mod document_deletion_tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_delete_document_unauthorized() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let documents_db = Database { pool: pool.clone() };
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
// Create two regular users
|
||||
let user1 = create_test_user(&pool, UserRole::User).await;
|
||||
let user2 = create_test_user(&pool, UserRole::User).await;
|
||||
let user1_data = CreateUser {
|
||||
username: format!("testuser1_{}", Uuid::new_v4()),
|
||||
email: format!("test1_{}@example.com", Uuid::new_v4()),
|
||||
password: "password123".to_string(),
|
||||
role: Some(UserRole::User),
|
||||
};
|
||||
let user1 = db.create_user(user1_data).await.expect("Failed to create user1");
|
||||
|
||||
let user2_data = CreateUser {
|
||||
username: format!("testuser2_{}", Uuid::new_v4()),
|
||||
email: format!("test2_{}@example.com", Uuid::new_v4()),
|
||||
password: "password123".to_string(),
|
||||
role: Some(UserRole::User),
|
||||
};
|
||||
let user2 = db.create_user(user2_data).await.expect("Failed to create user2");
|
||||
|
||||
// Create document owned by user1
|
||||
let document = create_and_insert_test_document(&pool, user1.id).await;
|
||||
let document = super::create_test_document(user1.id);
|
||||
let document = db.create_document(document).await.expect("Failed to create document");
|
||||
|
||||
// Try to delete document as user2 (should fail)
|
||||
let result = documents_db
|
||||
let result = db
|
||||
.delete_document(document.id, user2.id, user2.role)
|
||||
.await
|
||||
.expect("Database query failed");
|
||||
|
|
@ -491,7 +445,7 @@ mod document_deletion_tests {
|
|||
assert!(result.is_none());
|
||||
|
||||
// Verify document still exists
|
||||
let found_doc = documents_db
|
||||
let found_doc = db
|
||||
.get_document_by_id(document.id, user1.id, user1.role)
|
||||
.await
|
||||
.expect("Database query failed");
|
||||
|
|
@ -707,97 +661,27 @@ mod document_deletion_tests {
|
|||
#[cfg(test)]
|
||||
mod rbac_deletion_tests {
|
||||
use super::*;
|
||||
use crate::db::Database;
|
||||
use crate::models::{UserRole, User, Document, AuthProvider};
|
||||
use chrono::Utc;
|
||||
use sqlx::PgPool;
|
||||
use std::env;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::models::{UserRole, CreateUser};
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn create_test_db_pool() -> PgPool {
|
||||
let database_url = env::var("TEST_DATABASE_URL")
|
||||
.expect("TEST_DATABASE_URL must be set for database tests");
|
||||
PgPool::connect(&database_url)
|
||||
.await
|
||||
.expect("Failed to connect to test database")
|
||||
}
|
||||
|
||||
async fn create_test_user(pool: &PgPool, role: UserRole) -> User {
|
||||
let user_id = Uuid::new_v4();
|
||||
let user = User {
|
||||
id: user_id,
|
||||
username: format!("testuser_{}", user_id),
|
||||
email: format!("test_{}@example.com", user_id),
|
||||
password_hash: Some("hashed_password".to_string()),
|
||||
role,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
oidc_subject: None,
|
||||
oidc_issuer: None,
|
||||
oidc_email: None,
|
||||
auth_provider: AuthProvider::Local,
|
||||
};
|
||||
|
||||
sqlx::query("INSERT INTO users (id, username, email, password_hash, role, created_at, updated_at, oidc_subject, oidc_issuer, oidc_email, auth_provider) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)")
|
||||
.bind(user.id)
|
||||
.bind(&user.username)
|
||||
.bind(&user.email)
|
||||
.bind(&user.password_hash)
|
||||
.bind(user.role.to_string())
|
||||
.bind(user.created_at)
|
||||
.bind(user.updated_at)
|
||||
.bind(&user.oidc_subject)
|
||||
.bind(&user.oidc_issuer)
|
||||
.bind(&user.oidc_email)
|
||||
.bind(user.auth_provider.to_string())
|
||||
.execute(pool)
|
||||
.await
|
||||
.expect("Failed to insert test user");
|
||||
|
||||
user
|
||||
}
|
||||
|
||||
async fn create_and_insert_test_document(pool: &PgPool, user_id: Uuid) -> Document {
|
||||
let document = super::create_test_document(user_id);
|
||||
|
||||
sqlx::query("INSERT INTO documents (id, filename, original_filename, file_path, file_size, mime_type, content, ocr_text, ocr_confidence, ocr_word_count, ocr_processing_time_ms, ocr_status, ocr_error, ocr_completed_at, tags, created_at, updated_at, user_id, file_hash) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)")
|
||||
.bind(document.id)
|
||||
.bind(&document.filename)
|
||||
.bind(&document.original_filename)
|
||||
.bind(&document.file_path)
|
||||
.bind(document.file_size as i64)
|
||||
.bind(&document.mime_type)
|
||||
.bind(&document.content)
|
||||
.bind(&document.ocr_text)
|
||||
.bind(document.ocr_confidence)
|
||||
.bind(document.ocr_word_count.map(|x| x as i32))
|
||||
.bind(document.ocr_processing_time_ms.map(|x| x as i32))
|
||||
.bind(&document.ocr_status)
|
||||
.bind(&document.ocr_error)
|
||||
.bind(document.ocr_completed_at)
|
||||
.bind(&document.tags)
|
||||
.bind(document.created_at)
|
||||
.bind(document.updated_at)
|
||||
.bind(document.user_id)
|
||||
.bind(&document.file_hash)
|
||||
.execute(pool)
|
||||
.await
|
||||
.expect("Failed to insert test document");
|
||||
|
||||
document
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database"]
|
||||
async fn test_user_can_delete_own_document() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let documents_db = Database { pool: pool.clone() };
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
let user = create_test_user(&pool, UserRole::User).await;
|
||||
let document = create_and_insert_test_document(&pool, user.id).await;
|
||||
let user_data = CreateUser {
|
||||
username: format!("testuser_{}", Uuid::new_v4()),
|
||||
email: format!("test_{}@example.com", Uuid::new_v4()),
|
||||
password: "password123".to_string(),
|
||||
role: Some(UserRole::User),
|
||||
};
|
||||
let user = db.create_user(user_data).await.expect("Failed to create user");
|
||||
let document = super::create_test_document(user.id);
|
||||
let document = db.create_document(document).await.expect("Failed to create document");
|
||||
|
||||
// User should be able to delete their own document
|
||||
let result = documents_db
|
||||
let result = db
|
||||
.delete_document(document.id, user.id, user.role)
|
||||
.await
|
||||
.expect("Failed to delete document");
|
||||
|
|
|
|||
|
|
@ -901,22 +901,11 @@ mod tests {
|
|||
#[tokio::test]
|
||||
#[ignore = "Requires PostgreSQL database for integration testing"]
|
||||
async fn test_enhanced_search_integration() {
|
||||
// This would test the actual database integration
|
||||
// Similar to existing db_tests but for enhanced search
|
||||
let db_url = std::env::var("TEST_DATABASE_URL")
|
||||
.unwrap_or_else(|_| "postgresql://postgres:postgres@localhost:5432/readur_test".to_string());
|
||||
use crate::test_utils::{TestContext, TestAuthHelper};
|
||||
|
||||
let db = Database::new(&db_url).await.expect("Failed to connect to test database");
|
||||
db.migrate().await.expect("Failed to migrate test database");
|
||||
|
||||
// Create test user
|
||||
let user_data = CreateUser {
|
||||
username: "test_enhanced_search".to_string(),
|
||||
email: "enhanced@test.com".to_string(),
|
||||
password: "password123".to_string(),
|
||||
role: Some(crate::models::UserRole::User),
|
||||
};
|
||||
let user = db.create_user(user_data).await.unwrap();
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create test document with rich content
|
||||
let document = Document {
|
||||
|
|
@ -937,7 +926,7 @@ mod tests {
|
|||
tags: vec!["enhanced".to_string(), "search".to_string(), "test".to_string()],
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
user_id: user.id,
|
||||
user_id: user.user_response.id,
|
||||
file_hash: Some("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string()),
|
||||
original_created_at: None,
|
||||
original_modified_at: None,
|
||||
|
|
@ -946,7 +935,7 @@ mod tests {
|
|||
ocr_failure_reason: None,
|
||||
};
|
||||
|
||||
db.create_document(document).await.unwrap();
|
||||
ctx.state.db.create_document(document).await.unwrap();
|
||||
|
||||
// Test enhanced search with snippets
|
||||
let search_request = SearchRequest {
|
||||
|
|
@ -960,7 +949,7 @@ mod tests {
|
|||
search_mode: Some(SearchMode::Simple),
|
||||
};
|
||||
|
||||
let result = db.enhanced_search_documents(user.id, search_request).await;
|
||||
let result = ctx.state.db.enhanced_search_documents(user.user_response.id, search_request).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let (documents, total, query_time) = result.unwrap();
|
||||
|
|
|
|||
|
|
@ -6,63 +6,199 @@ mod tests {
|
|||
create_ignored_file_from_document
|
||||
};
|
||||
use crate::models::{CreateIgnoredFile, IgnoredFilesQuery, User, UserRole, Document, AuthProvider};
|
||||
use crate::test_utils::{TestContext, TestAuthHelper};
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
use sqlx::PgPool;
|
||||
use std::env;
|
||||
|
||||
async fn create_test_db_pool() -> PgPool {
|
||||
let database_url = env::var("TEST_DATABASE_URL")
|
||||
.or_else(|_| env::var("DATABASE_URL"))
|
||||
.unwrap_or_else(|_| {
|
||||
// Skip tests if no database URL is available
|
||||
println!("Skipping database tests: TEST_DATABASE_URL or DATABASE_URL not set");
|
||||
std::process::exit(0);
|
||||
});
|
||||
PgPool::connect(&database_url)
|
||||
.await
|
||||
.expect("Failed to connect to test database")
|
||||
}
|
||||
|
||||
async fn create_test_user(pool: &PgPool) -> User {
|
||||
let user_id = Uuid::new_v4();
|
||||
let user = User {
|
||||
id: user_id,
|
||||
username: format!("testuser_{}", user_id),
|
||||
email: format!("test_{}@example.com", user_id),
|
||||
password_hash: Some("hashed_password".to_string()),
|
||||
role: UserRole::User,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
oidc_subject: None,
|
||||
oidc_issuer: None,
|
||||
oidc_email: None,
|
||||
auth_provider: AuthProvider::Local,
|
||||
#[tokio::test]
|
||||
async fn test_create_ignored_file() {
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: "abc123".to_string(),
|
||||
filename: "test.pdf".to_string(),
|
||||
original_filename: "original_test.pdf".to_string(),
|
||||
file_path: "/path/to/test.pdf".to_string(),
|
||||
file_size: 1024,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some("/webdav/test.pdf".to_string()),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.user_response.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
sqlx::query("INSERT INTO users (id, username, email, password_hash, role, created_at, updated_at, oidc_subject, oidc_issuer, oidc_email, auth_provider) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)")
|
||||
.bind(user.id)
|
||||
.bind(&user.username)
|
||||
.bind(&user.email)
|
||||
.bind(&user.password_hash)
|
||||
.bind(user.role.to_string())
|
||||
.bind(user.created_at)
|
||||
.bind(user.updated_at)
|
||||
.bind(&user.oidc_subject)
|
||||
.bind(&user.oidc_issuer)
|
||||
.bind(&user.oidc_email)
|
||||
.bind(user.auth_provider.to_string())
|
||||
.execute(pool)
|
||||
.await
|
||||
.expect("Failed to insert test user");
|
||||
let result = create_ignored_file(&ctx.state.db.pool, ignored_file).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
user
|
||||
let created = result.unwrap();
|
||||
assert_eq!(created.file_hash, "abc123");
|
||||
assert_eq!(created.filename, "test.pdf");
|
||||
assert_eq!(created.ignored_by, user.user_response.id);
|
||||
assert_eq!(created.source_type, Some("webdav".to_string()));
|
||||
}
|
||||
|
||||
async fn create_test_document(pool: &PgPool, user_id: Uuid) -> Document {
|
||||
let document_id = Uuid::new_v4();
|
||||
let document = Document {
|
||||
id: document_id,
|
||||
#[tokio::test]
|
||||
async fn test_list_ignored_files() {
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create multiple ignored files
|
||||
for i in 0..3 {
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: format!("hash{}", i),
|
||||
filename: format!("test{}.pdf", i),
|
||||
original_filename: format!("original_test{}.pdf", i),
|
||||
file_path: format!("/path/to/test{}.pdf", i),
|
||||
file_size: 1024 * (i + 1) as i64,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some(format!("/webdav/test{}.pdf", i)),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.user_response.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
create_ignored_file(&ctx.state.db.pool, ignored_file).await.unwrap();
|
||||
}
|
||||
|
||||
let query = IgnoredFilesQuery {
|
||||
limit: Some(10),
|
||||
offset: Some(0),
|
||||
source_type: None,
|
||||
source_identifier: None,
|
||||
ignored_by: None,
|
||||
filename: None,
|
||||
};
|
||||
|
||||
let result = list_ignored_files(&ctx.state.db.pool, user.user_response.id, &query).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let ignored_files = result.unwrap();
|
||||
assert_eq!(ignored_files.len(), 3);
|
||||
assert!(ignored_files.iter().all(|f| f.ignored_by == user.user_response.id));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_ignored_file_by_id() {
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: "test_hash".to_string(),
|
||||
filename: "test.pdf".to_string(),
|
||||
original_filename: "original_test.pdf".to_string(),
|
||||
file_path: "/path/to/test.pdf".to_string(),
|
||||
file_size: 1024,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some("/webdav/test.pdf".to_string()),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.user_response.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
let created = create_ignored_file(&ctx.state.db.pool, ignored_file).await.unwrap();
|
||||
|
||||
let result = get_ignored_file_by_id(&ctx.state.db.pool, created.id, user.user_response.id).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let fetched = result.unwrap();
|
||||
assert!(fetched.is_some());
|
||||
|
||||
let fetched = fetched.unwrap();
|
||||
assert_eq!(fetched.id, created.id);
|
||||
assert_eq!(fetched.file_hash, "test_hash");
|
||||
assert_eq!(fetched.filename, "test.pdf");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_ignored_file() {
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: "test_hash".to_string(),
|
||||
filename: "test.pdf".to_string(),
|
||||
original_filename: "original_test.pdf".to_string(),
|
||||
file_path: "/path/to/test.pdf".to_string(),
|
||||
file_size: 1024,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some("/webdav/test.pdf".to_string()),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.user_response.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
let created = create_ignored_file(&ctx.state.db.pool, ignored_file).await.unwrap();
|
||||
|
||||
let result = delete_ignored_file(&ctx.state.db.pool, created.id, user.user_response.id).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
|
||||
// Verify it's deleted
|
||||
let fetched = get_ignored_file_by_id(&ctx.state.db.pool, created.id, user.user_response.id).await;
|
||||
assert!(fetched.is_ok());
|
||||
assert!(fetched.unwrap().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_is_file_ignored() {
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: "test_hash".to_string(),
|
||||
filename: "test.pdf".to_string(),
|
||||
original_filename: "original_test.pdf".to_string(),
|
||||
file_path: "/path/to/test.pdf".to_string(),
|
||||
file_size: 1024,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some("/webdav/test.pdf".to_string()),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.user_response.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
create_ignored_file(&ctx.state.db.pool, ignored_file).await.unwrap();
|
||||
|
||||
// Test with exact match
|
||||
let result = is_file_ignored(
|
||||
&ctx.state.db.pool,
|
||||
"test_hash",
|
||||
Some("webdav"),
|
||||
Some("/webdav/test.pdf")
|
||||
).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
|
||||
// Test with just hash
|
||||
let result = is_file_ignored(&ctx.state.db.pool, "test_hash", None, None).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
|
||||
// Test with non-existing hash
|
||||
let result = is_file_ignored(&ctx.state.db.pool, "non_existing", None, None).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(!result.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_ignored_file_from_document() {
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
let document = ctx.state.db.create_document(crate::models::Document {
|
||||
id: Uuid::new_v4(),
|
||||
filename: "test_document.pdf".to_string(),
|
||||
original_filename: "test_document.pdf".to_string(),
|
||||
file_path: "/uploads/test_document.pdf".to_string(),
|
||||
|
|
@ -79,228 +215,19 @@ mod tests {
|
|||
tags: vec!["test".to_string(), "document".to_string()],
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
user_id,
|
||||
user_id: user.user_response.id,
|
||||
file_hash: Some("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()),
|
||||
original_created_at: None,
|
||||
original_modified_at: None,
|
||||
source_metadata: None,
|
||||
ocr_retry_count: None,
|
||||
ocr_failure_reason: None,
|
||||
};
|
||||
|
||||
sqlx::query("INSERT INTO documents (id, filename, original_filename, file_path, file_size, mime_type, content, ocr_text, ocr_confidence, ocr_word_count, ocr_processing_time_ms, ocr_status, ocr_error, ocr_completed_at, tags, created_at, updated_at, user_id, file_hash) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)")
|
||||
.bind(document.id)
|
||||
.bind(&document.filename)
|
||||
.bind(&document.original_filename)
|
||||
.bind(&document.file_path)
|
||||
.bind(document.file_size as i64)
|
||||
.bind(&document.mime_type)
|
||||
.bind(&document.content)
|
||||
.bind(&document.ocr_text)
|
||||
.bind(document.ocr_confidence)
|
||||
.bind(document.ocr_word_count.map(|x| x as i32))
|
||||
.bind(document.ocr_processing_time_ms.map(|x| x as i32))
|
||||
.bind(&document.ocr_status)
|
||||
.bind(&document.ocr_error)
|
||||
.bind(document.ocr_completed_at)
|
||||
.bind(&document.tags)
|
||||
.bind(document.created_at)
|
||||
.bind(document.updated_at)
|
||||
.bind(document.user_id)
|
||||
.bind(&document.file_hash)
|
||||
.execute(pool)
|
||||
.await
|
||||
.expect("Failed to insert test document");
|
||||
|
||||
document
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_ignored_file() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let user = create_test_user(&pool).await;
|
||||
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: "abc123".to_string(),
|
||||
filename: "test.pdf".to_string(),
|
||||
original_filename: "original_test.pdf".to_string(),
|
||||
file_path: "/path/to/test.pdf".to_string(),
|
||||
file_size: 1024,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some("/webdav/test.pdf".to_string()),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
let result = create_ignored_file(&pool, ignored_file).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let created = result.unwrap();
|
||||
assert_eq!(created.file_hash, "abc123");
|
||||
assert_eq!(created.filename, "test.pdf");
|
||||
assert_eq!(created.ignored_by, user.id);
|
||||
assert_eq!(created.source_type, Some("webdav".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_ignored_files() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let user = create_test_user(&pool).await;
|
||||
|
||||
// Create multiple ignored files
|
||||
for i in 0..3 {
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: format!("hash{}", i),
|
||||
filename: format!("test{}.pdf", i),
|
||||
original_filename: format!("original_test{}.pdf", i),
|
||||
file_path: format!("/path/to/test{}.pdf", i),
|
||||
file_size: 1024 * (i + 1) as i64,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some(format!("/webdav/test{}.pdf", i)),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
create_ignored_file(&pool, ignored_file).await.unwrap();
|
||||
}
|
||||
|
||||
let query = IgnoredFilesQuery {
|
||||
limit: Some(10),
|
||||
offset: Some(0),
|
||||
source_type: None,
|
||||
source_identifier: None,
|
||||
ignored_by: None,
|
||||
filename: None,
|
||||
};
|
||||
|
||||
let result = list_ignored_files(&pool, user.id, &query).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let ignored_files = result.unwrap();
|
||||
assert_eq!(ignored_files.len(), 3);
|
||||
assert!(ignored_files.iter().all(|f| f.ignored_by == user.id));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_ignored_file_by_id() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let user = create_test_user(&pool).await;
|
||||
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: "test_hash".to_string(),
|
||||
filename: "test.pdf".to_string(),
|
||||
original_filename: "original_test.pdf".to_string(),
|
||||
file_path: "/path/to/test.pdf".to_string(),
|
||||
file_size: 1024,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some("/webdav/test.pdf".to_string()),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
let created = create_ignored_file(&pool, ignored_file).await.unwrap();
|
||||
|
||||
let result = get_ignored_file_by_id(&pool, created.id, user.id).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let fetched = result.unwrap();
|
||||
assert!(fetched.is_some());
|
||||
|
||||
let fetched = fetched.unwrap();
|
||||
assert_eq!(fetched.id, created.id);
|
||||
assert_eq!(fetched.file_hash, "test_hash");
|
||||
assert_eq!(fetched.filename, "test.pdf");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_ignored_file() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let user = create_test_user(&pool).await;
|
||||
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: "test_hash".to_string(),
|
||||
filename: "test.pdf".to_string(),
|
||||
original_filename: "original_test.pdf".to_string(),
|
||||
file_path: "/path/to/test.pdf".to_string(),
|
||||
file_size: 1024,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some("/webdav/test.pdf".to_string()),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
let created = create_ignored_file(&pool, ignored_file).await.unwrap();
|
||||
|
||||
let result = delete_ignored_file(&pool, created.id, user.id).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
|
||||
// Verify it's deleted
|
||||
let fetched = get_ignored_file_by_id(&pool, created.id, user.id).await;
|
||||
assert!(fetched.is_ok());
|
||||
assert!(fetched.unwrap().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_is_file_ignored() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let user = create_test_user(&pool).await;
|
||||
|
||||
let ignored_file = CreateIgnoredFile {
|
||||
file_hash: "test_hash".to_string(),
|
||||
filename: "test.pdf".to_string(),
|
||||
original_filename: "original_test.pdf".to_string(),
|
||||
file_path: "/path/to/test.pdf".to_string(),
|
||||
file_size: 1024,
|
||||
mime_type: "application/pdf".to_string(),
|
||||
source_type: Some("webdav".to_string()),
|
||||
source_path: Some("/webdav/test.pdf".to_string()),
|
||||
source_identifier: Some("webdav-server-1".to_string()),
|
||||
ignored_by: user.id,
|
||||
reason: Some("deleted by user".to_string()),
|
||||
};
|
||||
|
||||
create_ignored_file(&pool, ignored_file).await.unwrap();
|
||||
|
||||
// Test with exact match
|
||||
let result = is_file_ignored(
|
||||
&pool,
|
||||
"test_hash",
|
||||
Some("webdav"),
|
||||
Some("/webdav/test.pdf")
|
||||
).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
|
||||
// Test with just hash
|
||||
let result = is_file_ignored(&pool, "test_hash", None, None).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap());
|
||||
|
||||
// Test with non-existing hash
|
||||
let result = is_file_ignored(&pool, "non_existing", None, None).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(!result.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_ignored_file_from_document() {
|
||||
let pool = create_test_db_pool().await;
|
||||
let user = create_test_user(&pool).await;
|
||||
let document = create_test_document(&pool, user.id).await;
|
||||
}).await.unwrap();
|
||||
|
||||
let result = create_ignored_file_from_document(
|
||||
&pool,
|
||||
&ctx.state.db.pool,
|
||||
document.id,
|
||||
user.id,
|
||||
user.user_response.id,
|
||||
Some("deleted by user".to_string()),
|
||||
Some("webdav".to_string()),
|
||||
Some("/webdav/test.pdf".to_string()),
|
||||
|
|
@ -315,7 +242,7 @@ mod tests {
|
|||
assert_eq!(ignored_file.filename, document.filename);
|
||||
assert_eq!(ignored_file.file_size, document.file_size);
|
||||
assert_eq!(ignored_file.mime_type, document.mime_type);
|
||||
assert_eq!(ignored_file.ignored_by, user.id);
|
||||
assert_eq!(ignored_file.ignored_by, user.user_response.id);
|
||||
assert_eq!(ignored_file.source_type, Some("webdav".to_string()));
|
||||
assert_eq!(ignored_file.reason, Some("deleted by user".to_string()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,79 +3,20 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::models::UserRole;
|
||||
use crate::routes::labels::{CreateLabel, UpdateLabel, LabelAssignment, Label};
|
||||
use crate::test_utils::{TestContext, TestAuthHelper};
|
||||
use axum::http::StatusCode;
|
||||
use chrono::Utc;
|
||||
use serde_json::json;
|
||||
use sqlx::{PgPool, Row};
|
||||
use sqlx::Row;
|
||||
use std::collections::HashMap;
|
||||
use testcontainers::{runners::AsyncRunner, ContainerAsync};
|
||||
use testcontainers_modules::postgres::Postgres;
|
||||
use uuid::Uuid;
|
||||
|
||||
struct TestContext {
|
||||
db: PgPool,
|
||||
_container: ContainerAsync<Postgres>,
|
||||
user_id: Uuid,
|
||||
admin_user_id: Uuid,
|
||||
}
|
||||
|
||||
async fn setup_test_db() -> TestContext {
|
||||
// Start PostgreSQL container
|
||||
let postgres_image = Postgres::default();
|
||||
let container = postgres_image.start().await.expect("Failed to start postgres container");
|
||||
let port = container.get_host_port_ipv4(5432).await.expect("Failed to get postgres port");
|
||||
|
||||
let connection_string = format!(
|
||||
"postgres://postgres:postgres@127.0.0.1:{}/postgres",
|
||||
port
|
||||
);
|
||||
|
||||
// Connect to database
|
||||
let db = PgPool::connect(&connection_string)
|
||||
.await
|
||||
.expect("Failed to connect to test database");
|
||||
|
||||
// Enable required extensions
|
||||
sqlx::query("CREATE EXTENSION IF NOT EXISTS pgcrypto;")
|
||||
.execute(&db)
|
||||
.await
|
||||
.expect("Failed to create pgcrypto extension");
|
||||
|
||||
// Run migrations
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&db)
|
||||
.await
|
||||
.expect("Failed to run migrations");
|
||||
|
||||
// Create test users
|
||||
let user_id = Uuid::new_v4();
|
||||
let admin_user_id = Uuid::new_v4();
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO users (id, username, email, password_hash, role, created_at, updated_at)
|
||||
VALUES
|
||||
($1, 'testuser', 'test@example.com', 'hashed_password', 'user', NOW(), NOW()),
|
||||
($2, 'admin', 'admin@example.com', 'hashed_password', 'admin', NOW(), NOW())
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(admin_user_id)
|
||||
.execute(&db)
|
||||
.await
|
||||
.expect("Failed to create test users");
|
||||
|
||||
TestContext {
|
||||
db,
|
||||
_container: container,
|
||||
user_id,
|
||||
admin_user_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_label_success() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
let label_data = CreateLabel {
|
||||
name: "Test Label".to_string(),
|
||||
|
|
@ -92,12 +33,12 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind(&label_data.name)
|
||||
.bind(&label_data.description)
|
||||
.bind(&label_data.color)
|
||||
.bind(&label_data.icon)
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -108,7 +49,7 @@ mod tests {
|
|||
"SELECT id, user_id, name, description, color, background_color, icon, is_system, created_at, updated_at, 0::bigint as document_count, 0::bigint as source_count FROM labels WHERE id = $1"
|
||||
)
|
||||
.bind(label_id)
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to fetch created label");
|
||||
|
||||
|
|
@ -116,13 +57,15 @@ mod tests {
|
|||
assert_eq!(created_label.description.as_ref().unwrap(), "A test label");
|
||||
assert_eq!(created_label.color, "#ff0000");
|
||||
assert_eq!(created_label.icon.as_ref().unwrap(), "star");
|
||||
assert_eq!(created_label.user_id, Some(ctx.user_id));
|
||||
assert_eq!(created_label.user_id, Some(user.user_response.id));
|
||||
assert!(!created_label.is_system);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_label_duplicate_name_fails() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create first label
|
||||
sqlx::query(
|
||||
|
|
@ -131,10 +74,10 @@ mod tests {
|
|||
VALUES ($1, $2, $3)
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("Duplicate Name")
|
||||
.bind("#ff0000")
|
||||
.execute(&ctx.db)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to create first label");
|
||||
|
||||
|
|
@ -145,10 +88,10 @@ mod tests {
|
|||
VALUES ($1, $2, $3)
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("Duplicate Name")
|
||||
.bind("#00ff00")
|
||||
.execute(&ctx.db)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
|
|
@ -157,7 +100,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_update_label_success() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create label
|
||||
let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
|
||||
|
|
@ -167,10 +112,10 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("Original Name")
|
||||
.bind("#ff0000")
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -201,8 +146,8 @@ mod tests {
|
|||
.bind(&update_data.description)
|
||||
.bind(&update_data.color)
|
||||
.bind(&update_data.icon)
|
||||
.bind(ctx.user_id)
|
||||
.fetch_one(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -216,7 +161,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_delete_label_success() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create label
|
||||
let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
|
||||
|
|
@ -226,10 +173,10 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("To Delete")
|
||||
.bind("#ff0000")
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -238,8 +185,8 @@ mod tests {
|
|||
"DELETE FROM labels WHERE id = $1 AND user_id = $2 AND is_system = FALSE"
|
||||
)
|
||||
.bind(label_id)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -250,7 +197,7 @@ mod tests {
|
|||
"SELECT id FROM labels WHERE id = $1"
|
||||
)
|
||||
.bind(label_id)
|
||||
.fetch_optional(&ctx.db)
|
||||
.fetch_optional(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Query failed");
|
||||
|
||||
|
|
@ -259,7 +206,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_cannot_delete_system_label() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create system label
|
||||
let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
|
||||
|
|
@ -273,7 +222,7 @@ mod tests {
|
|||
.bind("System Label")
|
||||
.bind("#ff0000")
|
||||
.bind(true)
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -282,8 +231,8 @@ mod tests {
|
|||
"DELETE FROM labels WHERE id = $1 AND user_id = $2 AND is_system = FALSE"
|
||||
)
|
||||
.bind(label_id)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -294,7 +243,7 @@ mod tests {
|
|||
"SELECT id FROM labels WHERE id = $1"
|
||||
)
|
||||
.bind(label_id)
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(system_label.is_ok());
|
||||
|
|
@ -302,7 +251,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_document_label_assignment() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create document
|
||||
let document_id = Uuid::new_v4();
|
||||
|
|
@ -316,13 +267,13 @@ mod tests {
|
|||
"#,
|
||||
)
|
||||
.bind(document_id)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("test.txt")
|
||||
.bind("test.txt")
|
||||
.bind("/test/test.txt")
|
||||
.bind(1024)
|
||||
.bind("text/plain")
|
||||
.execute(&ctx.db)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to create test document");
|
||||
|
||||
|
|
@ -334,10 +285,10 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("Document Label")
|
||||
.bind("#ff0000")
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -350,8 +301,8 @@ mod tests {
|
|||
)
|
||||
.bind(document_id)
|
||||
.bind(label_id)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -367,7 +318,7 @@ mod tests {
|
|||
)
|
||||
.bind(document_id)
|
||||
.bind(label_id)
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(assignment.is_ok());
|
||||
|
|
@ -375,12 +326,14 @@ mod tests {
|
|||
let label_name: String = assignment.get("label_name");
|
||||
let assigned_by: Option<uuid::Uuid> = assignment.get("assigned_by");
|
||||
assert_eq!(label_name, "Document Label");
|
||||
assert_eq!(assigned_by.unwrap(), ctx.user_id);
|
||||
assert_eq!(assigned_by.unwrap(), user.user_response.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_document_label_removal() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create document and label
|
||||
let document_id = Uuid::new_v4();
|
||||
|
|
@ -394,13 +347,13 @@ mod tests {
|
|||
"#,
|
||||
)
|
||||
.bind(document_id)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("test.txt")
|
||||
.bind("test.txt")
|
||||
.bind("/test/test.txt")
|
||||
.bind(1024)
|
||||
.bind("text/plain")
|
||||
.execute(&ctx.db)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to create test document");
|
||||
|
||||
|
|
@ -411,10 +364,10 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("Document Label")
|
||||
.bind("#ff0000")
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -427,8 +380,8 @@ mod tests {
|
|||
)
|
||||
.bind(document_id)
|
||||
.bind(label_id)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to assign label");
|
||||
|
||||
|
|
@ -438,7 +391,7 @@ mod tests {
|
|||
)
|
||||
.bind(document_id)
|
||||
.bind(label_id)
|
||||
.execute(&ctx.db)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
|
@ -450,7 +403,7 @@ mod tests {
|
|||
)
|
||||
.bind(document_id)
|
||||
.bind(label_id)
|
||||
.fetch_optional(&ctx.db)
|
||||
.fetch_optional(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Query failed");
|
||||
|
||||
|
|
@ -459,7 +412,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_get_document_labels() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create document
|
||||
let document_id = Uuid::new_v4();
|
||||
|
|
@ -473,13 +428,13 @@ mod tests {
|
|||
"#,
|
||||
)
|
||||
.bind(document_id)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind("test.txt")
|
||||
.bind("test.txt")
|
||||
.bind("/test/test.txt")
|
||||
.bind(1024)
|
||||
.bind("text/plain")
|
||||
.execute(&ctx.db)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to create test document");
|
||||
|
||||
|
|
@ -493,10 +448,10 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind(name)
|
||||
.bind(format!("#ff{:02x}00", i * 50))
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
label_ids.push(label_id);
|
||||
|
|
@ -512,8 +467,8 @@ mod tests {
|
|||
)
|
||||
.bind(document_id)
|
||||
.bind(label_id)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to assign label");
|
||||
}
|
||||
|
|
@ -529,7 +484,7 @@ mod tests {
|
|||
"#,
|
||||
)
|
||||
.bind(document_id)
|
||||
.fetch_all(&ctx.db)
|
||||
.fetch_all(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to fetch document labels");
|
||||
|
||||
|
|
@ -544,7 +499,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_label_usage_counts() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create label
|
||||
let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
|
||||
|
|
@ -554,8 +511,8 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.fetch_one(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -573,10 +530,10 @@ mod tests {
|
|||
"#,
|
||||
)
|
||||
.bind(doc_id)
|
||||
.bind(ctx.user_id)
|
||||
.bind(user.user_response.id)
|
||||
.bind(format!("test{}.txt", i))
|
||||
.bind(format!("/test/test{}.txt", i))
|
||||
.execute(&ctx.db)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to create test document");
|
||||
document_ids.push(doc_id);
|
||||
|
|
@ -592,8 +549,8 @@ mod tests {
|
|||
)
|
||||
.bind(doc_id)
|
||||
.bind(label_id)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to assign label");
|
||||
}
|
||||
|
|
@ -612,7 +569,7 @@ mod tests {
|
|||
"#,
|
||||
)
|
||||
.bind(label_id)
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to get usage count");
|
||||
|
||||
|
|
@ -622,7 +579,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_label_color_validation() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Test valid color
|
||||
let valid_result = sqlx::query(
|
||||
|
|
@ -632,8 +591,8 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(valid_result.is_ok());
|
||||
|
|
@ -644,13 +603,13 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_system_labels_migration() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
|
||||
// Check that system labels were created by migration
|
||||
let system_labels = sqlx::query(
|
||||
"SELECT name FROM labels WHERE is_system = TRUE ORDER BY name"
|
||||
)
|
||||
.fetch_all(&ctx.db)
|
||||
.fetch_all(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to fetch system labels");
|
||||
|
||||
|
|
@ -675,7 +634,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_cascade_delete_on_document_removal() {
|
||||
let ctx = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create document and label
|
||||
let document_id = Uuid::new_v4();
|
||||
|
|
@ -689,8 +650,8 @@ mod tests {
|
|||
"#,
|
||||
)
|
||||
.bind(document_id)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to create test document");
|
||||
|
||||
|
|
@ -701,8 +662,8 @@ mod tests {
|
|||
RETURNING id
|
||||
"#,
|
||||
)
|
||||
.bind(ctx.user_id)
|
||||
.fetch_one(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -715,8 +676,8 @@ mod tests {
|
|||
)
|
||||
.bind(document_id)
|
||||
.bind(label_id)
|
||||
.bind(ctx.user_id)
|
||||
.execute(&ctx.db)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to assign label");
|
||||
|
||||
|
|
@ -725,7 +686,7 @@ mod tests {
|
|||
"DELETE FROM documents WHERE id = $1"
|
||||
)
|
||||
.bind(document_id)
|
||||
.execute(&ctx.db)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to delete document");
|
||||
|
||||
|
|
@ -734,7 +695,7 @@ mod tests {
|
|||
"SELECT document_id FROM document_labels WHERE document_id = $1"
|
||||
)
|
||||
.bind(document_id)
|
||||
.fetch_all(&ctx.db)
|
||||
.fetch_all(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Query failed");
|
||||
|
||||
|
|
@ -745,7 +706,7 @@ mod tests {
|
|||
"SELECT id FROM labels WHERE id = $1"
|
||||
)
|
||||
.bind(label_id)
|
||||
.fetch_one(&ctx.db)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await;
|
||||
|
||||
assert!(label.is_ok());
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::UpdateSettings;
|
||||
use crate::test_utils::{create_test_app, create_test_user, login_user};
|
||||
use crate::test_utils::{TestContext, TestAuthHelper};
|
||||
use axum::http::StatusCode;
|
||||
use serde_json::json;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_settings_default() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let user = create_test_user(&app).await;
|
||||
let token = login_user(&app, &user.username, "password123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
let token = auth_helper.login_user(&user.username, "password123").await;
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
|
|
@ -40,9 +41,10 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_update_settings() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let user = create_test_user(&app).await;
|
||||
let token = login_user(&app, &user.username, "password123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
let token = auth_helper.login_user(&user.username, "password123").await;
|
||||
|
||||
let update_data = UpdateSettings {
|
||||
ocr_language: Some("spa".to_string()),
|
||||
|
|
@ -96,7 +98,7 @@ mod tests {
|
|||
webdav_sync_interval_minutes: None,
|
||||
};
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.clone()
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
|
|
@ -117,7 +119,7 @@ mod tests {
|
|||
|
||||
if status == StatusCode::OK {
|
||||
// Verify the update
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
|
|
@ -140,11 +142,12 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_settings_isolated_per_user() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
|
||||
// Create two users
|
||||
let user1 = create_test_user(&app).await;
|
||||
let token1 = login_user(&app, &user1.username, "password123").await;
|
||||
let user1 = auth_helper.create_test_user().await;
|
||||
let token1 = auth_helper.login_user(&user1.username, "password123").await;
|
||||
|
||||
let user2_data = json!({
|
||||
"username": "testuser2",
|
||||
|
|
@ -152,7 +155,7 @@ mod tests {
|
|||
"password": "password456"
|
||||
});
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.clone()
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
|
|
@ -166,7 +169,7 @@ mod tests {
|
|||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let token2 = login_user(&app, "testuser2", "password456").await;
|
||||
let token2 = auth_helper.login_user("testuser2", "password456").await;
|
||||
|
||||
// Update user1's settings
|
||||
let update_data = UpdateSettings {
|
||||
|
|
@ -221,7 +224,7 @@ mod tests {
|
|||
webdav_sync_interval_minutes: None,
|
||||
};
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.clone()
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
|
|
@ -242,7 +245,7 @@ mod tests {
|
|||
|
||||
if status == StatusCode::OK {
|
||||
// Check user2's settings are still default
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
|
|
@ -267,9 +270,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_settings_requires_auth() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let ctx = TestContext::new().await;
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
|
|
|
|||
|
|
@ -1,53 +1,30 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::db::ocr_retry::*;
|
||||
use sqlx::{PgPool, Row};
|
||||
use testcontainers::{runners::AsyncRunner, ContainerAsync};
|
||||
use testcontainers_modules::postgres::Postgres;
|
||||
use crate::test_utils::{TestContext, TestAuthHelper};
|
||||
use sqlx::Row;
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn setup_test_db() -> (ContainerAsync<Postgres>, PgPool) {
|
||||
let postgres_image = Postgres::default();
|
||||
let container = postgres_image.start().await.expect("Failed to start postgres container");
|
||||
let port = container.get_host_port_ipv4(5432).await.expect("Failed to get postgres port");
|
||||
|
||||
let connection_string = format!(
|
||||
"postgres://postgres:postgres@127.0.0.1:{}/postgres",
|
||||
port
|
||||
);
|
||||
|
||||
let pool = PgPool::connect(&connection_string).await.expect("Failed to connect to test database");
|
||||
sqlx::migrate!("./migrations").run(&pool).await.expect("Failed to run migrations");
|
||||
|
||||
(container, pool)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_simple_retry_record() {
|
||||
let (_container, pool) = setup_test_db().await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
// Create a simple test document entry first
|
||||
// Create a test document using the TestContext database
|
||||
let doc_id = Uuid::new_v4();
|
||||
let user_id = Uuid::new_v4();
|
||||
|
||||
sqlx::query("INSERT INTO users (id, username, email, password_hash) VALUES ($1, 'test', 'test@test.com', 'test')")
|
||||
.bind(user_id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.expect("Failed to create test user");
|
||||
|
||||
sqlx::query("INSERT INTO documents (id, filename, original_filename, user_id, mime_type, file_size, created_at, updated_at) VALUES ($1, 'test.pdf', 'test.pdf', $2, 'application/pdf', 1024, NOW(), NOW())")
|
||||
sqlx::query("INSERT INTO documents (id, filename, original_filename, user_id, mime_type, file_size, created_at, updated_at, file_path) VALUES ($1, 'test.pdf', 'test.pdf', $2, 'application/pdf', 1024, NOW(), NOW(), '/test/test.pdf')")
|
||||
.bind(doc_id)
|
||||
.bind(user_id)
|
||||
.execute(&pool)
|
||||
.bind(user.user_response.id)
|
||||
.execute(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to create test document");
|
||||
|
||||
// Test the record_ocr_retry function
|
||||
let retry_id = record_ocr_retry(
|
||||
&pool,
|
||||
&ctx.state.db.pool,
|
||||
doc_id,
|
||||
user_id,
|
||||
user.user_response.id,
|
||||
"manual_retry",
|
||||
10,
|
||||
None,
|
||||
|
|
@ -56,7 +33,7 @@ mod tests {
|
|||
// Verify the retry was recorded
|
||||
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM ocr_retry_history WHERE id = $1")
|
||||
.bind(retry_id)
|
||||
.fetch_one(&pool)
|
||||
.fetch_one(&ctx.state.db.pool)
|
||||
.await
|
||||
.expect("Failed to count retries");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::{CreateUser, UpdateUser, UserResponse, AuthProvider, UserRole};
|
||||
use crate::test_utils::{create_test_app, create_test_user, create_admin_user, login_user};
|
||||
use crate::test_utils::{TestContext, TestAuthHelper};
|
||||
use axum::http::StatusCode;
|
||||
use serde_json::json;
|
||||
use tower::util::ServiceExt;
|
||||
|
|
@ -9,9 +9,10 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_list_users() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let admin = create_admin_user(&app).await;
|
||||
let token = login_user(&app, &admin.username, "adminpass123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let admin = auth_helper.create_admin_user().await;
|
||||
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||
|
||||
// Create another user
|
||||
let user2_data = json!({
|
||||
|
|
@ -20,7 +21,7 @@ mod tests {
|
|||
"password": "password456"
|
||||
});
|
||||
|
||||
app.clone()
|
||||
ctx.app.clone()
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("POST")
|
||||
|
|
@ -32,7 +33,7 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
|
|
@ -58,15 +59,16 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_get_user_by_id() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let admin = create_admin_user(&app).await;
|
||||
let token = login_user(&app, &admin.username, "adminpass123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let admin = auth_helper.create_admin_user().await;
|
||||
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
.uri(format!("/api/users/{}", admin.id))
|
||||
.uri(format!("/api/users/{}", admin.id()))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.body(axum::body::Body::empty())
|
||||
.unwrap(),
|
||||
|
|
@ -81,16 +83,17 @@ mod tests {
|
|||
.unwrap();
|
||||
let fetched_user: UserResponse = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
assert_eq!(fetched_user.id, admin.id);
|
||||
assert_eq!(fetched_user.id.to_string(), admin.id());
|
||||
assert_eq!(fetched_user.username, admin.username);
|
||||
assert_eq!(fetched_user.email, admin.email);
|
||||
assert_eq!(fetched_user.email, admin.user_response.email);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_user_via_api() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let admin = create_admin_user(&app).await;
|
||||
let token = login_user(&app, &admin.username, "adminpass123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let admin = auth_helper.create_admin_user().await;
|
||||
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||
|
||||
let new_user_data = CreateUser {
|
||||
username: "newuser".to_string(),
|
||||
|
|
@ -99,7 +102,7 @@ mod tests {
|
|||
role: Some(crate::models::UserRole::User),
|
||||
};
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("POST")
|
||||
|
|
@ -125,12 +128,13 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_update_user() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let admin = create_admin_user(&app).await;
|
||||
let token = login_user(&app, &admin.username, "adminpass123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let admin = auth_helper.create_admin_user().await;
|
||||
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||
|
||||
// Create a regular user to update
|
||||
let user = create_test_user(&app).await;
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
let update_data = UpdateUser {
|
||||
username: Some("updateduser".to_string()),
|
||||
|
|
@ -138,11 +142,11 @@ mod tests {
|
|||
password: None,
|
||||
};
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("PUT")
|
||||
.uri(format!("/api/users/{}", user.id))
|
||||
.uri(format!("/api/users/{}", user.id()))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.header("Content-Type", "application/json")
|
||||
.body(axum::body::Body::from(serde_json::to_vec(&update_data).unwrap()))
|
||||
|
|
@ -164,12 +168,13 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_update_user_password() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let admin = create_admin_user(&app).await;
|
||||
let token = login_user(&app, &admin.username, "adminpass123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let admin = auth_helper.create_admin_user().await;
|
||||
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||
|
||||
// Create a regular user to update
|
||||
let user = create_test_user(&app).await;
|
||||
let user = auth_helper.create_test_user().await;
|
||||
|
||||
let update_data = UpdateUser {
|
||||
username: None,
|
||||
|
|
@ -177,12 +182,12 @@ mod tests {
|
|||
password: Some("newpassword456".to_string()),
|
||||
};
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.clone()
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("PUT")
|
||||
.uri(format!("/api/users/{}", user.id))
|
||||
.uri(format!("/api/users/{}", user.id()))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.header("Content-Type", "application/json")
|
||||
.body(axum::body::Body::from(serde_json::to_vec(&update_data).unwrap()))
|
||||
|
|
@ -194,15 +199,16 @@ mod tests {
|
|||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
// Verify new password works
|
||||
let new_token = login_user(&app, "testuser", "newpassword456").await;
|
||||
let new_token = auth_helper.login_user("testuser", "newpassword456").await;
|
||||
assert!(!new_token.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_user() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let admin = create_admin_user(&app).await;
|
||||
let token = login_user(&app, &admin.username, "adminpass123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let admin = auth_helper.create_admin_user().await;
|
||||
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||
|
||||
// Create another user to delete
|
||||
let user2_data = json!({
|
||||
|
|
@ -211,7 +217,7 @@ mod tests {
|
|||
"password": "password456"
|
||||
});
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.clone()
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
|
|
@ -230,7 +236,7 @@ mod tests {
|
|||
let user2: UserResponse = serde_json::from_slice(&body).unwrap();
|
||||
|
||||
// Delete the user
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.clone()
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
|
|
@ -246,7 +252,7 @@ mod tests {
|
|||
assert_eq!(response.status(), StatusCode::NO_CONTENT);
|
||||
|
||||
// Verify user is deleted
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
|
|
@ -263,15 +269,16 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_cannot_delete_self() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let admin = create_admin_user(&app).await;
|
||||
let token = login_user(&app, &admin.username, "adminpass123").await;
|
||||
let ctx = TestContext::new().await;
|
||||
let auth_helper = TestAuthHelper::new(ctx.app.clone());
|
||||
let admin = auth_helper.create_admin_user().await;
|
||||
let token = auth_helper.login_user(&admin.username, "adminpass123").await;
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("DELETE")
|
||||
.uri(format!("/api/users/{}", admin.id))
|
||||
.uri(format!("/api/users/{}", admin.id()))
|
||||
.header("Authorization", format!("Bearer {}", token))
|
||||
.body(axum::body::Body::empty())
|
||||
.unwrap(),
|
||||
|
|
@ -284,9 +291,9 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_users_require_auth() {
|
||||
let (app, _container) = create_test_app().await;
|
||||
let ctx = TestContext::new().await;
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("GET")
|
||||
|
|
@ -303,10 +310,8 @@ mod tests {
|
|||
// OIDC Database Tests
|
||||
#[tokio::test]
|
||||
async fn test_create_oidc_user() {
|
||||
let (_app, container) = create_test_app().await;
|
||||
let port = container.get_host_port_ipv4(5432).await.unwrap();
|
||||
let database_url = format!("postgresql://test:test@localhost:{}/test", port);
|
||||
let db = crate::db::Database::new(&database_url).await.unwrap();
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
// Generate random identifiers to avoid test interference
|
||||
let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string();
|
||||
|
|
@ -339,10 +344,8 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_get_user_by_oidc_subject() {
|
||||
let (_app, container) = create_test_app().await;
|
||||
let port = container.get_host_port_ipv4(5432).await.unwrap();
|
||||
let database_url = format!("postgresql://test:test@localhost:{}/test", port);
|
||||
let db = crate::db::Database::new(&database_url).await.unwrap();
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
// Generate random identifiers to avoid test interference
|
||||
let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string();
|
||||
|
|
@ -379,10 +382,8 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_get_user_by_oidc_subject_not_found() {
|
||||
let (_app, container) = create_test_app().await;
|
||||
let port = container.get_host_port_ipv4(5432).await.unwrap();
|
||||
let database_url = format!("postgresql://test:test@localhost:{}/test", port);
|
||||
let db = crate::db::Database::new(&database_url).await.unwrap();
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
// Generate random subject that definitely doesn't exist
|
||||
let test_id = uuid::Uuid::new_v4().to_string();
|
||||
|
|
@ -398,10 +399,8 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_oidc_user_different_issuer() {
|
||||
let (_app, container) = create_test_app().await;
|
||||
let port = container.get_host_port_ipv4(5432).await.unwrap();
|
||||
let database_url = format!("postgresql://test:test@localhost:{}/test", port);
|
||||
let db = crate::db::Database::new(&database_url).await.unwrap();
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
// Generate random identifiers to avoid test interference
|
||||
let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string();
|
||||
|
|
@ -435,10 +434,8 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_local_user_login_works() {
|
||||
let (app, container) = create_test_app().await;
|
||||
let port = container.get_host_port_ipv4(5432).await.unwrap();
|
||||
let database_url = format!("postgresql://test:test@localhost:{}/test", port);
|
||||
let db = crate::db::Database::new(&database_url).await.unwrap();
|
||||
let ctx = TestContext::new().await;
|
||||
let db = &ctx.state.db;
|
||||
|
||||
// Create regular local user
|
||||
let create_user = CreateUser {
|
||||
|
|
@ -460,7 +457,7 @@ mod tests {
|
|||
"password": "password123"
|
||||
});
|
||||
|
||||
let response = app
|
||||
let response = ctx.app
|
||||
.oneshot(
|
||||
axum::http::Request::builder()
|
||||
.method("POST")
|
||||
|
|
|
|||
Loading…
Reference in New Issue