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