""" Unit tests for the BaseController class. This module tests the common functionality provided by the BaseController to ensure consistent behavior across all controllers. """ import pytest import logging from unittest.mock import Mock, patch, MagicMock from flask import HTTPException from pydantic import BaseModel, ValidationError # Import the BaseController and decorators import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src')) from server.web.controllers.base_controller import ( BaseController, handle_api_errors, require_auth, optional_auth, validate_json_input ) class MockPydanticModel(BaseModel): """Mock Pydantic model for testing validation.""" name: str age: int class TestBaseController: """Test cases for BaseController class.""" def setup_method(self): """Setup test fixtures.""" self.controller = BaseController() def test_initialization(self): """Test BaseController initialization.""" assert self.controller is not None assert hasattr(self.controller, 'logger') assert isinstance(self.controller.logger, logging.Logger) def test_handle_error(self): """Test error handling functionality.""" test_error = ValueError("Test error") result = self.controller.handle_error(test_error, 400) assert isinstance(result, HTTPException) assert result.status_code == 400 assert str(test_error) in str(result.detail) def test_handle_error_default_status_code(self): """Test error handling with default status code.""" test_error = RuntimeError("Runtime error") result = self.controller.handle_error(test_error) assert isinstance(result, HTTPException) assert result.status_code == 500 def test_validate_request_success(self): """Test successful request validation.""" mock_model = MockPydanticModel(name="John", age=25) result = self.controller.validate_request(mock_model) assert result is True def test_validate_request_failure(self): """Test failed request validation.""" # Create a mock that raises validation error mock_model = Mock() mock_model.side_effect = ValidationError("Validation failed", MockPydanticModel) with pytest.raises(Exception): self.controller.validate_request(mock_model) def test_format_response_basic(self): """Test basic response formatting.""" data = {"test": "value"} result = self.controller.format_response(data) expected = { "status": "success", "message": "Success", "data": data } assert result == expected def test_format_response_custom_message(self): """Test response formatting with custom message.""" data = {"user_id": 123} message = "User created successfully" result = self.controller.format_response(data, message) expected = { "status": "success", "message": message, "data": data } assert result == expected def test_format_error_response_basic(self): """Test basic error response formatting.""" message = "Error occurred" result, status_code = self.controller.format_error_response(message) expected = { "status": "error", "message": message, "error_code": 400 } assert result == expected assert status_code == 400 def test_format_error_response_with_details(self): """Test error response formatting with details.""" message = "Validation failed" details = {"field": "name", "error": "required"} result, status_code = self.controller.format_error_response(message, 422, details) expected = { "status": "error", "message": message, "error_code": 422, "details": details } assert result == expected assert status_code == 422 def test_create_success_response_minimal(self): """Test minimal success response creation.""" result, status_code = self.controller.create_success_response() expected = { "status": "success", "message": "Operation successful" } assert result == expected assert status_code == 200 def test_create_success_response_full(self): """Test full success response creation with all parameters.""" data = {"items": [1, 2, 3]} message = "Data retrieved" pagination = {"page": 1, "total": 100} meta = {"version": "1.0"} result, status_code = self.controller.create_success_response( data=data, message=message, status_code=201, pagination=pagination, meta=meta ) expected = { "status": "success", "message": message, "data": data, "pagination": pagination, "meta": meta } assert result == expected assert status_code == 201 def test_create_error_response_minimal(self): """Test minimal error response creation.""" message = "Something went wrong" result, status_code = self.controller.create_error_response(message) expected = { "status": "error", "message": message, "error_code": 400 } assert result == expected assert status_code == 400 def test_create_error_response_full(self): """Test full error response creation with all parameters.""" message = "Custom error" details = {"trace": "error trace"} error_code = "CUSTOM_ERROR" result, status_code = self.controller.create_error_response( message=message, status_code=422, details=details, error_code=error_code ) expected = { "status": "error", "message": message, "error_code": error_code, "details": details } assert result == expected assert status_code == 422 class TestHandleApiErrors: """Test cases for handle_api_errors decorator.""" @patch('server.web.controllers.base_controller.jsonify') def test_handle_value_error(self, mock_jsonify): """Test handling of ValueError.""" mock_jsonify.return_value = Mock() @handle_api_errors def test_function(): raise ValueError("Invalid input") result = test_function() mock_jsonify.assert_called_once() call_args = mock_jsonify.call_args[0][0] assert call_args['status'] == 'error' assert call_args['message'] == 'Invalid input data' assert call_args['error_code'] == 400 @patch('server.web.controllers.base_controller.jsonify') def test_handle_permission_error(self, mock_jsonify): """Test handling of PermissionError.""" mock_jsonify.return_value = Mock() @handle_api_errors def test_function(): raise PermissionError("Access denied") result = test_function() mock_jsonify.assert_called_once() call_args = mock_jsonify.call_args[0][0] assert call_args['status'] == 'error' assert call_args['message'] == 'Access denied' assert call_args['error_code'] == 403 @patch('server.web.controllers.base_controller.jsonify') def test_handle_file_not_found_error(self, mock_jsonify): """Test handling of FileNotFoundError.""" mock_jsonify.return_value = Mock() @handle_api_errors def test_function(): raise FileNotFoundError("File not found") result = test_function() mock_jsonify.assert_called_once() call_args = mock_jsonify.call_args[0][0] assert call_args['status'] == 'error' assert call_args['message'] == 'Resource not found' assert call_args['error_code'] == 404 @patch('server.web.controllers.base_controller.jsonify') @patch('server.web.controllers.base_controller.logging') def test_handle_generic_exception(self, mock_logging, mock_jsonify): """Test handling of generic exceptions.""" mock_jsonify.return_value = Mock() mock_logger = Mock() mock_logging.getLogger.return_value = mock_logger @handle_api_errors def test_function(): raise RuntimeError("Unexpected error") result = test_function() mock_jsonify.assert_called_once() call_args = mock_jsonify.call_args[0][0] assert call_args['status'] == 'error' assert call_args['message'] == 'Internal server error' assert call_args['error_code'] == 500 def test_handle_http_exception_reraise(self): """Test that HTTPExceptions are re-raised.""" @handle_api_errors def test_function(): raise HTTPException(status_code=404, detail="Not found") with pytest.raises(HTTPException): test_function() def test_successful_execution(self): """Test that successful functions execute normally.""" @handle_api_errors def test_function(): return "success" result = test_function() assert result == "success" class TestValidateJsonInput: """Test cases for validate_json_input decorator.""" @patch('server.web.controllers.base_controller.request') @patch('server.web.controllers.base_controller.jsonify') def test_non_json_request(self, mock_jsonify, mock_request): """Test handling of non-JSON requests.""" mock_request.is_json = False mock_jsonify.return_value = Mock() @validate_json_input(required_fields=['name']) def test_function(): return "success" result = test_function() mock_jsonify.assert_called_once() call_args = mock_jsonify.call_args[0][0] assert call_args['status'] == 'error' assert call_args['message'] == 'Request must contain JSON data' @patch('server.web.controllers.base_controller.request') @patch('server.web.controllers.base_controller.jsonify') def test_invalid_json(self, mock_jsonify, mock_request): """Test handling of invalid JSON.""" mock_request.is_json = True mock_request.get_json.return_value = None mock_jsonify.return_value = Mock() @validate_json_input(required_fields=['name']) def test_function(): return "success" result = test_function() mock_jsonify.assert_called_once() call_args = mock_jsonify.call_args[0][0] assert call_args['status'] == 'error' assert call_args['message'] == 'Invalid JSON data' @patch('server.web.controllers.base_controller.request') @patch('server.web.controllers.base_controller.jsonify') def test_missing_required_fields(self, mock_jsonify, mock_request): """Test handling of missing required fields.""" mock_request.is_json = True mock_request.get_json.return_value = {'age': 25} mock_jsonify.return_value = Mock() @validate_json_input(required_fields=['name', 'email']) def test_function(): return "success" result = test_function() mock_jsonify.assert_called_once() call_args = mock_jsonify.call_args[0][0] assert call_args['status'] == 'error' assert 'Missing required fields' in call_args['message'] @patch('server.web.controllers.base_controller.request') def test_successful_validation(self, mock_request): """Test successful validation with all required fields.""" mock_request.is_json = True mock_request.get_json.return_value = {'name': 'John', 'email': 'john@example.com'} @validate_json_input(required_fields=['name', 'email']) def test_function(): return "success" result = test_function() assert result == "success" @patch('server.web.controllers.base_controller.request') @patch('server.web.controllers.base_controller.jsonify') def test_field_validator_failure(self, mock_jsonify, mock_request): """Test field validator failure.""" mock_request.is_json = True mock_request.get_json.return_value = {'age': -5} mock_jsonify.return_value = Mock() def validate_age(value): return value > 0 @validate_json_input(age=validate_age) def test_function(): return "success" result = test_function() mock_jsonify.assert_called_once() call_args = mock_jsonify.call_args[0][0] assert call_args['status'] == 'error' assert 'Invalid value for field: age' in call_args['message']