"""Integration tests for Lambda handler.""" from unittest.mock import MagicMock, Mock, patch import pymysql import pytest from src import app from src.enums import ( BatchStatus, BatchType, StatementPeriodStatus, TargetType, UploadStatus, UploadType, ) from src.errors import PermanentError from src.schemas import ( AdjustmentBatch, FileUpload, StatementPeriod, ) class TestHandler: """Tests for Lambda handler function.""" @patch('src.app.Repository') @patch('src.app.mysql_connection') def test_handler_success(self, mock_mysql_connection, mock_repository_class): """Test successful lambda handler execution.""" # Setup mocks mock_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_conn # Setup Repository mock mock_repository = Mock() mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_batch_by_file_upload.return_value = AdjustmentBatch( statement_period_id=456, batch_id=789, batch_status=BatchStatus.PENDING, batch_type=BatchType.UPLOAD, source_file_upload_id=123, ) mock_repository_class.return_value = mock_repository # Create test event event = { 'detail-type': 'file_upload.completed', 'detail': { 'metadata': { 'target_type': 'file_upload', 'target_id': 123, 'correlation_id': 'corr-456', }, 'data': {'upload_type': 'adjustments'}, }, } context = Mock() # Execute result = app.handler(event, context) # Verify assert result['detail_type'] == 'adjustment_batch.initialized' assert result['detail']['data']['s3_bucket'] == 'test-bucket' assert result['detail']['data']['s3_key'] == 'test.csv' assert result['detail']['metadata']['correlation_id'] == 'corr-456' assert result['detail']['metadata']['target_id'] == 789 assert ( result['detail']['metadata']['target_type'] == TargetType.WORKSHEET_ADJUSTMENT_BATCH ) # Verify instantiation and injection mock_repository_class.assert_called_once_with(mock_conn) @patch('src.app.mysql_connection') def test_handler_validation_error(self, mock_mysql_connection): """Test handler with invalid event raises PermanentError.""" event = {'invalid': 'event'} context = Mock() with pytest.raises(PermanentError): app.handler(event, context) @patch('src.app.Repository') @patch('src.app.mysql_connection') def test_handler_file_upload_not_found( self, mock_mysql_connection, mock_repository_class ): """Test handler with non-existent file_upload raises PermanentError.""" mock_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_conn mock_repository = Mock() mock_repository.get_file_upload.return_value = None mock_repository_class.return_value = mock_repository event = { 'detail-type': 'file_upload.completed', 'detail': { 'metadata': { 'target_type': 'file_upload', 'target_id': 999, }, 'data': {'upload_type': 'adjustments'}, }, } context = Mock() with pytest.raises(PermanentError) as exc_info: app.handler(event, context) assert 'File upload not found: file_upload_id=999' in str(exc_info.value) @patch('src.app.Repository') @patch('src.app.mysql_connection') def test_handler_invalid_upload_status( self, mock_mysql_connection, mock_repository_class ): """Test handler with invalid upload status raises PermanentError.""" mock_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_conn mock_repository = Mock() # Use model_construct to bypass validation for invalid enum value mock_repository.get_file_upload.return_value = FileUpload.model_construct( file_upload_id=123, s3_bucket='test-bucket', s3_key='test.csv', upload_status='pending', upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) mock_repository_class.return_value = mock_repository event = { 'detail-type': 'file_upload.completed', 'detail': { 'metadata': { 'target_type': 'file_upload', 'target_id': 123, }, 'data': {'upload_type': 'adjustments'}, }, } context = Mock() with pytest.raises(PermanentError) as exc_info: app.handler(event, context) assert 'Invalid upload status for file_upload_id=123' in str(exc_info.value) @patch('src.app.mysql_connection') def test_handler_transient_database_error(self, mock_mysql_connection): """Test handler with transient database error.""" mock_mysql_connection.side_effect = pymysql.err.OperationalError( 2003, "Can't connect to MySQL server" ) event = { 'detail-type': 'file_upload.completed', 'detail': { 'metadata': { 'target_type': 'file_upload', 'target_id': 123, }, 'data': {'upload_type': 'adjustments'}, }, } context = Mock() with pytest.raises(Exception): app.handler(event, context) @patch('src.app.Repository') @patch('src.app.mysql_connection') def test_handler_logs_processing_time( self, mock_mysql_connection, mock_repository_class ): """Test handler logs processing time.""" mock_conn = MagicMock() mock_mysql_connection.return_value.__enter__.return_value = mock_conn mock_repository = Mock() mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_batch_by_file_upload.return_value = AdjustmentBatch( batch_id=789, statement_period_id=456, batch_status=BatchStatus.PENDING, batch_type=BatchType.UPLOAD, source_file_upload_id=123, ) mock_repository_class.return_value = mock_repository event = { 'detail-type': 'file_upload.completed', 'detail': { 'metadata': { 'target_type': 'file_upload', 'target_id': 123, }, 'data': {'upload_type': 'adjustments'}, }, } context = Mock() with patch('src.app.logger') as mock_logger: app.handler(event, context) # Verify processing time was logged log_calls = [call.args[0] for call in mock_logger.info.call_args_list] assert any('Finished processing in' in call for call in log_calls)