""" 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'])