307 lines
13 KiB
Python
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 |