fix(tests): resolve broken test utils

This commit is contained in:
perf3ct 2025-07-04 00:31:53 +00:00
parent a3f49f9bd7
commit 51fb3a7e48
21 changed files with 1644 additions and 527 deletions

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
Final script to fix all remaining issues in documents_tests.rs
"""
import re
import sys
def fix_documents_tests(content):
"""Fix all remaining issues in documents_tests.rs"""
# Fix 1: Replace user.id() with user.user_response.id (for TestUser objects)
# This converts String to Uuid properly
content = re.sub(r'(\w+)\.id\(\)', r'\1.user_response.id', content)
# Fix 2: Replace user.role with user.user_response.role (for TestUser objects)
content = re.sub(r'(\w+)\.role\b', r'\1.user_response.role', content)
# Fix 3: Replace create_test_admin() with create_admin_user()
content = re.sub(r'\.create_test_admin\(\)', '.create_admin_user()', content)
# Fix 4: Fix document.id() back to document.id (documents don't have id() method)
content = re.sub(r'(doc\w*|document\w*|result\[\d+\]|deleted_doc|found_doc\.unwrap\(\))\.user_response\.id\b', r'\1.id', content)
# Fix 5: Fix response.id() to response.id for DocumentResponse
content = re.sub(r'response\.user_response\.id\b', 'response.id', content)
# Fix 6: Fix any standalone .user_response.id calls that shouldn't be there
content = re.sub(r'\.user_response\.id\(\)', '.user_response.id', content)
# Fix 7: Fix doubled "user_response" patterns
content = re.sub(r'\.user_response\.user_response\.', '.user_response.', content)
return content
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
# Read the file
try:
with open(file_path, 'r') as f:
content = f.read()
except FileNotFoundError:
print(f"Error: Could not find file {file_path}")
return 1
# Apply fixes
print("Applying final fixes to documents_tests.rs...")
fixed_content = fix_documents_tests(content)
# Write back the fixed content
try:
with open(file_path, 'w') as f:
f.write(fixed_content)
print(f"Successfully applied fixes to {file_path}")
return 0
except Exception as e:
print(f"Error writing file: {e}")
return 1
if __name__ == '__main__':
sys.exit(main())

59
fix_models_user.py Normal file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""
Fix models::User objects that were incorrectly converted to use .user_response
"""
import re
import sys
def fix_models_user(content):
"""Fix models::User objects that were incorrectly converted"""
# Find all lines that create models::User objects via db.create_user()
# and track the variable names
user_vars = set()
lines = content.split('\n')
for line in lines:
if 'db.create_user(' in line and 'await' in line:
# This creates a models::User object
match = re.search(r'let (\w+) = .*db\.create_user\(', line)
if match:
user_vars.add(match.group(1))
# Now fix all references to these variables
for var in user_vars:
# Revert .user_response.id back to .id
content = content.replace(f'{var}.user_response.id', f'{var}.id')
# Revert .user_response.role back to .role
content = content.replace(f'{var}.user_response.role', f'{var}.role')
return content
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
# Read the file
try:
with open(file_path, 'r') as f:
content = f.read()
except FileNotFoundError:
print(f"Error: Could not find file {file_path}")
return 1
# Apply fixes
print("Fixing models::User objects...")
fixed_content = fix_models_user(content)
# Write back the fixed content
try:
with open(file_path, 'w') as f:
f.write(fixed_content)
print(f"Successfully fixed {file_path}")
return 0
except Exception as e:
print(f"Error writing file: {e}")
return 1
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""
Final comprehensive fix for TestUser vs models::User distinction
"""
import re
import sys
def fix_user_object_types(content):
"""Fix the distinction between TestUser and models::User objects"""
lines = content.split('\n')
fixed_lines = []
# Track which variables are TestUser vs User objects
testuser_vars = set()
user_vars = set()
for i, line in enumerate(lines):
# Identify TestUser variables (created by auth_helper methods)
if re.search(r'let (\w+) = auth_helper\.create_test_user\(\)', line):
var_name = re.search(r'let (\w+) = auth_helper\.create_test_user\(\)', line).group(1)
testuser_vars.add(var_name)
elif re.search(r'let (\w+) = auth_helper\.create_admin_user\(\)', line):
var_name = re.search(r'let (\w+) = auth_helper\.create_admin_user\(\)', line).group(1)
testuser_vars.add(var_name)
elif re.search(r'let (\w+) = auth_helper\.create_test_admin\(\)', line):
var_name = re.search(r'let (\w+) = auth_helper\.create_test_admin\(\)', line).group(1)
testuser_vars.add(var_name)
# Identify models::User variables (created by db.create_user)
elif re.search(r'let (\w+) = .*db\.create_user\(', line):
var_name = re.search(r'let (\w+) = .*db\.create_user\(', line).group(1)
user_vars.add(var_name)
# Fix the line based on variable types
fixed_line = line
# For TestUser objects, ensure they use .user_response
for var in testuser_vars:
# Convert .id to .user_response.id for TestUser objects
fixed_line = re.sub(rf'\b{var}\.id\b', f'{var}.user_response.id', fixed_line)
# Convert .role to .user_response.role for TestUser objects
fixed_line = re.sub(rf'\b{var}\.role\b', f'{var}.user_response.role', fixed_line)
# For models::User objects, ensure they use direct access
for var in user_vars:
# Remove .user_response for User objects
fixed_line = re.sub(rf'\b{var}\.user_response\.id\b', f'{var}.id', fixed_line)
fixed_line = re.sub(rf'\b{var}\.user_response\.role\b', f'{var}.role', fixed_line)
fixed_lines.append(fixed_line)
return '\n'.join(fixed_lines)
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
# Read the file
try:
with open(file_path, 'r') as f:
content = f.read()
except FileNotFoundError:
print(f"Error: Could not find file {file_path}")
return 1
# Apply fixes
print("Applying comprehensive TestUser vs User fixes...")
fixed_content = fix_user_object_types(content)
# Write back the fixed content
try:
with open(file_path, 'w') as f:
f.write(fixed_content)
print(f"Successfully fixed {file_path}")
return 0
except Exception as e:
print(f"Error writing file: {e}")
return 1
if __name__ == '__main__':
sys.exit(main())

113
fix_user_vs_testuser.py Normal file
View File

@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""
Fix the distinction between models::User and TestUser objects
"""
import re
import sys
def fix_user_types(content):
"""Fix the distinction between models::User and TestUser objects"""
# First, find all the places where we import or create Users vs TestUsers
# and fix them appropriately
# In the test functions, we need to identify which variables are TestUser and which are User
# Let's look for patterns that indicate TestUser creation
# Pattern 1: Variables created from auth_helper.create_test_user() are TestUser
# Pattern 2: Variables created from auth_helper.create_admin_user() are TestUser
# Pattern 3: Variables created from auth_helper.create_test_admin() are TestUser
# Find all test functions and fix them individually
test_functions = re.findall(r'(#\[tokio::test\].*?^ })', content, re.MULTILINE | re.DOTALL)
for func in test_functions:
# Check if this function creates TestUser objects
if 'auth_helper.create_test_user()' in func or 'auth_helper.create_admin_user()' in func or 'auth_helper.create_test_admin()' in func:
# This function uses TestUser objects, keep .user_response
continue
else:
# This function might be using models::User objects, revert .user_response
# But only if the variable is clearly a User object
func_lines = func.split('\n')
for i, line in enumerate(func_lines):
# Look for variable declarations that create User objects
if 'create_test_user(&' in line and 'UserRole::' in line:
# This creates a models::User object
var_match = re.search(r'let (\w+) = create_test_user\(', line)
if var_match:
var_name = var_match.group(1)
# Replace .user_response with direct access for this variable
func = func.replace(f'{var_name}.user_response.id', f'{var_name}.id')
func = func.replace(f'{var_name}.user_response.role', f'{var_name}.role')
# Apply the fixed functions back to content
# This is complex, so let's use a different approach
# Let's be more specific about which variables are TestUser vs User
# Look for the specific patterns in the migration
# Fix models::User objects that got incorrectly converted
# Pattern: Variables that are clearly User objects (not TestUser)
lines = content.split('\n')
in_test_function = False
current_function_uses_testuser = False
fixed_lines = []
for line in lines:
if '#[tokio::test]' in line:
in_test_function = True
current_function_uses_testuser = False
elif in_test_function and line.strip() == '}':
in_test_function = False
current_function_uses_testuser = False
elif in_test_function and ('auth_helper.create_test_user()' in line or 'auth_helper.create_admin_user()' in line or 'auth_helper.create_test_admin()' in line):
current_function_uses_testuser = True
elif in_test_function and not current_function_uses_testuser:
# This function doesn't use TestUser objects, so revert .user_response
# But only for variables that are created with the old pattern
if 'create_test_user(&' in line and 'UserRole::' in line:
# This line creates a models::User object
var_match = re.search(r'let (\w+) = create_test_user\(', line)
if var_match:
var_name = var_match.group(1)
# Mark this variable as a User object
# We'll fix its usage in subsequent lines
pass
# Fix usage of User objects
line = re.sub(r'(\w+)\.user_response\.id\b', r'\1.id', line)
line = re.sub(r'(\w+)\.user_response\.role\b', r'\1.role', line)
fixed_lines.append(line)
return '\n'.join(fixed_lines)
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
# Read the file
try:
with open(file_path, 'r') as f:
content = f.read()
except FileNotFoundError:
print(f"Error: Could not find file {file_path}")
return 1
# Apply fixes
print("Fixing User vs TestUser distinction...")
fixed_content = fix_user_types(content)
# Write back the fixed content
try:
with open(file_path, 'w') as f:
f.write(fixed_content)
print(f"Successfully fixed {file_path}")
return 0
except Exception as e:
print(f"Error writing file: {e}")
return 1
if __name__ == '__main__':
sys.exit(main())

View File

@ -1,8 +1,8 @@
import { describe, test, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { screen } from '@testing-library/react';
import NotificationPanel from '../NotificationPanel';
import { NotificationProvider } from '../../../contexts/NotificationContext';
import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils';
import React from 'react';
// Mock date-fns
@ -10,7 +10,6 @@ vi.mock('date-fns', () => ({
formatDistanceToNow: vi.fn(() => '2 minutes ago'),
}));
const theme = createTheme();
const createMockAnchorEl = () => {
const mockEl = document.createElement('div');
@ -28,13 +27,14 @@ const createMockAnchorEl = () => {
};
describe('NotificationPanel - Simple Tests', () => {
beforeEach(() => {
setupTestEnvironment();
});
test('should not render when anchorEl is null', () => {
const { container } = render(
<ThemeProvider theme={theme}>
const { container } = renderWithProviders(
<NotificationProvider>
<NotificationPanel anchorEl={null} onClose={vi.fn()} />
</NotificationProvider>
</ThemeProvider>
);
expect(container.firstChild).toBeNull();
@ -43,12 +43,10 @@ describe('NotificationPanel - Simple Tests', () => {
test('should render notification panel with header when anchorEl is provided', () => {
const mockAnchorEl = createMockAnchorEl();
render(
<ThemeProvider theme={theme}>
renderWithProviders(
<NotificationProvider>
<NotificationPanel anchorEl={mockAnchorEl} onClose={vi.fn()} />
</NotificationProvider>
</ThemeProvider>
);
expect(screen.getByText('Notifications')).toBeInTheDocument();
@ -57,12 +55,10 @@ describe('NotificationPanel - Simple Tests', () => {
test('should show empty state when no notifications', () => {
const mockAnchorEl = createMockAnchorEl();
render(
<ThemeProvider theme={theme}>
renderWithProviders(
<NotificationProvider>
<NotificationPanel anchorEl={mockAnchorEl} onClose={vi.fn()} />
</NotificationProvider>
</ThemeProvider>
);
expect(screen.getByText('No notifications')).toBeInTheDocument();
@ -71,12 +67,10 @@ describe('NotificationPanel - Simple Tests', () => {
test('should render with theme provider correctly', () => {
const mockAnchorEl = createMockAnchorEl();
const { container } = render(
<ThemeProvider theme={theme}>
const { container } = renderWithProviders(
<NotificationProvider>
<NotificationPanel anchorEl={mockAnchorEl} onClose={vi.fn()} />
</NotificationProvider>
</ThemeProvider>
);
// Should render without crashing

View File

@ -1,8 +1,8 @@
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { screen, waitFor } from '@testing-library/react';
import FailedDocumentViewer from '../FailedDocumentViewer';
import { api } from '../../services/api';
import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils';
// Mock the API
vi.mock('../../services/api', () => ({
@ -11,7 +11,6 @@ vi.mock('../../services/api', () => ({
},
}));
const theme = createTheme();
const defaultProps = {
failedDocumentId: 'test-failed-doc-id',
@ -22,10 +21,8 @@ const defaultProps = {
const renderFailedDocumentViewer = (props = {}) => {
const combinedProps = { ...defaultProps, ...props };
return render(
<ThemeProvider theme={theme}>
return renderWithProviders(
<FailedDocumentViewer {...combinedProps} />
</ThemeProvider>
);
};
@ -44,6 +41,7 @@ global.URL = {
describe('FailedDocumentViewer', () => {
beforeEach(() => {
setupTestEnvironment();
vi.clearAllMocks();
});

View File

@ -1,24 +1,21 @@
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 TestNotification from '../TestNotification';
import { NotificationProvider } from '../../contexts/NotificationContext';
import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils';
import React from 'react';
const theme = createTheme();
const renderTestNotification = () => {
return render(
<ThemeProvider theme={theme}>
return renderWithProviders(
<NotificationProvider>
<TestNotification />
</NotificationProvider>
</ThemeProvider>
);
};
describe('TestNotification', () => {
beforeEach(() => {
setupTestEnvironment();
vi.clearAllMocks();
});
@ -126,12 +123,10 @@ describe('TestNotification Integration', () => {
);
};
render(
<ThemeProvider theme={theme}>
renderWithProviders(
<NotificationProvider>
<TestWrapper />
</NotificationProvider>
</ThemeProvider>
);
expect(screen.getByTestId('notification-count')).toHaveTextContent('0');

View File

@ -1,6 +1,7 @@
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, act } from '@testing-library/react';
import { screen, act } from '@testing-library/react';
import { NotificationProvider, useNotifications } from '../NotificationContext';
import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils';
import React from 'react';
// Simple test component
@ -52,7 +53,7 @@ const SimpleTestComponent: React.FC = () => {
};
const renderWithProvider = () => {
return render(
return renderWithProviders(
<NotificationProvider>
<SimpleTestComponent />
</NotificationProvider>
@ -61,6 +62,7 @@ const renderWithProvider = () => {
describe('NotificationContext - Simple Tests', () => {
beforeEach(() => {
setupTestEnvironment();
vi.useFakeTimers();
});
@ -162,7 +164,7 @@ describe('NotificationContext - Simple Tests', () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
expect(() => {
render(<SimpleTestComponent />);
renderWithProviders(<SimpleTestComponent />);
}).toThrow('useNotifications must be used within NotificationProvider');
consoleSpy.mockRestore();
@ -196,6 +198,7 @@ describe('NotificationContext - Types', () => {
};
beforeEach(() => {
setupTestEnvironment();
vi.useFakeTimers();
});
@ -204,7 +207,7 @@ describe('NotificationContext - Types', () => {
});
test.each(['success', 'error', 'info', 'warning'] as const)('should handle %s notification type', (type) => {
render(
renderWithProviders(
<NotificationProvider>
<TypeTestComponent type={type} />
</NotificationProvider>

166
migrate_documents_tests.py Normal file
View File

@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""
Script to migrate remaining tests in documents_tests.rs to use the new TestContext pattern.
"""
import re
import sys
def migrate_test_file(file_path):
"""Migrate the documents_tests.rs file to use new test patterns."""
with open(file_path, 'r') as f:
content = f.read()
# Store the original content for comparison
original_content = content
# 1. Remove #[ignore = "Requires PostgreSQL database"] annotations
content = re.sub(r' #\[ignore = "Requires PostgreSQL database"\]\n', '', content)
# 2. Remove old database pool creation lines
content = re.sub(r' let pool = create_test_db_pool\(\)\.await;\n', '', content)
# 3. Remove old Database struct creation lines
content = re.sub(r' let documents_db = Database \{ pool: pool\.clone\(\) \};\n', '', content)
# 4. Replace old user creation with new pattern
# Handle both User and Admin role patterns
content = re.sub(
r' let user = create_test_user\(&pool, UserRole::User\)\.await;',
' let ctx = TestContext::new().await;\n let auth_helper = TestAuthHelper::new(ctx.app.clone());\n let user = auth_helper.create_test_user().await;',
content
)
content = re.sub(
r' let admin = create_test_user\(&pool, UserRole::Admin\)\.await;',
' let admin = auth_helper.create_test_admin().await;',
content
)
# Handle other variations of user creation
content = re.sub(
r' let user1 = create_test_user\(&pool, UserRole::User\)\.await;',
' let ctx = TestContext::new().await;\n let auth_helper = TestAuthHelper::new(ctx.app.clone());\n let user1 = auth_helper.create_test_user().await;',
content
)
content = re.sub(
r' let user2 = create_test_user\(&pool, UserRole::User\)\.await;',
' let user2 = auth_helper.create_test_user().await;',
content
)
content = re.sub(
r' let tenant1_user1 = create_test_user\(&pool, UserRole::User\)\.await;',
' let ctx = TestContext::new().await;\n let auth_helper = TestAuthHelper::new(ctx.app.clone());\n let tenant1_user1 = auth_helper.create_test_user().await;',
content
)
content = re.sub(
r' let tenant1_user2 = create_test_user\(&pool, UserRole::User\)\.await;',
' let tenant1_user2 = auth_helper.create_test_user().await;',
content
)
content = re.sub(
r' let tenant2_user1 = create_test_user\(&pool, UserRole::User\)\.await;',
' let tenant2_user1 = auth_helper.create_test_user().await;',
content
)
content = re.sub(
r' let tenant2_user2 = create_test_user\(&pool, UserRole::User\)\.await;',
' let tenant2_user2 = auth_helper.create_test_user().await;',
content
)
# 5. Replace document creation and insertion pattern
content = re.sub(
r' let ([a-zA-Z0-9_]+) = create_and_insert_test_document\(&pool, ([a-zA-Z0-9_.()]+)\)\.await;',
r' let \1_doc = create_test_document(\2);\n let \1 = ctx.state.db.create_document(\1_doc).await.expect("Failed to create document");',
content
)
# 6. Replace documents_db. with ctx.state.db.
content = re.sub(r'documents_db\.', 'ctx.state.db.', content)
# 7. Replace user.id with user.id() for TestUser instances
# This is tricky because we need to be careful about which instances are TestUser vs regular User
# We'll handle this pattern by pattern based on context
# For delete_document calls that use user.id, user.role pattern
content = re.sub(
r'\.delete_document\(([^,]+), ([a-zA-Z0-9_]+)\.id, ([a-zA-Z0-9_]+)\.role\)',
r'.delete_document(\1, \2.id(), \3.role)',
content
)
# For bulk_delete_documents calls
content = re.sub(
r'\.bulk_delete_documents\(([^,]+), ([a-zA-Z0-9_]+)\.id, ([a-zA-Z0-9_]+)\.role\)',
r'.bulk_delete_documents(\1, \2.id(), \3.role)',
content
)
# For get_document_by_id calls
content = re.sub(
r'\.get_document_by_id\(([^,]+), ([a-zA-Z0-9_]+)\.id, ([a-zA-Z0-9_]+)\.role\)',
r'.get_document_by_id(\1, \2.id(), \3.role)',
content
)
# For create_test_document calls
content = re.sub(
r'create_test_document\(([a-zA-Z0-9_]+)\.id\)',
r'create_test_document(\1.id())',
content
)
# For bind calls in SQL
content = re.sub(
r'\.bind\(([a-zA-Z0-9_]+)\.id\)',
r'.bind(\1.id())',
content
)
# For let user_id assignments
content = re.sub(
r' let user_id = ([a-zA-Z0-9_]+)\.id;',
r' let user_id = \1.id();',
content
)
# Add missing imports if TestContext/TestAuthHelper aren't already imported
# Check if the imports are present
if 'use crate::test_utils::{TestContext, TestAuthHelper};' not in content:
# Find the existing test_utils import and update it
content = re.sub(
r'use crate::test_utils::TestContext;',
'use crate::test_utils::{TestContext, TestAuthHelper};',
content
)
# Check if we made any changes
if content != original_content:
return content
else:
return None
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
print("Starting migration of documents_tests.rs...")
migrated_content = migrate_test_file(file_path)
if migrated_content:
# Write the migrated content back
with open(file_path, 'w') as f:
f.write(migrated_content)
print("Migration completed successfully!")
else:
print("No changes needed - file is already migrated or no patterns found.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""
Enhanced script to migrate remaining tests in documents_tests.rs to use the new TestContext pattern.
"""
import re
import sys
def migrate_test_file(file_path):
"""Migrate the documents_tests.rs file to use new test patterns."""
with open(file_path, 'r') as f:
content = f.read()
# Store the original content for comparison
original_content = content
# Fix remaining documents_db references that were missed
content = re.sub(r' let result = documents_db', ' let result = ctx.state.db', content)
content = re.sub(r' let result2 = documents_db', ' let result2 = ctx.state.db', content)
# Fix any remaining documents_db references in method calls
content = re.sub(r'documents_db\n', 'ctx.state.db\n', content)
# Fix variable naming from the document creation pattern
# The regex replacement created variables like user_doc_doc, let's fix those
content = re.sub(r' let ([a-zA-Z0-9_]+)_doc_doc = create_test_document\(([^)]+)\);',
r' let \1_doc = create_test_document(\2);', content)
# Check if we made any changes
if content != original_content:
return content
else:
return None
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
print("Starting enhanced migration of documents_tests.rs...")
migrated_content = migrate_test_file(file_path)
if migrated_content:
# Write the migrated content back
with open(file_path, 'w') as f:
f.write(migrated_content)
print("Enhanced migration completed successfully!")
else:
print("No changes needed - file is already migrated or no patterns found.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""
Final cleanup script to fix variable naming issues from the migration.
"""
import re
import sys
def migrate_test_file(file_path):
"""Clean up variable naming issues from the migration."""
with open(file_path, 'r') as f:
content = f.read()
# Store the original content for comparison
original_content = content
# Fix the doubled variable names created by the regex
content = re.sub(r' let ([a-zA-Z0-9_]+)_doc_doc = create_test_document\(([^)]+)\);',
r' let \1_doc = create_test_document(\2);', content)
# Also fix any references to these variables in the same context
content = re.sub(r'create_document\(([a-zA-Z0-9_]+)_doc_doc\)',
r'create_document(\1_doc)', content)
# Check if we made any changes
if content != original_content:
return content
else:
return None
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
print("Starting final cleanup of documents_tests.rs...")
migrated_content = migrate_test_file(file_path)
if migrated_content:
# Write the migrated content back
with open(file_path, 'w') as f:
f.write(migrated_content)
print("Final cleanup completed successfully!")
else:
print("No changes needed - file is already clean.")
if __name__ == "__main__":
main()

96
migrate_tests.py Normal file
View File

@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""
Bulk migration script to convert old test patterns to new TestContext/TestAuthHelper patterns
in documents_tests.rs
"""
import re
import sys
def migrate_test_patterns(content):
"""Apply all migration patterns to the content"""
# Remove #[ignore] attributes
content = re.sub(r'\s*#\[ignore = "Requires PostgreSQL database"\]', '', content)
# Pattern 1: Replace basic test setup
# Old pattern:
# let pool = create_test_db_pool().await;
# let documents_db = Database { pool: pool.clone() };
# New pattern:
# let ctx = TestContext::new().await;
# let auth_helper = TestAuthHelper::new(ctx.app.clone());
pool_db_pattern = r'let pool = create_test_db_pool\(\)\.await;\s*let documents_db = Database \{ pool: pool\.clone\(\) \};'
pool_db_replacement = 'let ctx = TestContext::new().await;\n let auth_helper = TestAuthHelper::new(ctx.app.clone());'
content = re.sub(pool_db_pattern, pool_db_replacement, content, flags=re.MULTILINE)
# Pattern 2: Replace user creation
# let user = create_test_user(&pool, UserRole::User).await;
# -> let user = auth_helper.create_test_user().await;
user_pattern = r'let (\w+) = create_test_user\(&pool, UserRole::User\)\.await;'
user_replacement = r'let \1 = auth_helper.create_test_user().await;'
content = re.sub(user_pattern, user_replacement, content)
# Pattern 3: Replace admin creation
# let admin = create_test_user(&pool, UserRole::Admin).await;
# -> let admin = auth_helper.create_test_admin().await;
admin_pattern = r'let (\w+) = create_test_user\(&pool, UserRole::Admin\)\.await;'
admin_replacement = r'let \1 = auth_helper.create_test_admin().await;'
content = re.sub(admin_pattern, admin_replacement, content)
# Pattern 4: Replace document creation and insertion
# let doc = create_and_insert_test_document(&pool, user.id).await;
# -> let doc = create_test_document(user.id());
# let doc = ctx.state.db.create_document(doc).await.expect("Failed to create document");
doc_pattern = r'let (\w+) = create_and_insert_test_document\(&pool, (\w+)\.id\)\.await;'
def doc_replacement(match):
doc_name = match.group(1)
user_name = match.group(2)
return f'let {doc_name} = create_test_document({user_name}.id());\n let {doc_name} = ctx.state.db.create_document({doc_name}).await.expect("Failed to create document");'
content = re.sub(doc_pattern, doc_replacement, content)
# Pattern 5: Replace documents_db. with ctx.state.db.
content = re.sub(r'documents_db\.', 'ctx.state.db.', content)
# Pattern 6: Replace .id with .id() for user objects (be careful with document.id)
# Only replace when it's clearly a user/admin object
content = re.sub(r'(\w+)\.id(?![().])', r'\1.id()', content)
# Fix document.id() back to document.id (documents don't have id() method)
content = re.sub(r'(doc\w*)\.id\(\)', r'\1.id', content)
content = re.sub(r'(document\w*)\.id\(\)', r'\1.id', content)
content = re.sub(r'(\w*_doc\w*)\.id\(\)', r'\1.id', content)
content = re.sub(r'(result\[\d+\])\.id\(\)', r'\1.id', content)
content = re.sub(r'(deleted_doc)\.id\(\)', r'\1.id', content)
content = re.sub(r'(found_doc\.unwrap\(\))\.id\(\)', r'\1.id', content)
return content
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
# Read the file
try:
with open(file_path, 'r') as f:
content = f.read()
except FileNotFoundError:
print(f"Error: Could not find file {file_path}")
return 1
# Apply migrations
print("Applying migration patterns...")
migrated_content = migrate_test_patterns(content)
# Write back the migrated content
try:
with open(file_path, 'w') as f:
f.write(migrated_content)
print(f"Successfully migrated {file_path}")
return 0
except Exception as e:
print(f"Error writing file: {e}")
return 1
if __name__ == '__main__':
sys.exit(main())

77
migrate_tests_fix.py Normal file
View File

@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""
Enhanced migration script to fix remaining issues in documents_tests.rs
"""
import re
import sys
def migrate_remaining_issues(content):
"""Fix remaining issues from the bulk migration"""
# Fix remaining pool references
content = re.sub(r'\.execute\(&pool\)', '.execute(&ctx.state.db.pool)', content)
# Fix Database::new patterns - replace with TestContext
database_new_pattern = r'let database = Database::new\(&connection_string\)\.await\.unwrap\(\);'
database_new_replacement = 'let ctx = TestContext::new().await;\n let database = &ctx.state.db;'
content = re.sub(database_new_pattern, database_new_replacement, content)
# Also handle the variable name 'database' in subsequent lines
# Replace database. with ctx.state.db. only in test functions
content = re.sub(r'\bdatabase\.', 'ctx.state.db.', content)
# Fix cases where we have ctx declared multiple times in the same function
# This is a more complex pattern - let's fix it by ensuring we only declare ctx once per function
# Find functions with multiple ctx declarations and fix them
def fix_multiple_ctx(match):
func_content = match.group(0)
# Count ctx declarations
ctx_count = len(re.findall(r'let ctx = TestContext::new\(\)\.await;', func_content))
if ctx_count > 1:
# Keep only the first one, replace others with comments
first_done = False
def replace_ctx(ctx_match):
nonlocal first_done
if not first_done:
first_done = True
return ctx_match.group(0)
else:
return '// let ctx = TestContext::new().await; // Already declared above'
func_content = re.sub(r'let ctx = TestContext::new\(\)\.await;', replace_ctx, func_content)
return func_content
# Apply this to each test function
func_pattern = r'#\[tokio::test\][^}]*?(?=\n #\[tokio::test\]|\n}\n|\Z)'
content = re.sub(func_pattern, fix_multiple_ctx, content, flags=re.MULTILINE | re.DOTALL)
return content
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
# Read the file
try:
with open(file_path, 'r') as f:
content = f.read()
except FileNotFoundError:
print(f"Error: Could not find file {file_path}")
return 1
# Apply additional fixes
print("Applying additional migration fixes...")
migrated_content = migrate_remaining_issues(content)
# Write back the migrated content
try:
with open(file_path, 'w') as f:
f.write(migrated_content)
print(f"Successfully applied fixes to {file_path}")
return 0
except Exception as e:
print(f"Error writing file: {e}")
return 1
if __name__ == '__main__':
sys.exit(main())

75
precise_testuser_fix.py Normal file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""
Precise fix for TestUser field access based on variable creation patterns
"""
import re
import sys
def fix_testuser_access(content):
"""Fix TestUser objects to use proper .user_response field access"""
lines = content.split('\n')
fixed_lines = []
# Track which variables are TestUser objects within each function
current_testuser_vars = set()
in_function = False
for line in lines:
# Reset when entering a new function
if re.match(r'\s*#\[tokio::test\]', line) or re.match(r'\s*async fn ', line):
current_testuser_vars.clear()
in_function = True
elif re.match(r'^\s*}$', line) and in_function:
in_function = False
current_testuser_vars.clear()
# Track TestUser variable declarations
if in_function:
# Variables created by auth_helper methods are TestUser
testuser_match = re.search(r'let (\w+) = auth_helper\.(?:create_test_user|create_admin_user|create_test_admin)\(\)', line)
if testuser_match:
var_name = testuser_match.group(1)
current_testuser_vars.add(var_name)
print(f"Found TestUser variable: {var_name}")
# Fix field access for known TestUser variables
fixed_line = line
for var_name in current_testuser_vars:
# Replace .id with .user_response.id for TestUser objects
fixed_line = re.sub(rf'\b{var_name}\.id\b', f'{var_name}.user_response.id', fixed_line)
# Replace .role with .user_response.role for TestUser objects
fixed_line = re.sub(rf'\b{var_name}\.role\b', f'{var_name}.user_response.role', fixed_line)
fixed_lines.append(fixed_line)
return '\n'.join(fixed_lines)
def main():
file_path = '/root/repos/readur/src/tests/documents_tests.rs'
# Read the file
try:
with open(file_path, 'r') as f:
content = f.read()
except FileNotFoundError:
print(f"Error: Could not find file {file_path}")
return 1
# Apply fixes
print("Applying precise TestUser field access fixes...")
fixed_content = fix_testuser_access(content)
# Write back the fixed content
try:
with open(file_path, 'w') as f:
f.write(fixed_content)
print(f"Successfully fixed {file_path}")
return 0
except Exception as e:
print(f"Error writing file: {e}")
return 1
if __name__ == '__main__':
sys.exit(main())

View File

@ -168,6 +168,7 @@ impl TestContext {
/// Create a test context with custom configuration
pub async fn with_config(config_builder: TestConfigBuilder) -> Self {
let postgres_image = Postgres::default()
.with_tag("15") // Use PostgreSQL 15 which has gen_random_uuid() built-in
.with_env_var("POSTGRES_USER", "test")
.with_env_var("POSTGRES_PASSWORD", "test")
.with_env_var("POSTGRES_DB", "test");
@ -178,7 +179,10 @@ impl TestContext {
let database_url = std::env::var("TEST_DATABASE_URL")
.unwrap_or_else(|_| format!("postgresql://test:test@localhost:{}/test", port));
let db = crate::db::Database::new(&database_url).await.unwrap();
db.migrate().await.unwrap();
// Run proper SQLx migrations (PostgreSQL 15+ has gen_random_uuid() built-in)
let migrations = sqlx::migrate!("./migrations");
migrations.run(&db.pool).await.unwrap();
let config = config_builder.build(database_url);
let queue_service = Arc::new(crate::ocr::queue::OcrQueueService::new(db.clone(), db.pool.clone(), 2));
@ -376,6 +380,11 @@ impl TestAuthHelper {
}
}
/// Create an admin test user (alias for create_admin_user for backward compatibility)
pub async fn create_test_admin(&self) -> TestUser {
self.create_admin_user().await
}
/// Login a user and return their authentication token
pub async fn login_user(&self, username: &str, password: &str) -> String {
let login_data = json!({

View File

@ -34,7 +34,7 @@ mod tests {
created_at: Utc::now(),
updated_at: Utc::now(),
user_id,
file_hash: Some("abcd1234567890123456789012345678901234567890123456789012345678".to_string()),
file_hash: Some(format!("{:x}", Uuid::new_v4().as_u128())), // Generate unique file hash
original_created_at: None,
original_modified_at: None,
source_metadata: None,

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,30 @@
use sqlx::PgPool;
use crate::test_utils::TestContext;
use uuid;
#[cfg(test)]
mod migration_constraint_tests {
use super::*;
#[sqlx::test]
async fn test_failed_documents_constraint_validation(pool: PgPool) {
#[tokio::test]
async fn test_failed_documents_constraint_validation() {
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Create a test user first to avoid foreign key constraint violations
let user_id = uuid::Uuid::new_v4();
sqlx::query(
"INSERT INTO users (id, username, email, password_hash, role)
VALUES ($1, $2, $3, $4, $5)"
)
.bind(user_id)
.bind("test_constraint_user")
.bind("test_constraint@example.com")
.bind("hash")
.bind("user")
.execute(pool)
.await
.unwrap();
// Test that all allowed failure_reason values work
let valid_reasons = vec![
"duplicate_content", "duplicate_filename", "unsupported_format",
@ -22,21 +41,40 @@ mod migration_constraint_tests {
INSERT INTO failed_documents (
user_id, filename, failure_reason, failure_stage, ingestion_source
) VALUES (
gen_random_uuid(), $1, $2, 'validation', 'test'
$1, $2, $3, 'validation', 'test'
)
"#
)
.bind(user_id)
.bind(format!("test_file_{}.txt", reason))
.bind(reason)
.execute(&pool)
.execute(pool)
.await;
assert!(result.is_ok(), "Valid failure_reason '{}' should be accepted", reason);
}
}
#[sqlx::test]
async fn test_failed_documents_invalid_constraint_rejection(pool: PgPool) {
#[tokio::test]
async fn test_failed_documents_invalid_constraint_rejection() {
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Create a test user first to avoid foreign key constraint violations
let user_id = uuid::Uuid::new_v4();
sqlx::query(
"INSERT INTO users (id, username, email, password_hash, role)
VALUES ($1, $2, $3, $4, $5)"
)
.bind(user_id)
.bind("test_invalid_user")
.bind("test_invalid@example.com")
.bind("hash")
.bind("user")
.execute(pool)
.await
.unwrap();
// Test that invalid failure_reason values are rejected
let invalid_reasons = vec![
"invalid_reason", "unknown", "timeout", "memory_limit",
@ -49,21 +87,40 @@ mod migration_constraint_tests {
INSERT INTO failed_documents (
user_id, filename, failure_reason, failure_stage, ingestion_source
) VALUES (
gen_random_uuid(), $1, $2, 'validation', 'test'
$1, $2, $3, 'validation', 'test'
)
"#
)
.bind(user_id)
.bind(format!("test_file_{}.txt", reason))
.bind(reason)
.execute(&pool)
.execute(pool)
.await;
assert!(result.is_err(), "Invalid failure_reason '{}' should be rejected", reason);
}
}
#[sqlx::test]
async fn test_failed_documents_stage_constraint_validation(pool: PgPool) {
#[tokio::test]
async fn test_failed_documents_stage_constraint_validation() {
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Create a test user first to avoid foreign key constraint violations
let user_id = uuid::Uuid::new_v4();
sqlx::query(
"INSERT INTO users (id, username, email, password_hash, role)
VALUES ($1, $2, $3, $4, $5)"
)
.bind(user_id)
.bind("test_stage_user")
.bind("test_stage@example.com")
.bind("hash")
.bind("user")
.execute(pool)
.await
.unwrap();
// Test that all allowed failure_stage values work
let valid_stages = vec![
"ingestion", "validation", "ocr", "storage", "processing", "sync"
@ -75,21 +132,40 @@ mod migration_constraint_tests {
INSERT INTO failed_documents (
user_id, filename, failure_reason, failure_stage, ingestion_source
) VALUES (
gen_random_uuid(), $1, 'other', $2, 'test'
$1, $2, 'other', $3, 'test'
)
"#
)
.bind(user_id)
.bind(format!("test_file_{}.txt", stage))
.bind(stage)
.execute(&pool)
.execute(pool)
.await;
assert!(result.is_ok(), "Valid failure_stage '{}' should be accepted", stage);
}
}
#[sqlx::test]
async fn test_migration_mapping_compatibility(pool: PgPool) {
#[tokio::test]
async fn test_migration_mapping_compatibility() {
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Create a test user first to avoid foreign key constraint violations
let user_id = uuid::Uuid::new_v4();
sqlx::query(
"INSERT INTO users (id, username, email, password_hash, role)
VALUES ($1, $2, $3, $4, $5)"
)
.bind(user_id)
.bind("test_migration_user")
.bind("test_migration@example.com")
.bind("hash")
.bind("user")
.execute(pool)
.await
.unwrap();
// Test that the migration mapping logic matches our constraints
let migration_mappings = vec![
("low_ocr_confidence", "low_ocr_confidence"),
@ -127,13 +203,14 @@ mod migration_constraint_tests {
INSERT INTO failed_documents (
user_id, filename, failure_reason, failure_stage, ingestion_source
) VALUES (
gen_random_uuid(), $1, $2, 'ocr', 'migration'
$1, $2, $3, 'ocr', 'migration'
)
"#
)
.bind(user_id)
.bind(format!("migration_test_{}.txt", input_reason.replace("/", "_")))
.bind(mapped_reason)
.execute(&pool)
.execute(pool)
.await;
assert!(result.is_ok(),

View File

@ -1,14 +1,29 @@
use sqlx::{PgPool, Row};
use crate::test_utils::TestContext;
use sqlx::Row;
use uuid::Uuid;
#[cfg(test)]
mod migration_integration_tests {
use super::*;
#[sqlx::test]
async fn test_full_migration_workflow(pool: PgPool) {
// Setup: Create sample documents with various OCR failure reasons
#[tokio::test]
async fn test_full_migration_workflow() {
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Setup: Create a test user first
let user_id = Uuid::new_v4();
sqlx::query(
"INSERT INTO users (id, username, email, password_hash, role)
VALUES ($1, $2, $3, $4, $5)"
)
.bind(user_id)
.bind("test_migration_user")
.bind("test_migration@example.com")
.bind("hash")
.bind("user")
.execute(pool)
.await
.unwrap();
// Create test documents with different failure scenarios
let test_documents = vec![
@ -37,7 +52,7 @@ mod migration_integration_tests {
.bind(filename)
.bind(*failure_reason)
.bind(error_msg)
.execute(&pool)
.execute(pool)
.await
.expect("Failed to insert test document");
}
@ -46,7 +61,7 @@ mod migration_integration_tests {
let before_count: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM documents WHERE ocr_status = 'failed'"
)
.fetch_one(&pool)
.fetch_one(pool)
.await
.expect("Failed to count documents");
@ -57,7 +72,7 @@ mod migration_integration_tests {
r#"
INSERT INTO failed_documents (
user_id, filename, original_filename, file_path, file_size,
mime_type, ocr_error, failure_reason, failure_stage, ingestion_source,
mime_type, error_message, failure_reason, failure_stage, ingestion_source,
created_at, updated_at
)
SELECT
@ -80,16 +95,19 @@ mod migration_integration_tests {
WHERE d.ocr_status = 'failed'
"#
)
.execute(&pool)
.execute(pool)
.await;
assert!(migration_result.is_ok(), "Migration should succeed");
match migration_result {
Ok(_) => {},
Err(e) => panic!("Migration failed: {:?}", e),
}
// Verify all documents were migrated
let migrated_count: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM failed_documents WHERE ingestion_source = 'migration'"
)
.fetch_one(&pool)
.fetch_one(pool)
.await
.expect("Failed to count migrated documents");
@ -110,7 +128,7 @@ mod migration_integration_tests {
"SELECT failure_reason FROM failed_documents WHERE filename = $1"
)
.bind(filename)
.fetch_one(&pool)
.fetch_one(pool)
.await
.expect("Failed to fetch failure reason");
@ -126,7 +144,7 @@ mod migration_integration_tests {
let delete_result = sqlx::query(
"DELETE FROM documents WHERE ocr_status = 'failed'"
)
.execute(&pool)
.execute(pool)
.await;
assert!(delete_result.is_ok(), "Delete should succeed");
@ -135,7 +153,7 @@ mod migration_integration_tests {
let remaining_failed: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM documents WHERE ocr_status = 'failed'"
)
.fetch_one(&pool)
.fetch_one(pool)
.await
.expect("Failed to count remaining documents");
@ -145,7 +163,7 @@ mod migration_integration_tests {
let failed_docs = sqlx::query(
"SELECT filename, failure_reason, failure_stage FROM failed_documents ORDER BY filename"
)
.fetch_all(&pool)
.fetch_all(pool)
.await
.expect("Failed to fetch failed documents");
@ -166,10 +184,25 @@ mod migration_integration_tests {
}
}
#[sqlx::test]
async fn test_migration_with_edge_cases(pool: PgPool) {
// Test migration with edge cases that previously caused issues
#[tokio::test]
async fn test_migration_with_edge_cases() {
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Create a test user first
let user_id = Uuid::new_v4();
sqlx::query(
"INSERT INTO users (id, username, email, password_hash, role)
VALUES ($1, $2, $3, $4, $5)"
)
.bind(user_id)
.bind("test_edge_user")
.bind("test_edge@example.com")
.bind("hash")
.bind("user")
.execute(pool)
.await
.unwrap();
// Edge cases that might break migration
let edge_cases = vec![
@ -195,7 +228,7 @@ mod migration_integration_tests {
.bind(filename)
.bind(*failure_reason)
.bind(error_msg)
.execute(&pool)
.execute(pool)
.await
.expect("Failed to insert edge case document");
}
@ -224,7 +257,7 @@ mod migration_integration_tests {
WHERE d.ocr_status = 'failed'
"#
)
.execute(&pool)
.execute(pool)
.await;
assert!(migration_result.is_ok(), "Migration should handle edge cases");
@ -233,7 +266,7 @@ mod migration_integration_tests {
let edge_case_mappings = sqlx::query(
"SELECT filename, failure_reason FROM failed_documents WHERE ingestion_source = 'migration_edge_test'"
)
.fetch_all(&pool)
.fetch_all(pool)
.await
.expect("Failed to fetch edge case mappings");
@ -245,10 +278,25 @@ mod migration_integration_tests {
}
}
#[sqlx::test]
async fn test_constraint_enforcement_during_migration(pool: PgPool) {
// This test ensures that if we accidentally introduce invalid data
// during migration, the constraints will catch it
#[tokio::test]
async fn test_constraint_enforcement_during_migration() {
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Create a test user first to avoid foreign key constraint violations
let user_id = Uuid::new_v4();
sqlx::query(
"INSERT INTO users (id, username, email, password_hash, role)
VALUES ($1, $2, $3, $4, $5)"
)
.bind(user_id)
.bind("test_constraint_user")
.bind("test_constraint@example.com")
.bind("hash")
.bind("user")
.execute(pool)
.await
.unwrap();
// Try to insert data that violates constraints
let invalid_insert = sqlx::query(
@ -256,11 +304,12 @@ mod migration_integration_tests {
INSERT INTO failed_documents (
user_id, filename, failure_reason, failure_stage, ingestion_source
) VALUES (
gen_random_uuid(), 'invalid_test.pdf', 'migration_completed', 'migration', 'test'
$1, 'invalid_test.pdf', 'migration_completed', 'migration', 'test'
)
"#
)
.execute(&pool)
.bind(user_id)
.execute(pool)
.await;
// This should fail due to constraint violation

View File

@ -6,23 +6,14 @@
#[cfg(test)]
mod tests {
use crate::db::Database;
use crate::test_utils::TestContext;
use sqlx::Row;
use uuid::Uuid;
async fn create_test_db() -> Database {
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");
db.migrate().await.expect("Failed to migrate test database");
db
}
#[tokio::test]
async fn test_row_trait_import_is_available() {
let db = create_test_db().await;
let pool = db.get_pool();
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// This test ensures Row trait is imported and available
// The .get() method would fail to compile if Row trait is missing
@ -39,8 +30,8 @@ mod tests {
#[tokio::test]
async fn test_sum_aggregate_type_safety() {
let db = create_test_db().await;
let pool = db.get_pool();
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Create test data
let user_id = Uuid::new_v4();
@ -103,8 +94,8 @@ mod tests {
#[tokio::test]
async fn test_group_by_aggregate_type_safety() {
let db = create_test_db().await;
let pool = db.get_pool();
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Test the exact SQL pattern from ignored_files.rs GROUP BY query
let results = sqlx::query(
@ -132,8 +123,8 @@ mod tests {
#[tokio::test]
async fn test_numeric_vs_bigint_difference() {
let db = create_test_db().await;
let pool = db.get_pool();
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Demonstrate the difference between NUMERIC and BIGINT return types
@ -162,8 +153,8 @@ mod tests {
#[tokio::test]
async fn test_ignored_files_aggregate_queries() {
let db = create_test_db().await;
let pool = db.get_pool();
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Create test user
let user_id = Uuid::new_v4();
@ -185,18 +176,20 @@ mod tests {
let file_id = Uuid::new_v4();
sqlx::query(
r#"
INSERT INTO ignored_files (id, ignored_by, filename, file_path, file_size, mime_type, source_type, reason)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
INSERT INTO ignored_files (id, ignored_by, filename, original_filename, file_path, file_size, mime_type, source_type, reason, file_hash)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
"#
)
.bind(file_id)
.bind(user_id)
.bind(format!("ignored_{}.pdf", i))
.bind(format!("ignored_{}.pdf", i)) // Add original_filename
.bind(format!("/test/ignored_{}.pdf", i))
.bind(1024i64 * (i + 1) as i64)
.bind("application/pdf")
.bind("source_sync")
.bind(Some("Test reason"))
.bind(format!("{:x}", Uuid::new_v4().as_u128())) // Add unique file_hash
.execute(pool)
.await
.unwrap();
@ -255,8 +248,8 @@ mod tests {
#[tokio::test]
async fn test_queue_enqueue_pending_sql_patterns() {
let db = create_test_db().await;
let pool = db.get_pool();
let ctx = TestContext::new().await;
let pool = ctx.state.db.get_pool();
// Test the SQL patterns from queue.rs that need Row trait
let pending_documents = sqlx::query(

View File

@ -10,8 +10,19 @@ mod tests {
#[tokio::test]
async fn test_list_users() {
let ctx = TestContext::new().await;
let db = &ctx.state.db;
// Create admin user using direct database approach
let admin_data = CreateUser {
username: "adminuser".to_string(),
email: "admin@example.com".to_string(),
password: "adminpass123".to_string(),
role: Some(UserRole::Admin),
};
let admin = db.create_user(admin_data).await.expect("Failed to create admin");
// Login using TestAuthHelper for token generation
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
@ -129,12 +140,29 @@ mod tests {
#[tokio::test]
async fn test_update_user() {
let ctx = TestContext::new().await;
let db = &ctx.state.db;
// Create admin user using direct database approach
let admin_data = CreateUser {
username: "adminuser".to_string(),
email: "admin@example.com".to_string(),
password: "adminpass123".to_string(),
role: Some(UserRole::Admin),
};
let admin = db.create_user(admin_data).await.expect("Failed to create admin");
// Login using TestAuthHelper for token generation
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 = auth_helper.create_test_user().await;
// Create a regular user using direct database approach
let user_data = CreateUser {
username: "testuser".to_string(),
email: "test@example.com".to_string(),
password: "password123".to_string(),
role: Some(UserRole::User),
};
let user = db.create_user(user_data).await.expect("Failed to create user");
let update_data = UpdateUser {
username: Some("updateduser".to_string()),
@ -146,7 +174,7 @@ mod tests {
.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()))
@ -169,12 +197,29 @@ mod tests {
#[tokio::test]
async fn test_update_user_password() {
let ctx = TestContext::new().await;
let db = &ctx.state.db;
// Create admin user using direct database approach
let admin_data = CreateUser {
username: "adminuser".to_string(),
email: "admin@example.com".to_string(),
password: "adminpass123".to_string(),
role: Some(UserRole::Admin),
};
let admin = db.create_user(admin_data).await.expect("Failed to create admin");
// Login using TestAuthHelper for token generation
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 = auth_helper.create_test_user().await;
// Create a regular user using direct database approach
let user_data = CreateUser {
username: "testuser".to_string(),
email: "test@example.com".to_string(),
password: "password123".to_string(),
role: Some(UserRole::User),
};
let user = db.create_user(user_data).await.expect("Failed to create user");
let update_data = UpdateUser {
username: None,
@ -187,7 +232,7 @@ mod tests {
.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()))