""" Unit tests for app.py Flask routes """ import pytest import json from unittest.mock import Mock, patch, MagicMock from app import app @pytest.fixture def client(): """Create a test client for the Flask app.""" app.config['TESTING'] = True app.config['SECRET_KEY'] = 'test-secret-key' with app.test_client() as client: yield client @pytest.fixture def mock_session(): """Create a mock session.""" return {"user_id": "test_user_123"} class TestHealthCheck: """Test health check endpoint.""" def test_health_endpoint(self, client): """Test /health endpoint returns healthy status.""" response = client.get('/health') assert response.status_code == 200 data = json.loads(response.data) assert data['status'] == 'healthy' class TestIndexRoute: """Test index route.""" def test_index_returns_template(self, client): """Test index route returns 200.""" with patch('app.render_template') as mock_render: mock_render.return_value = 'Test' response = client.get('/') assert response.status_code == 200 class TestConfigurationAPI: """Test configuration API endpoints.""" def test_check_config_not_configured(self, client): """Test check_config returns false when not configured.""" response = client.get('/api/config/check') assert response.status_code == 200 data = json.loads(response.data) assert 'configured' in data def test_save_config_missing_credentials(self, client): """Test save_config fails without credentials.""" response = client.post( '/api/config/save', data=json.dumps({}), content_type='application/json' ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data @patch('app.credentials_storage') def test_save_config_success(self, mock_storage, client): """Test save_config succeeds with valid credentials.""" mock_storage.get_config.return_value = None mock_storage.save_config.return_value = None response = client.post( '/api/config/save', data=json.dumps({ 'client_id': 'test-client-id', 'client_secret': 'test-client-secret', 'tenant_id': 'common' }), content_type='application/json' ) assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True @patch('app.credentials_storage') def test_reset_config(self, mock_storage, client): """Test reset_config clears configuration.""" mock_storage.delete_config.return_value = None response = client.post('/api/config/reset') assert response.status_code == 200 data = json.loads(response.data) assert data['success'] is True class TestSharePointAPI: """Test SharePoint API endpoints.""" def test_list_connections_not_configured(self, client): """Test list_connections fails without configuration.""" response = client.get('/api/sharepoint/connections') assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data def test_get_sites_missing_connection_id(self, client): """Test get_sites requires valid connection_id.""" response = client.get('/api/sharepoint/invalid_conn/sites') assert response.status_code == 400 @patch('app.get_or_create_connector') def test_get_files_missing_site_id(self, mock_connector, client): """Test get_files requires site_id parameter.""" mock_connector.return_value = Mock() response = client.get('/api/sharepoint/test_conn/files') assert response.status_code == 400 data = json.loads(response.data) assert 'site_id is required' in data['error'] @patch('app.get_or_create_connector') def test_read_file_missing_parameters(self, mock_connector, client): """Test read_file requires site_id and file_path.""" mock_connector.return_value = Mock() response = client.get('/api/sharepoint/test_conn/read') assert response.status_code == 400 data = json.loads(response.data) assert 'required' in data['error'] class TestChatAPI: """Test chat API endpoints.""" def test_chat_send_missing_parameters(self, client): """Test chat_send requires all parameters.""" response = client.post( '/api/chat/send', data=json.dumps({}), content_type='application/json' ) assert response.status_code == 400 data = json.loads(response.data) assert 'required' in data['error'] def test_chat_send_no_document_loaded(self, client): """Test chat_send fails without loaded document.""" response = client.post( '/api/chat/send', data=json.dumps({ 'site_id': 'test_site', 'file_path': 'test.txt', 'message': 'Hello' }), content_type='application/json' ) assert response.status_code == 400 data = json.loads(response.data) assert 'No document loaded' in data['error'] def test_chat_history_missing_parameters(self, client): """Test chat_history requires parameters.""" response = client.get('/api/chat/history') assert response.status_code == 400 data = json.loads(response.data) assert 'required' in data['error'] def test_chat_clear_missing_parameters(self, client): """Test chat_clear requires parameters.""" response = client.post( '/api/chat/clear', data=json.dumps({}), content_type='application/json' ) assert response.status_code == 400 class TestLLMStatus: """Test LLM status endpoint.""" @patch('app.llm_client') def test_llm_status_available(self, mock_llm, client): """Test LLM status returns available when service is up.""" mock_llm.is_available.return_value = True response = client.get('/api/llm/status') assert response.status_code == 200 data = json.loads(response.data) assert data['available'] is True assert 'provider' in data @patch('app.llm_client') def test_llm_status_unavailable(self, mock_llm, client): """Test LLM status returns unavailable when service is down.""" mock_llm.is_available.side_effect = Exception("Connection failed") response = client.get('/api/llm/status') assert response.status_code == 200 data = json.loads(response.data) assert data['available'] is False assert 'error' in data class TestVectorStoreAPI: """Test vector store / multi-document chat API.""" @patch('app.vector_store', None) def test_add_document_vector_store_unavailable(self, client): """Test add_document fails when vector store is unavailable.""" response = client.post( '/api/documents/add', data=json.dumps({}), content_type='application/json' ) assert response.status_code == 503 data = json.loads(response.data) assert 'Vector store not available' in data['error'] @patch('app.vector_store', None) def test_list_tags_vector_store_unavailable(self, client): """Test list_tags fails when vector store is unavailable.""" response = client.get('/api/documents/tags') assert response.status_code == 503 @patch('app.vector_store', None) def test_chat_multi_vector_store_unavailable(self, client): """Test chat_multi fails when vector store is unavailable.""" response = client.post( '/api/chat/multi', data=json.dumps({'message': 'test'}), content_type='application/json' ) assert response.status_code == 503 class TestIndexingAPI: """Test background indexing API.""" @patch('app.vector_store', None) @patch('app.get_or_create_connector') def test_start_indexing_vector_store_unavailable(self, mock_connector, client): """Test start_indexing fails without vector store.""" mock_connector.return_value = Mock() response = client.post( '/api/indexing/start', data=json.dumps({}), content_type='application/json' ) assert response.status_code == 503 def test_get_indexing_status_not_found(self, client): """Test get_indexing_status returns 404 for unknown job.""" with patch('app.get_indexer') as mock_indexer: mock_indexer.return_value.get_job_status.return_value = None response = client.get('/api/indexing/status/unknown_job') assert response.status_code == 404 class TestErrorHandlers: """Test error handlers.""" def test_404_handler(self, client): """Test 404 error handler.""" response = client.get('/nonexistent-route') assert response.status_code == 404 data = json.loads(response.data) assert 'error' in data assert data['error'] == 'Not found' class TestDecorators: """Test authentication decorators.""" def test_require_auth_creates_user_id(self, client): """Test require_auth decorator creates user_id in session.""" with client.session_transaction() as sess: assert 'user_id' not in sess response = client.get('/') with client.session_transaction() as sess: assert 'user_id' in sess