"""Lambda test module.""" from unittest.mock import Mock, patch import pytest from pydantic import ValidationError import app from file_upload_complete.schemas import ProcessorResponse class TestHandler: """Tests for Lambda handler function.""" @patch('app.FileUploadCompleteProcessor') @patch('app.FileUploadEvent') def test_handler_success(self, mock_event_class, mock_processor_class): """Test successful handler execution.""" # Setup mocks mock_event = Mock() mock_event_class.return_value = mock_event mock_processor = Mock() mock_processor_class.return_value = mock_processor mock_result = ProcessorResponse( status='completed', fileKey='test-key', apiResponse={'success': True} ) mock_processor.process.return_value = mock_result # Execute event_data = { 'detail': { 'bucket': {'name': 'test-bucket'}, 'object': {'key': 'test.txt'}, } } context = Mock() result = app.handler(event_data, context) # Verify mock_event_class.assert_called_once_with(**event_data) mock_processor_class.assert_called_once_with(mock_event) mock_processor.process.assert_called_once() assert result == { 'status': 'completed', 'fileKey': 'test-key', 'apiResponse': {'success': True}, } @patch('app.logger') @patch('app.FileUploadCompleteProcessor') @patch('app.FileUploadEvent') def test_handler_logs_event( self, mock_event_class, mock_processor_class, mock_logger ): """Test handler logs event information.""" mock_event = Mock() mock_event_class.return_value = mock_event mock_processor = Mock() mock_processor_class.return_value = mock_processor mock_processor.process.return_value = ProcessorResponse( status='completed', fileKey='key', apiResponse={} ) event_data = { 'detail': { 'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}, } } app.handler(event_data, Mock()) # Verify logging assert mock_logger.info.called log_calls = [call.args[0] for call in mock_logger.info.call_args_list] assert any('Event:' in str(call) for call in log_calls) @patch('app.logger') @patch('app.FileUploadEvent') def test_handler_invalid_event_structure(self, mock_event_class, mock_logger): """Test handler with invalid event structure.""" mock_event_class.side_effect = ValidationError.from_exception_data( 'FileUploadEvent', [ { 'type': 'missing', 'loc': ('detail',), 'msg': 'Field required', 'input': {}, } ], ) event_data = {'invalid': 'structure'} with pytest.raises(ValidationError): app.handler(event_data, Mock()) # Verify error logging assert mock_logger.error.called @patch('app.logger') @patch('app.FileUploadCompleteProcessor') @patch('app.FileUploadEvent') def test_handler_processor_exception( self, mock_event_class, mock_processor_class, mock_logger ): """Test handler when processor raises exception.""" mock_event = Mock() mock_event_class.return_value = mock_event mock_processor = Mock() mock_processor_class.return_value = mock_processor mock_processor.process.side_effect = Exception('Processing failed') event_data = { 'detail': { 'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}, } } with pytest.raises(Exception) as exc_info: app.handler(event_data, Mock()) assert 'Processing failed' in str(exc_info.value) assert mock_logger.exception.called @patch('app.logger') @patch('app.FileUploadCompleteProcessor') @patch('app.FileUploadEvent') def test_handler_logs_processing_time( self, mock_event_class, mock_processor_class, mock_logger ): """Test handler logs processing time.""" mock_event = Mock() mock_event_class.return_value = mock_event mock_processor = Mock() mock_processor_class.return_value = mock_processor mock_processor.process.return_value = ProcessorResponse( status='completed', fileKey='key', apiResponse={} ) event_data = { 'detail': { 'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}, } } app.handler(event_data, Mock()) # Verify processing time logging log_calls = [call.args[0] for call in mock_logger.info.call_args_list] assert any('Finished processing' in str(call) for call in log_calls) @patch('app.FileUploadCompleteProcessor') @patch('app.FileUploadEvent') def test_handler_with_quarantine_action( self, mock_event_class, mock_processor_class ): """Test handler with quarantine action.""" mock_event = Mock() mock_event_class.return_value = mock_event mock_processor = Mock() mock_processor_class.return_value = mock_processor mock_processor.process.return_value = ProcessorResponse( status='quarantined', fileKey='infected-key', apiResponse={} ) event_data = { 'action': 'quarantine', 'detail': { 'bucket': {'name': 'bucket'}, 'object': {'key': 'infected.exe'}, }, } result = app.handler(event_data, Mock()) assert result['status'] == 'quarantined' assert result['fileKey'] == 'infected-key' @patch('app.FileUploadCompleteProcessor') @patch('app.FileUploadEvent') def test_handler_returns_dict(self, mock_event_class, mock_processor_class): """Test handler returns dict format.""" mock_event = Mock() mock_event_class.return_value = mock_event mock_processor = Mock() mock_processor_class.return_value = mock_processor mock_processor.process.return_value = ProcessorResponse( status='completed', fileKey='key', apiResponse={'data': 'test'} ) event_data = { 'detail': { 'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}, } } result = app.handler(event_data, Mock()) assert isinstance(result, dict) assert 'status' in result assert 'fileKey' in result assert 'apiResponse' in result @patch('app.FileUploadCompleteProcessor') @patch('app.FileUploadEvent') def test_handler_with_context_object(self, mock_event_class, mock_processor_class): """Test handler accepts context object.""" mock_event = Mock() mock_event_class.return_value = mock_event mock_processor = Mock() mock_processor_class.return_value = mock_processor mock_processor.process.return_value = ProcessorResponse( status='completed', fileKey='key', apiResponse={} ) event_data = { 'detail': { 'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}, } } context = Mock() context.request_id = 'test-request-id' context.function_name = 'file-upload-complete' result = app.handler(event_data, context) assert result is not None # Context is passed but not necessarily used in current implementation @patch('app.logger') @patch('app.FileUploadEvent') def test_handler_validation_error_logging(self, mock_event_class, mock_logger): """Test handler logs validation error details.""" validation_error = ValidationError.from_exception_data( 'FileUploadEvent', [ { 'type': 'missing', 'loc': ('detail', 'bucket'), 'msg': 'Field required', 'input': {'detail': {}}, } ], ) mock_event_class.side_effect = validation_error with pytest.raises(ValidationError): app.handler({'detail': {}}, Mock()) # Verify error logging includes validation details assert mock_logger.error.called error_call = mock_logger.error.call_args[0][0] assert 'Invalid event structure' in error_call @patch('app.FileUploadCompleteProcessor') @patch('app.FileUploadEvent') def test_handler_with_complex_api_response( self, mock_event_class, mock_processor_class ): """Test handler with complex nested API response.""" mock_event = Mock() mock_event_class.return_value = mock_event mock_processor = Mock() mock_processor_class.return_value = mock_processor complex_response = { 'file': { 'id': '123', 'status': 'processed', 'metadata': {'size': 1024, 'type': 'csv'}, }, 'timestamp': '2024-01-01T12:00:00Z', } mock_processor.process.return_value = ProcessorResponse( status='completed', fileKey='key', apiResponse=complex_response ) event_data = { 'detail': { 'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}, } } result = app.handler(event_data, Mock()) assert result['apiResponse'] == complex_response assert result['apiResponse']['file']['id'] == '123' class TestSentryIntegration: """Tests for Sentry integration.""" @patch('app.config.SENTRY_DSN', 'https://test@sentry.io/123') @patch('app.sentry_sdk.init') def test_sentry_initialization_with_dsn(self, mock_sentry_init): """Test Sentry is initialized when DSN is configured.""" # Note: This would need to be tested by reloading the app module # or by verifying the initialization at module import time pass @patch('app.config.SENTRY_DSN', None) def test_sentry_not_initialized_without_dsn(self): """Test Sentry is not initialized when DSN is not configured.""" # Note: This verifies config behavior when DSN is None pass