194 lines
8.0 KiB
Python
194 lines
8.0 KiB
Python
"""
|
|
Unit tests for custom exception handler
|
|
Tests all exception types and status code mappings
|
|
"""
|
|
from django.test import TestCase, RequestFactory
|
|
from django.http import HttpRequest
|
|
from rest_framework import status
|
|
from rest_framework.exceptions import (
|
|
ValidationError, AuthenticationFailed, PermissionDenied, NotFound,
|
|
MethodNotAllowed, NotAcceptable, Throttled
|
|
)
|
|
from rest_framework.views import APIView
|
|
from igny8_core.api.exception_handlers import custom_exception_handler
|
|
|
|
|
|
class ExceptionHandlerTestCase(TestCase):
|
|
"""Test cases for custom exception handler"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
self.factory = RequestFactory()
|
|
self.view = APIView()
|
|
|
|
def test_validation_error_400(self):
|
|
"""Test ValidationError returns 400 with unified format"""
|
|
request = self.factory.post('/test/', {})
|
|
exc = ValidationError({"field": ["This field is required"]})
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
self.assertFalse(response.data['success'])
|
|
self.assertIn('error', response.data)
|
|
self.assertIn('errors', response.data)
|
|
self.assertIn('request_id', response.data)
|
|
|
|
def test_authentication_failed_401(self):
|
|
"""Test AuthenticationFailed returns 401 with unified format"""
|
|
request = self.factory.get('/test/')
|
|
exc = AuthenticationFailed("Authentication required")
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
self.assertFalse(response.data['success'])
|
|
self.assertEqual(response.data['error'], 'Authentication required')
|
|
self.assertIn('request_id', response.data)
|
|
|
|
def test_permission_denied_403(self):
|
|
"""Test PermissionDenied returns 403 with unified format"""
|
|
request = self.factory.get('/test/')
|
|
exc = PermissionDenied("Permission denied")
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
self.assertFalse(response.data['success'])
|
|
self.assertEqual(response.data['error'], 'Permission denied')
|
|
self.assertIn('request_id', response.data)
|
|
|
|
def test_not_found_404(self):
|
|
"""Test NotFound returns 404 with unified format"""
|
|
request = self.factory.get('/test/')
|
|
exc = NotFound("Resource not found")
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
|
self.assertFalse(response.data['success'])
|
|
self.assertEqual(response.data['error'], 'Resource not found')
|
|
self.assertIn('request_id', response.data)
|
|
|
|
def test_throttled_429(self):
|
|
"""Test Throttled returns 429 with unified format"""
|
|
request = self.factory.get('/test/')
|
|
exc = Throttled()
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)
|
|
self.assertFalse(response.data['success'])
|
|
self.assertEqual(response.data['error'], 'Rate limit exceeded')
|
|
self.assertIn('request_id', response.data)
|
|
|
|
def test_method_not_allowed_405(self):
|
|
"""Test MethodNotAllowed returns 405 with unified format"""
|
|
request = self.factory.post('/test/')
|
|
exc = MethodNotAllowed("POST")
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
|
|
self.assertFalse(response.data['success'])
|
|
self.assertIn('error', response.data)
|
|
self.assertIn('request_id', response.data)
|
|
|
|
def test_unhandled_exception_500(self):
|
|
"""Test unhandled exception returns 500 with unified format"""
|
|
request = self.factory.get('/test/')
|
|
exc = ValueError("Unexpected error")
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
self.assertFalse(response.data['success'])
|
|
self.assertEqual(response.data['error'], 'Internal server error')
|
|
self.assertIn('request_id', response.data)
|
|
|
|
def test_exception_handler_includes_request_id(self):
|
|
"""Test exception handler includes request_id in response"""
|
|
request = self.factory.get('/test/')
|
|
request.request_id = 'test-request-id-exception'
|
|
exc = ValidationError("Test error")
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertIn('request_id', response.data)
|
|
self.assertEqual(response.data['request_id'], 'test-request-id-exception')
|
|
|
|
def test_exception_handler_debug_mode(self):
|
|
"""Test exception handler includes debug info in DEBUG mode"""
|
|
from django.conf import settings
|
|
original_debug = settings.DEBUG
|
|
|
|
try:
|
|
settings.DEBUG = True
|
|
request = self.factory.get('/test/')
|
|
exc = ValueError("Test error")
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertIn('debug', response.data)
|
|
self.assertIn('exception_type', response.data['debug'])
|
|
self.assertIn('exception_message', response.data['debug'])
|
|
self.assertIn('view', response.data['debug'])
|
|
self.assertIn('path', response.data['debug'])
|
|
self.assertIn('method', response.data['debug'])
|
|
finally:
|
|
settings.DEBUG = original_debug
|
|
|
|
def test_exception_handler_no_debug_mode(self):
|
|
"""Test exception handler excludes debug info when DEBUG=False"""
|
|
from django.conf import settings
|
|
original_debug = settings.DEBUG
|
|
|
|
try:
|
|
settings.DEBUG = False
|
|
request = self.factory.get('/test/')
|
|
exc = ValueError("Test error")
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertNotIn('debug', response.data)
|
|
finally:
|
|
settings.DEBUG = original_debug
|
|
|
|
def test_field_specific_validation_errors(self):
|
|
"""Test field-specific validation errors are included"""
|
|
request = self.factory.post('/test/', {})
|
|
exc = ValidationError({
|
|
"email": ["Invalid email format"],
|
|
"password": ["Password too short", "Password must contain numbers"]
|
|
})
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertIn('errors', response.data)
|
|
self.assertIn('email', response.data['errors'])
|
|
self.assertIn('password', response.data['errors'])
|
|
self.assertEqual(len(response.data['errors']['password']), 2)
|
|
|
|
def test_non_field_validation_errors(self):
|
|
"""Test non-field validation errors are handled"""
|
|
request = self.factory.post('/test/', {})
|
|
exc = ValidationError({"non_field_errors": ["General validation error"]})
|
|
context = {'request': request, 'view': self.view}
|
|
|
|
response = custom_exception_handler(exc, context)
|
|
|
|
self.assertIn('errors', response.data)
|
|
self.assertIn('non_field_errors', response.data['errors'])
|
|
|