background task

This commit is contained in:
2025-08-11 19:26:40 -07:00
parent cc1523cbb2
commit e9c976619a
15 changed files with 1474 additions and 2 deletions

View File

@@ -0,0 +1,113 @@
import pytest
from unittest.mock import Mock, patch, MagicMock
from flask_login import FlaskLoginClient
def test_trigger_email_processing_success(client, auth):
"""Test successful email processing trigger."""
# Login as a user
auth.login()
# Mock the EmailProcessor
with patch('app.routes.background_processing.EmailProcessor') as mock_processor:
mock_processor_instance = mock_processor.return_value
mock_processor_instance.process_user_emails.return_value = {
'success_count': 5,
'error_count': 0,
'processed_folders': []
}
# Make the request
response = client.post('/api/background/process-emails')
# Verify response
assert response.status_code == 200
json_data = response.get_json()
assert json_data['success'] is True
assert 'Processed 5 emails successfully' in json_data['message']
def test_trigger_email_processing_unauthorized(client):
"""Test email processing trigger without authentication."""
# Make the request without logging in
response = client.post('/api/background/process-emails')
# Verify response (should redirect to login)
assert response.status_code == 302 # Redirect to login
def test_trigger_folder_processing_success(client, auth, app):
"""Test successful folder processing trigger."""
# Login as a user
auth.login()
# Create a mock folder for the current user
with app.app_context():
from app.models import User, Folder
from app import db
# Get or create test user
user = User.query.filter_by(email='test@example.com').first()
if not user:
user = User(
first_name='Test',
last_name='User',
email='test@example.com',
password_hash='hashed_password'
)
db.session.add(user)
# Create test folder
folder = Folder(
user_id=user.id,
name='Test Folder',
rule_text='move to Archive',
priority=1
)
db.session.add(folder)
db.session.commit()
folder_id = folder.id
# Mock the EmailProcessor
with patch('app.routes.background_processing.EmailProcessor') as mock_processor:
mock_processor_instance = mock_processor.return_value
mock_processor_instance.process_folder_emails.return_value = {
'processed_count': 3,
'error_count': 0
}
# Make the request
response = client.post(f'/api/background/process-folder/{folder_id}')
# Verify response
assert response.status_code == 200
json_data = response.get_json()
assert json_data['success'] is True
assert 'Processed 3 emails for folder Test Folder' in json_data['message']
# Cleanup
with app.app_context():
from app.models import db, Folder
folder = Folder.query.get(folder_id)
if folder:
db.session.delete(folder)
db.session.commit()
def test_trigger_folder_processing_not_found(client, auth):
"""Test folder processing trigger with non-existent folder."""
# Login as a user
auth.login()
# Make the request with non-existent folder ID
response = client.post('/api/background/process-folder/999')
# Verify response
assert response.status_code == 404
json_data = response.get_json()
assert json_data['success'] is False
assert 'Folder not found or access denied' in json_data['error']
def test_trigger_folder_processing_unauthorized(client):
"""Test folder processing trigger without authentication."""
# Make the request without logging in
response = client.post('/api/background/process-folder/1')
# Verify response (should redirect to login)
assert response.status_code == 302 # Redirect to login

View File

@@ -0,0 +1,239 @@
import pytest
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime, timedelta
from app.email_processor import EmailProcessor
from app.models import User, Folder, ProcessedEmail
def test_email_processor_initialization():
"""Test that EmailProcessor initializes correctly."""
# Create a mock user
mock_user = Mock(spec=User)
mock_user.id = 1
mock_user.email = 'test@example.com'
mock_user.imap_config = {'username': 'user', 'password': 'pass'}
# Initialize processor
processor = EmailProcessor(mock_user)
# Verify initialization
assert processor.user == mock_user
assert processor.logger is not None
@patch('app.email_processor.EmailProcessor._process_email_batch')
def test_process_folder_emails_no_pending(mock_batch):
"""Test processing a folder with no pending emails."""
# Create mocks
mock_user = Mock(spec=User)
mock_user.id = 1
mock_folder = Mock(spec=Folder)
mock_folder.id = 1
mock_folder.name = 'Test Folder'
mock_folder.rule_text = 'move to Archive'
# Mock the processed emails service
with patch('app.email_processor.ProcessedEmailsService') as mock_service:
mock_service_instance = mock_service.return_value
mock_service_instance.get_pending_emails.return_value = []
# Initialize processor
processor = EmailProcessor(mock_user)
result = processor.process_folder_emails(mock_folder)
# Verify results
assert result['processed_count'] == 0
assert result['error_count'] == 0
assert mock_batch.called is False
@patch('app.email_processor.EmailProcessor._process_email_batch')
def test_process_folder_emails_with_pending(mock_batch):
"""Test processing a folder with pending emails."""
# Create mocks
mock_user = Mock(spec=User)
mock_user.id = 1
mock_folder = Mock(spec=Folder)
mock_folder.id = 1
mock_folder.name = 'Test Folder'
mock_folder.rule_text = 'move to Archive'
# Mock the processed emails service
with patch('app.email_processor.ProcessedEmailsService') as mock_service:
mock_service_instance = mock_service.return_value
mock_service_instance.get_pending_emails.return_value = ['1', '2', '3']
# Setup batch processing mock
mock_batch.return_value = {'processed_count': 3, 'error_count': 0}
# Initialize processor
processor = EmailProcessor(mock_user)
result = processor.process_folder_emails(mock_folder)
# Verify results
assert result['processed_count'] == 3
assert result['error_count'] == 0
mock_batch.assert_called_once()
@patch('app.email_processor.EmailProcessor._update_folder_counts')
def test_process_user_emails_no_folders(mock_update):
"""Test processing user emails with no folders to process."""
# Create mock user
mock_user = Mock(spec=User)
mock_user.id = 1
mock_user.email = 'test@example.com'
# Mock the database query
with patch('app.email_processor.Folder') as mock_folder:
mock_folder.query.filter_by.return_value.order_by.return_value.all.return_value = []
# Initialize processor
processor = EmailProcessor(mock_user)
result = processor.process_user_emails()
# Verify results
assert result['success_count'] == 0
assert result['error_count'] == 0
assert len(result['processed_folders']) == 0
mock_update.assert_not_called()
@patch('app.email_processor.EmailProcessor._update_folder_counts')
def test_process_user_emails_with_folders(mock_update):
"""Test processing user emails with folders to process."""
# Create mock user
mock_user = Mock(spec=User)
mock_user.id = 1
mock_user.email = 'test@example.com'
# Create mock folder
mock_folder = Mock(spec=Folder)
mock_folder.id = 1
mock_folder.name = 'Test Folder'
mock_folder.rule_text = 'move to Archive'
mock_folder.priority = 1
# Mock the database query
with patch('app.email_processor.Folder') as mock_folder_class:
mock_folder_class.query.filter_by.return_value.order_by.return_value.all.return_value = [mock_folder]
# Mock the process_folder_emails method
with patch('app.email_processor.EmailProcessor.process_folder_emails') as mock_process:
mock_process.return_value = {
'processed_count': 5,
'error_count': 0
}
# Initialize processor
processor = EmailProcessor(mock_user)
result = processor.process_user_emails()
# Verify results
assert result['success_count'] == 5
assert result['error_count'] == 0
assert len(result['processed_folders']) == 1
mock_process.assert_called_once()
@patch('app.email_processor.EmailProcessor._move_email')
def test_process_email_batch_success(mock_move):
"""Test processing an email batch successfully."""
# Create mocks
mock_user = Mock(spec=User)
mock_user.id = 1
mock_user.imap_config = {'username': 'user', 'password': 'pass'}
mock_folder = Mock(spec=Folder)
mock_folder.id = 1
mock_folder.name = 'Source'
mock_folder.rule_text = 'move to Archive'
# Mock IMAP service
with patch('app.email_processor.IMAPService') as mock_imap:
mock_imap_instance = mock_imap.return_value
mock_imap_instance._connect.return_value = None
mock_imap_instance.connection.login.return_value = ('OK', [])
mock_imap_instance.connection.select.return_value = ('OK', [])
mock_imap_instance.get_email_headers.return_value = {
'subject': 'Test Email',
'from': 'sender@example.com'
}
# Mock rule evaluation
with patch('app.email_processor.EmailProcessor._evaluate_rules') as mock_evaluate:
mock_evaluate.return_value = 'Archive'
mock_move.return_value = True
# Mock processed emails service
with patch('app.email_processor.ProcessedEmailsService') as mock_service:
mock_service_instance = mock_service.return_value
mock_service_instance.mark_emails_processed.return_value = 1
# Initialize processor
processor = EmailProcessor(mock_user)
result = processor._process_email_batch(mock_folder, ['1'])
# Verify results
assert result['processed_count'] == 1
assert result['error_count'] == 0
mock_move.assert_called_once()
def test_evaluate_rules_no_rule_text():
"""Test rule evaluation with no rule text."""
# Create mocks
mock_user = Mock(spec=User)
mock_folder = Mock(spec=Folder)
# Initialize processor
processor = EmailProcessor(mock_user)
# Test with None rule text
result = processor._evaluate_rules({'subject': 'Test'}, None)
assert result is None
# Test with empty rule text
result = processor._evaluate_rules({'subject': 'Test'}, '')
assert result is None
def test_evaluate_rules_with_move_to():
"""Test rule evaluation with 'move to' directive."""
# Create mocks
mock_user = Mock(spec=User)
mock_folder = Mock(spec=Folder)
# Initialize processor
processor = EmailProcessor(mock_user)
# Test with simple move to
result = processor._evaluate_rules({'subject': 'Test'}, 'move to Archive')
assert result == 'Archive'
# Test with punctuation
result = processor._evaluate_rules({'subject': 'Test'}, 'move to Archive.')
assert result == 'Archive'
# Test with extra spaces
result = processor._evaluate_rules({'subject': 'Test'}, 'move to Archive ')
assert result == 'Archive'
@patch('app.email_processor.ProcessedEmailsService')
def test_update_folder_counts(mock_service):
"""Test updating folder counts after processing."""
# Create mocks
mock_user = Mock(spec=User)
mock_folder = Mock(spec=Folder)
mock_folder.name = 'Test Folder'
mock_folder.pending_count = 5
mock_folder.total_count = 10
# Mock service methods
mock_service_instance = mock_service.return_value
mock_service_instance.get_pending_count.return_value = 3
with patch('app.email_processor.EmailProcessor._get_imap_connection') as mock_imap:
mock_imap.return_value.get_folder_email_count.return_value = 12
# Initialize processor
processor = EmailProcessor(mock_user)
processor._update_folder_counts(mock_folder)
# Verify counts were updated
assert mock_folder.pending_count == 3
assert mock_folder.total_count == 12

127
tests/test_scheduler.py Normal file
View File

@@ -0,0 +1,127 @@
import pytest
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime, timedelta
from threading import Thread
from app.scheduler import Scheduler
def test_scheduler_initialization():
"""Test that Scheduler initializes correctly."""
# Create a mock app
mock_app = Mock()
# Initialize scheduler
scheduler = Scheduler(mock_app, interval_minutes=10)
# Verify initialization
assert scheduler.app == mock_app
assert scheduler.interval == 600 # 10 minutes in seconds
assert scheduler.thread is None
assert scheduler.running is False
def test_scheduler_init_app():
"""Test that init_app method works correctly."""
# Create a mock app
mock_app = Mock()
mock_app.extensions = {}
# Initialize scheduler
scheduler = Scheduler()
scheduler.init_app(mock_app)
# Verify scheduler is stored in app extensions
assert 'scheduler' in mock_app.extensions
assert mock_app.extensions['scheduler'] == scheduler
def test_scheduler_start_stop():
"""Test that scheduler can be started and stopped."""
# Create a mock app
mock_app = Mock()
mock_app.app_context.return_value.__enter__.return_value = None
mock_app.app_context.return_value.__exit__.return_value = None
# Initialize scheduler
scheduler = Scheduler(mock_app)
# Start the scheduler
with patch('app.scheduler.Scheduler._run') as mock_run:
mock_run.side_effect = lambda: setattr(scheduler, 'running', False) # Stop after one iteration
scheduler.start()
# Give it a moment to start
import time
time.sleep(0.1)
# Verify thread was created and started
assert scheduler.thread is not None
assert scheduler.running is True
# Wait for the run method to complete
if scheduler.thread:
scheduler.thread.join(timeout=1)
# Stop should be called automatically when running becomes False
assert scheduler.running is False
def test_scheduler_process_all_users_no_users():
"""Test process_all_users with no users in database."""
# Create a mock app
mock_app = Mock()
mock_app.app_context.return_value.__enter__.return_value = None
mock_app.app_context.return_value.__exit__.return_value = None
# Initialize scheduler
scheduler = Scheduler(mock_app)
# Mock the User query
with patch('app.scheduler.User') as mock_user:
mock_user.query.all.return_value = []
# Call process_all_users
with patch('app.scheduler.Scheduler.logger') as mock_logger:
scheduler.process_all_users()
# Verify logger was called
mock_logger.info.assert_any_call("No users found for processing")
def test_scheduler_process_all_users_with_users():
"""Test process_all_users with users in database."""
# Create a mock app
mock_app = Mock()
mock_app.app_context.return_value.__enter__.return_value = None
mock_app.app_context.return_value.__exit__.return_value = None
# Initialize scheduler
scheduler = Scheduler(mock_app)
# Create mock users
mock_user1 = Mock()
mock_user1.id = 1
mock_user1.email = 'user1@example.com'
# Mock the User query
with patch('app.scheduler.User') as mock_user_class:
mock_user_class.query.all.return_value = [mock_user1]
# Mock the EmailProcessor
with patch('app.scheduler.EmailProcessor') as mock_processor:
mock_processor_instance = mock_processor.return_value
mock_processor_instance.process_user_emails.return_value = {
'success_count': 5,
'error_count': 0,
'processed_folders': []
}
# Call process_all_users
with patch('app.scheduler.Scheduler.logger') as mock_logger:
scheduler.process_all_users()
# Verify processor was called
mock_processor.assert_called_once_with(mock_user1)
mock_processor_instance.process_user_emails.assert_called_once()
# Verify logging
mock_logger.info.assert_any_call("Processing emails for 1 users")
mock_logger.info.assert_any_call(
f"Completed processing for user {mock_user1.email}: 5 success, 0 errors, 0 folders processed"
)