390 lines
13 KiB
Python
390 lines
13 KiB
Python
"""
|
|
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'] |