Files
email-organizer/tests/functional/test_ai_rule_user_flow.py
2025-08-11 06:37:24 -07:00

307 lines
13 KiB
Python

import pytest
from unittest.mock import patch, Mock
from app import create_app, db
from app.models import User, Folder, AIRuleCache
from bs4 import BeautifulSoup
class TestAIRuleUserFlow:
"""Test cases for the complete AI rule generation user flow."""
@pytest.fixture
def app(self):
"""Create and configure a test app."""
app = create_app('testing')
with app.app_context():
db.create_all()
yield app
db.drop_all()
@pytest.fixture
def client(self, app):
"""Create a test client."""
return app.test_client()
@pytest.fixture
def user(self, app):
"""Create a test user."""
with app.app_context():
user = User(
first_name='Test',
last_name='User',
email='test@example.com',
password_hash='hashed_password'
)
db.session.add(user)
db.session.commit()
return user
def test_folder_creation_modal_with_ai_controls(self, client, user):
"""Test that folder creation modal includes AI controls."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
response = client.get('/api/folders/new')
assert response.status_code == 200
# Check that AI controls are present
assert b'Generate Rule' in response.data
assert b'Multiple Options' in response.data
assert b'generate-rule' in response.data
def test_ai_rule_generation_in_modal(self, client, user):
"""Test AI rule generation within the folder creation modal."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
with patch('app.routes.folders.ai_service') as mock_ai_service:
# Mock AI service response
mock_ai_service.generate_multiple_rules.return_value = (
[{'text': "Move emails from 'boss@company.com' to this folder", 'quality_score': 85}],
{'total_generated': 1}
)
# Simulate AI rule generation request
response = client.post('/api/folders/generate-rule', data={
'folder_name': 'Work',
'folder_type': 'destination',
'rule_type': 'multiple'
})
assert response.status_code == 200
# Check that the response contains AI-generated rule
assert b'Move emails from' in response.data
assert b'generated-rule-text' in response.data
assert b'85%' in response.data
def test_multiple_rule_options_in_modal(self, client, user):
"""Test multiple rule options within the folder creation modal."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
with patch('app.routes.folders.ai_service') as mock_ai_service:
# Mock AI service response
mock_ai_service.generate_multiple_rules.return_value = (
[
{'text': 'Move emails from boss@company.com', 'quality_score': 85},
{'text': 'Move emails with urgent subject', 'quality_score': 75},
{'text': 'Move emails from team members', 'quality_score': 70}
],
{'total_generated': 3}
)
# Simulate multiple rule generation request
response = client.post('/api/folders/generate-rule', data={
'folder_name': 'Work',
'folder_type': 'destination',
'rule_type': 'multiple'
})
assert response.status_code == 200
# Check that multiple rules are displayed
assert b'Move emails from boss@company.com' in response.data
assert b'Move emails with urgent subject' in response.data
assert b'Move emails from team members' in response.data
assert b'85%' in response.data
assert b'75%' in response.data
assert b'70%' in response.data
def test_folder_creation_with_ai_rule(self, client, user):
"""Test folder creation using AI-generated rule."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
with patch('app.routes.folders.ai_service') as mock_ai_service:
# Mock AI service response
mock_ai_service.generate_multiple_rules.return_value = (
[{'text': "Move emails from 'newsletter@company.com' to this folder", 'quality_score': 90}],
{'total_generated': 1}
)
# First, generate the rule
response = client.post('/api/folders/generate-rule', data={
'folder_name': 'Newsletters',
'folder_type': 'destination',
'rule_type': 'multiple'
})
assert response.status_code == 200
# Then create folder with the generated rule
# We need to extract the rule from the response and use it
soup = BeautifulSoup(response.data, 'html.parser')
rule_text = soup.find(id='generated-rule-text').text.strip()
response = client.post('/api/folders', data={
'name': 'Newsletters',
'rule_text': rule_text,
'priority': '0'
})
assert response.status_code == 201
# Check that folder was created
folder = Folder.query.filter_by(name='Newsletters', user_id=user.id).first()
assert folder is not None
assert folder.rule_text == rule_text
def test_rule_quality_assessment(self, client, user):
"""Test rule quality assessment functionality."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
with patch('app.routes.folders.ai_service') as mock_ai_service:
# Mock AI service response
mock_ai_service.assess_rule_quality.return_value = {
'score': 75,
'grade': 'good',
'feedback': 'Good rule with room for improvement',
'assessed_at': '2023-01-01T00:00:00'
}
response = client.post('/api/folders/assess-rule', data={
'rule_text': 'Move emails from boss@company.com to this folder',
'folder_name': 'Work',
'folder_type': 'destination'
})
assert response.status_code == 200
# Check that quality assessment is displayed
assert b'75%' in response.data
assert b'generated-rule-text' in response.data
def test_error_handling_in_modal(self, client, user):
"""Test error handling within the modal."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
# Test with invalid inputs
response = client.post('/api/folders/generate-rule', data={
'folder_type': 'destination',
'rule_type': 'multiple'
})
assert response.status_code == 200
# Check that error is displayed in modal format
assert b'Folder name is required' in response.data
def test_fallback_rule_generation(self, client, user):
"""Test fallback rule generation when AI service fails."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
with patch('app.routes.folders.ai_service') as mock_ai_service:
# Mock AI service failure
mock_ai_service.generate_multiple_rules.return_value = (None, {'error': 'Service unavailable'})
mock_ai_service.get_fallback_rule.return_value = 'Move emails containing "Work" to this folder'
response = client.post('/api/folders/generate-rule', data={
'folder_name': 'Work',
'folder_type': 'destination',
'rule_type': 'multiple'
})
assert response.status_code == 200
# Check that fallback rule is displayed
assert b'Move emails containing "Work" to this folder' in response.data
assert b'AI service unavailable' in response.data
def test_cache_usage_indicator(self, client, user):
"""Test that cache usage is properly indicated."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
# Create a cache entry
from datetime import datetime
cache_entry = AIRuleCache(
user_id=user.id,
folder_name='Work',
folder_type='destination',
rule_text='Cached rule',
rule_metadata={'quality_score': 90},
cache_key='test-key',
expires_at=datetime(2023, 12, 31, 23, 59, 59), # Future expiration
is_active=True
)
db.session.add(cache_entry)
db.session.commit()
with patch('app.routes.folders.ai_service') as mock_ai_service:
# Make AI service return different rule to verify cache is used
mock_ai_service.generate_single_rule.return_value = (
'New rule',
{'quality_score': 95}
)
response = client.post('/api/folders/generate-rule', data={
'folder_name': 'Work',
'folder_type': 'destination',
'rule_type': 'single'
})
assert response.status_code == 200
# Check that cached rule is used
assert b'Using cached rule' in response.data
assert b'Cached rule' in response.data
# New rule should not appear
assert b'New rule' not in response.data
def test_keyboard_navigation_support(self, client, user):
"""Test that keyboard navigation is supported."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
response = client.get('/api/folders/new')
assert response.status_code == 200
# Check that buttons have proper ARIA labels
assert b'aria-label' in response.data
assert b'Generate AI-powered email rule' in response.data
assert b'Generate multiple AI-powered email rule options' in response.data
def test_screen_reader_support(self, client, user):
"""Test that screen reader support is implemented."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
with patch('app.routes.folders.ai_service') as mock_ai_service:
# Mock AI service response
mock_ai_service.generate_single_rule.return_value = (
"Move emails from 'boss@company.com' to this folder",
{'quality_score': 85, 'model_used': 'test-model'}
)
response = client.post('/api/folders/generate-rule', data={
'folder_name': 'Work',
'folder_type': 'destination',
'rule_type': 'single'
})
assert response.status_code == 200
# Check that screen reader support is present
assert b'role="status"' in response.data
assert b'aria-live="polite"' in response.data
assert b'sr-only' in response.data
def test_loading_states(self, client, user):
"""Test that loading states are properly handled."""
# Simulate logged-in user by setting session
with client.session_transaction() as sess:
sess['user_id'] = user.id
response = client.get('/api/folders/new')
assert response.status_code == 200
# Check that loading states are configured
assert b'data-loading-disable' in response.data
assert b'data-loading-class' in response.data
assert b'loading-spinner' in response.data