""" Tests for the Flask API routes. """ import json import pytest from unittest.mock import Mock, patch from datetime import datetime, timezone from api_routes import QueueManagerAPI from models import QueueItem, QueueState, QueueStatus from queue_service import QueueService, QueueServiceError class TestQueueManagerAPI: """Test cases for the QueueManagerAPI class.""" @pytest.fixture def mock_queue_service(self): """Create a mock queue service for testing.""" service = Mock(spec=QueueService) service.get_queue_state.return_value = QueueState.RUNNING return service @pytest.fixture def api(self, mock_queue_service): """Create a QueueManagerAPI instance for testing.""" return QueueManagerAPI(queue_service=mock_queue_service) @pytest.fixture def client(self, api): """Create a test client for the Flask app.""" api.app.config['TESTING'] = True return api.app.test_client() def test_api_initialization(self, mock_queue_service): """Test API initialization.""" api = QueueManagerAPI(queue_service=mock_queue_service) assert api.queue_service == mock_queue_service assert api.app is not None assert api.app.config['JSON_SORT_KEYS'] is False assert api.app.config['JSONIFY_PRETTYPRINT_REGULAR'] is True def test_api_initialization_without_service(self): """Test API initialization without providing a queue service.""" with patch('api_routes.QueueService') as mock_service_class: mock_service = Mock() mock_service_class.return_value = mock_service api = QueueManagerAPI() assert api.queue_service == mock_service mock_service_class.assert_called_once() def test_health_check_healthy(self, client, mock_queue_service): """Test health check endpoint when service is healthy.""" mock_queue_service.get_queue_state.return_value = QueueState.RUNNING response = client.get('/health') assert response.status_code == 200 data = json.loads(response.data) assert data['status'] == 'healthy' assert data['queue_state'] == 'running' assert data['version'] == '1.0.0' assert 'timestamp' in data def test_health_check_unhealthy(self, client, mock_queue_service): """Test health check endpoint when service is unhealthy.""" mock_queue_service.get_queue_state.side_effect = QueueServiceError("Database error") response = client.get('/health') assert response.status_code == 503 data = json.loads(response.data) assert data['status'] == 'unhealthy' assert 'error' in data assert 'timestamp' in data def test_serve_index_success(self, client): """Test serving index.html successfully.""" with patch('api_routes.send_from_directory') as mock_send: mock_send.return_value = "Test" response = client.get('/') assert response.status_code == 200 mock_send.assert_called_once() def test_serve_index_alternative_route(self, client): """Test serving index.html via /index.html route.""" with patch('api_routes.send_from_directory') as mock_send: mock_send.return_value = "Test" response = client.get('/index.html') assert response.status_code == 200 mock_send.assert_called_once() def test_serve_index_error(self, client): """Test error handling when serving index.html fails.""" with patch('api_routes.send_from_directory') as mock_send: mock_send.side_effect = FileNotFoundError("File not found") response = client.get('/') assert response.status_code == 500 data = json.loads(response.data) assert data['error'] == 'Failed to serve index page' def test_error_handler_queue_service_error(self, client, mock_queue_service): """Test error handler for QueueServiceError.""" mock_queue_service.get_queue_state.side_effect = QueueServiceError("Test error") response = client.get('/health') assert response.status_code == 500 data = json.loads(response.data) assert data['error'] == 'Queue service error' assert data['message'] == 'Test error' assert 'timestamp' in data def test_error_handler_bad_request(self, client): """Test error handler for BadRequest.""" # This will be tested more thoroughly when we add endpoints that validate input response = client.post('/nonexistent', data="invalid json", content_type='application/json') assert response.status_code == 404 # Not found takes precedence def test_error_handler_not_found(self, client): """Test error handler for NotFound.""" response = client.get('/nonexistent') assert response.status_code == 404 data = json.loads(response.data) assert data['error'] == 'Not found' assert data['message'] == 'The requested resource was not found' assert 'timestamp' in data def test_validate_json_request_valid(self, api): """Test JSON request validation with valid data.""" with api.app.test_request_context('/test', json={'key': 'value'}): data = api._validate_json_request() assert data == {'key': 'value'} def test_validate_json_request_not_json(self, api): """Test JSON request validation with non-JSON request.""" with api.app.test_request_context('/test', data='not json'): with pytest.raises(Exception): # BadRequest api._validate_json_request() def test_validate_json_request_invalid_json(self, api): """Test JSON request validation with invalid JSON.""" with api.app.test_request_context('/test', data='{"invalid": json}', content_type='application/json'): with pytest.raises(Exception): # BadRequest api._validate_json_request() def test_parse_queue_filter_empty(self, api): """Test parsing empty filter parameters.""" filter_criteria = api._parse_queue_filter({}) assert filter_criteria.status is None assert filter_criteria.workflow_name is None assert filter_criteria.search_term is None assert filter_criteria.date_range is None def test_parse_queue_filter_status_single(self, api): """Test parsing single status filter.""" filter_criteria = api._parse_queue_filter({'status': 'pending'}) assert filter_criteria.status == [QueueStatus.PENDING] def test_parse_queue_filter_status_multiple(self, api): """Test parsing multiple status filters.""" filter_criteria = api._parse_queue_filter({'status': ['pending', 'running']}) assert filter_criteria.status == [QueueStatus.PENDING, QueueStatus.RUNNING] def test_parse_queue_filter_status_invalid(self, api): """Test parsing invalid status filter.""" with pytest.raises(Exception): # BadRequest api._parse_queue_filter({'status': 'invalid_status'}) def test_parse_queue_filter_workflow_name(self, api): """Test parsing workflow name filter.""" filter_criteria = api._parse_queue_filter({'workflow_name': 'test_workflow'}) assert filter_criteria.workflow_name == 'test_workflow' def test_parse_queue_filter_search(self, api): """Test parsing search term filter.""" filter_criteria = api._parse_queue_filter({'search': 'test search'}) assert filter_criteria.search_term == 'test search' def test_parse_queue_filter_date_range(self, api): """Test parsing date range filter.""" date_from = '2023-01-01T00:00:00' date_to = '2023-12-31T23:59:59' filter_criteria = api._parse_queue_filter({ 'date_from': date_from, 'date_to': date_to }) assert filter_criteria.date_range is not None assert filter_criteria.date_range[0] == datetime.fromisoformat(date_from) assert filter_criteria.date_range[1] == datetime.fromisoformat(date_to) def test_parse_queue_filter_date_range_invalid(self, api): """Test parsing invalid date range filter.""" with pytest.raises(Exception): # BadRequest api._parse_queue_filter({'date_from': 'invalid_date'}) def test_get_app(self, api): """Test getting the Flask app instance.""" app = api.get_app() assert app == api.app @patch('api_routes.logger') def test_logging_on_initialization(self, mock_logger, mock_queue_service): """Test that initialization logs are created.""" QueueManagerAPI(queue_service=mock_queue_service) mock_logger.info.assert_called_with("Queue Manager API initialized") @patch('api_routes.logger') def test_logging_on_health_check_failure(self, mock_logger, client, mock_queue_service): """Test that health check failures are logged.""" error_msg = "Database connection failed" mock_queue_service.get_queue_state.side_effect = Exception(error_msg) client.get('/health') mock_logger.error.assert_called() def test_flask_config(self, api): """Test Flask configuration settings.""" assert api.app.config['JSON_SORT_KEYS'] is False assert api.app.config['JSONIFY_PRETTYPRINT_REGULAR'] is True assert api.app.static_folder == 'web' assert api.app.static_url_path == '/static' class TestQueueManagerAPIIntegration: """Integration tests for the QueueManagerAPI.""" @pytest.fixture def real_api(self): """Create a real API instance with mocked dependencies.""" with patch('api_routes.QueueService') as mock_service_class: mock_service = Mock() mock_service.get_queue_state.return_value = QueueState.RUNNING mock_service_class.return_value = mock_service api = QueueManagerAPI() return api, mock_service def test_full_health_check_flow(self, real_api): """Test the complete health check flow.""" api, mock_service = real_api client = api.app.test_client() response = client.get('/health') assert response.status_code == 200 mock_service.get_queue_state.assert_called_once() def test_static_file_serving_setup(self, real_api): """Test that static file serving is properly configured.""" api, _ = real_api # Check that static folder is configured assert api.app.static_folder == 'web' assert api.app.static_url_path == '/static' # Check that routes are registered rules = [rule.rule for rule in api.app.url_map.iter_rules()] assert '/' in rules assert '/index.html' in rules assert '/health' in rules