background task
This commit is contained in:
113
tests/test_background_processing_routes.py
Normal file
113
tests/test_background_processing_routes.py
Normal 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
|
||||
239
tests/test_email_processor.py
Normal file
239
tests/test_email_processor.py
Normal 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
127
tests/test_scheduler.py
Normal 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"
|
||||
)
|
||||
Reference in New Issue
Block a user