cleanup contollers
This commit is contained in:
390
tests/unit/controllers/test_base_controller.py
Normal file
390
tests/unit/controllers/test_base_controller.py
Normal file
@@ -0,0 +1,390 @@
|
||||
"""
|
||||
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']
|
||||
Reference in New Issue
Block a user