"""Unit testcases for FileUploadCompleteProcessor.""" from unittest.mock import Mock, patch import pytest from file_upload_complete.exception import OwsServiceException from file_upload_complete.processor import FileUploadCompleteProcessor from file_upload_complete.schemas import FileUploadEvent, ProcessorResponse class TestFileUploadCompleteProcessorInit: """Tests for FileUploadCompleteProcessor initialization.""" def test_init_with_valid_event(self): """Test processor initialization with valid event.""" event = FileUploadEvent( detail={'bucket': {'name': 'test-bucket'}, 'object': {'key': 'test.txt'}} ) processor = FileUploadCompleteProcessor(event) assert processor._event == event def test_init_stores_event_reference(self): """Test processor stores reference to event.""" event = FileUploadEvent( action='quarantine', detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, ) processor = FileUploadCompleteProcessor(event) assert processor._event.action == 'quarantine' assert processor._event.detail.bucket.name == 'bucket' class TestFileUploadCompleteProcessorProcess: """Tests for FileUploadCompleteProcessor.process method.""" @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._create_abacus_event' ) @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._complete_upload' ) @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._get_file_upload_config' ) @patch('file_upload_complete.processor.get_s3_connector') def test_process_complete_flow( self, mock_get_connector, mock__get_file_upload_config, mock__complete_upload, mock__create_abacus_event, mock_file_upload, mock_file_upload_config, ): """Test successful file upload completion flow.""" # Setup mocks mock_s3_connector = Mock() mock_s3_connector.get_object_metadata.return_value = { 'metadata': {'filekey': 'test-file-key-123'} } mock_get_connector.return_value = mock_s3_connector # Create event and processor event = FileUploadEvent( detail={ 'bucket': {'name': 'test-bucket'}, 'object': {'key': 'uploads/test.csv'}, } ) mock__get_file_upload_config.return_value = mock_file_upload_config mock__complete_upload.return_value = ProcessorResponse( status='completed', fileKey='test-file-key-123', apiResponse=mock_file_upload, ) processor = FileUploadCompleteProcessor(event) # Execute result = processor.process() # Verify assert result.status == 'completed' assert result.fileKey == 'test-file-key-123' assert result.apiResponse == mock_file_upload mock_s3_connector.get_object_metadata.assert_called_once_with( 'test-bucket', 'uploads/test.csv' ) mock__complete_upload.assert_called_once_with('test-file-key-123') mock__get_file_upload_config.assert_called_once_with( mock_file_upload_config['file_upload_config_id'] ) mock__create_abacus_event.assert_not_called() @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._create_abacus_event' ) @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._complete_upload' ) @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._get_file_upload_config' ) @patch('file_upload_complete.processor.get_s3_connector') def test_create_abacus_event_on_complete_file_upload( self, mock_get_connector, mock__get_file_upload_config, mock__complete_upload, mock__create_abacus_event, mock_file_upload, mock_file_upload_config, ): """Test to create abacus event on successful file upload completion.""" # Setup mocks mock_s3_connector = Mock() mock_s3_connector.get_object_metadata.return_value = { 'metadata': {'filekey': 'test-file-key-123'} } mock_get_connector.return_value = mock_s3_connector # Create event and processor event = FileUploadEvent( detail={ 'bucket': {'name': 'test-bucket'}, 'object': {'key': 'uploads/test.csv'}, } ) mock_file_upload_config.update({'event_name': 'Test Event'}) mock__get_file_upload_config.return_value = mock_file_upload_config mock__complete_upload.return_value = ProcessorResponse( status='completed', fileKey='test-file-key-123', apiResponse=mock_file_upload, ) mock__create_abacus_event.return_value = True processor = FileUploadCompleteProcessor(event) # Execute result = processor.process() # Verify assert result.status == 'completed' assert result.fileKey == 'test-file-key-123' assert result.apiResponse == mock_file_upload mock_s3_connector.get_object_metadata.assert_called_once_with( 'test-bucket', 'uploads/test.csv' ) mock__complete_upload.assert_called_once_with('test-file-key-123') mock__get_file_upload_config.assert_called_once_with( mock_file_upload_config['file_upload_config_id'] ) mock__create_abacus_event.assert_called_once_with( mock_file_upload['file_upload_id'], mock_file_upload_config['event_name'] ) @patch( 'file_upload_complete.processor.ows_royalties.quarantine_infected_file_upload' ) @patch('file_upload_complete.processor.get_s3_connector') def test_process_quarantine_flow( self, mock_get_connector, mock_quarantine_infected_file_upload ): """Test file quarantine flow.""" # Setup mocks mock_s3_connector = Mock() mock_s3_connector.get_object_metadata.return_value = { 'metadata': {'filekey': 'quarantine-key-456'} } mock_get_connector.return_value = mock_s3_connector api_response = {'status': 'quarantined', 'reason': 'infected'} mock_quarantine_infected_file_upload.return_value = api_response # Create event with quarantine action event = FileUploadEvent( action='quarantine', detail={ 'bucket': {'name': 'test-bucket'}, 'object': {'key': 'uploads/infected.exe'}, }, ) processor = FileUploadCompleteProcessor(event) # Execute result = processor.process() # Verify assert result.status == 'quarantined' assert result.fileKey == 'quarantine-key-456' assert result.apiResponse == api_response mock_quarantine_infected_file_upload.assert_called_once_with( 'quarantine-key-456' ) @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._get_file_upload_config' ) @patch('file_upload_complete.processor.get_s3_connector') def test_process_file_key_variations( self, mock_get_connector, mock__get_file_upload_config ): """Test processor handles different file-key metadata variations.""" test_cases = [ {'filekey': 'key1'}, {'file_key': 'key2'}, {'file-key': 'key3'}, ] mock__get_file_upload_config.return_value = {'event_name': None} for metadata in test_cases: mock_s3_connector = Mock() mock_s3_connector.get_object_metadata.return_value = {'metadata': metadata} mock_get_connector.return_value = mock_s3_connector event = FileUploadEvent( detail={ 'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}, } ) processor = FileUploadCompleteProcessor(event) with patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._complete_upload' ) as mock_complete: mock_complete.return_value = ProcessorResponse( status='completed', fileKey=list(metadata.values())[0], apiResponse={ 'file_upload_id': 1, 'file_upload_config_id': 1, }, ) result = processor.process() expected_key = list(metadata.values())[0] assert result.fileKey == expected_key mock_complete.assert_called_once_with(expected_key) @patch('file_upload_complete.processor.get_s3_connector') def test_process_missing_file_key_raises_error(self, mock_get_connector): """Test process raises ValueError when file-key is missing from metadata.""" mock_s3_connector = Mock() mock_s3_connector.get_object_metadata.return_value = { 'metadata': {'other_field': 'value'} } mock_get_connector.return_value = mock_s3_connector event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) with pytest.raises(ValueError) as exc_info: processor.process() assert 'file-key not found' in str(exc_info.value) @patch('file_upload_complete.processor.get_s3_connector') def test_process_empty_metadata_raises_error(self, mock_get_connector): """Test process raises ValueError when metadata is empty.""" mock_s3_connector = Mock() mock_s3_connector.get_object_metadata.return_value = {'metadata': {}} mock_get_connector.return_value = mock_s3_connector event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) with pytest.raises(ValueError) as exc_info: processor.process() assert 'file-key not found' in str(exc_info.value) @patch('file_upload_complete.processor.logger') @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._get_file_upload_config' ) @patch( 'file_upload_complete.processor.FileUploadCompleteProcessor._complete_upload' ) @patch('file_upload_complete.processor.get_s3_connector') def test_process_logs_metadata_retrieval( self, mock_get_connector, mock_complete, mock__get_file_upload_config, mock_logger, mock_file_upload, mock_file_upload_config, ): """Test process logs metadata retrieval.""" mock_s3_connector = Mock() mock_s3_connector.get_object_metadata.return_value = { 'metadata': {'filekey': 'test-key'} } mock_get_connector.return_value = mock_s3_connector mock_complete.return_value = ProcessorResponse( status='completed', fileKey='test-file-key-123', apiResponse=mock_file_upload, ) mock__get_file_upload_config.return_value = mock_file_upload_config event = FileUploadEvent( detail={ 'bucket': {'name': 'my-bucket'}, 'object': {'key': 'path/file.txt'}, } ) processor = FileUploadCompleteProcessor(event) processor.process() # Verify logging calls assert mock_logger.info.called log_calls = [call.args[0] for call in mock_logger.info.call_args_list] assert any('Retrieving metadata' in call for call in log_calls) assert any('s3://my-bucket/path/file.txt' in call for call in log_calls) @patch('file_upload_complete.processor.get_s3_connector') def test_process_unexpected_action_raises_error(self, mock_get_connector): """Test process raises ValueError for unexpected action values.""" mock_s3_connector = Mock() mock_s3_connector.get_object_metadata.return_value = { 'metadata': {'filekey': 'test-key'} } mock_get_connector.return_value = mock_s3_connector event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) # Manually set an invalid action to test defensive programming # (Pydantic validation prevents this in normal operation) processor._event.action = 'invalid_action' with pytest.raises(ValueError) as exc_info: processor.process() assert 'Unexpected action value' in str(exc_info.value) assert 'invalid_action' in str(exc_info.value) class TestCompleteUpload: """Tests for FileUploadCompleteProcessor._complete_upload method.""" @patch('file_upload_complete.processor.ows_royalties.file_upload_complete') def test_complete_upload_success(self, mock_file_upload_complete): """Test successful upload completion.""" api_response = {'id': '123', 'status': 'completed'} mock_file_upload_complete.return_value = api_response event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) result = processor._complete_upload('test-file-key') assert result.status == 'completed' assert result.fileKey == 'test-file-key' assert result.apiResponse == api_response mock_file_upload_complete.assert_called_once_with('test-file-key') @patch('file_upload_complete.processor.logger') @patch('file_upload_complete.processor.ows_royalties.file_upload_complete') def test_complete_upload_logs_success(self, mock_file_upload_complete, mock_logger): """Test upload completion logs success message.""" mock_file_upload_complete.return_value = {} event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) processor._complete_upload('test-file-key') # Verify success logging assert mock_logger.info.called log_message = mock_logger.info.call_args[0][0] assert 'completed successfully' in log_message assert 'test-file-key' in log_message @patch('file_upload_complete.processor.logger') @patch('file_upload_complete.processor.ows_royalties.file_upload_complete') def test_complete_upload_handles_api_error( self, mock_file_upload_complete, mock_logger ): """Test upload completion handles API errors.""" mock_file_upload_complete.side_effect = OwsServiceException( 'API connection failed' ) event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) with pytest.raises(OwsServiceException) as exc_info: processor._complete_upload('test-file-key') assert 'API connection failed' in str(exc_info.value) assert mock_logger.error.called class TestQuarantineFile: """Tests for FileUploadCompleteProcessor._quarantine_file method.""" @patch( 'file_upload_complete.processor.ows_royalties.quarantine_infected_file_upload' ) def test_quarantine_file_success(self, mock_quarantine_infected_file_upload): """Test successful file quarantine.""" api_response = {'id': '456', 'status': 'quarantined'} mock_quarantine_infected_file_upload.return_value = api_response event = FileUploadEvent( action='quarantine', detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, ) processor = FileUploadCompleteProcessor(event) result = processor._quarantine_file('infected-file-key') assert result.status == 'quarantined' assert result.fileKey == 'infected-file-key' assert result.apiResponse == api_response mock_quarantine_infected_file_upload.assert_called_once_with( 'infected-file-key' ) @patch('file_upload_complete.processor.logger') @patch( 'file_upload_complete.processor.ows_royalties.quarantine_infected_file_upload' ) def test_quarantine_file_logs_warning( self, mock_quarantine_infected_file_upload, mock_logger ): """Test file quarantine logs warning message.""" mock_quarantine_infected_file_upload.return_value = {} event = FileUploadEvent( action='quarantine', detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, ) processor = FileUploadCompleteProcessor(event) processor._quarantine_file('infected-file-key') # Verify warning logging assert mock_logger.warning.called log_message = mock_logger.warning.call_args[0][0] assert 'quarantined' in log_message assert 'infected-file-key' in log_message @patch('file_upload_complete.processor.logger') @patch( 'file_upload_complete.processor.ows_royalties.quarantine_infected_file_upload' ) def test_quarantine_file_handles_api_error( self, mock_quarantine_infected_file_upload, mock_logger ): """Test file quarantine handles API errors.""" mock_quarantine_infected_file_upload.side_effect = OwsServiceException( 'Failed to quarantine' ) event = FileUploadEvent( action='quarantine', detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, ) processor = FileUploadCompleteProcessor(event) with pytest.raises(OwsServiceException) as exc_info: processor._quarantine_file('infected-file-key') assert 'Failed to quarantine' in str(exc_info.value) assert mock_logger.error.called class TestCreateAbacusEvent: """Tests for FileUploadCompleteProcessor._create_abacus_event method.""" @patch('file_upload_complete.processor.logger') @patch('file_upload_complete.processor.ows_abacus_event.create_abacus_event') def test__create_abacus_event( self, mock_create_abacus_event, mock_logger, mock_abacus_event ): """Test successful creating abacus event.""" mock_create_abacus_event.return_value = mock_abacus_event event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) result = processor._create_abacus_event(1, 'test_event') assert result == mock_abacus_event mock_create_abacus_event.assert_called_once_with( { 'event_name': 'test_event', 'target_id': 1, 'target_type': 'file_upload', } ) mock_logger.info.assert_called_once_with( 'Abacus event for file upload created successfully.' ) @patch('file_upload_complete.processor.logger') @patch('file_upload_complete.processor.ows_abacus_event.create_abacus_event') def test__create_abacus_event_handles_api_error( self, mock_create_abacus_event, mock_logger ): """Test creating abacus event handles API error.""" mock_create_abacus_event.side_effect = OwsServiceException( 'API connection failed' ) event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) with pytest.raises(OwsServiceException) as exc_info: processor._create_abacus_event(1, 'test_event') assert 'API connection failed' in str(exc_info.value) assert mock_logger.error.called class TestGetFileUploadConfig: """Tests for FileUploadCompleteProcessor._get_file_upload_config method.""" @patch('file_upload_complete.processor.logger') @patch('file_upload_complete.processor.ows_royalties.get_file_upload_config') def test_get_file_upload_config( self, mock_get_file_upload_config, mock_logger, mock_file_upload_config ): """Test successful getting file_upload_config details.""" mock_get_file_upload_config.return_value = mock_file_upload_config file_upload_config_id = mock_file_upload_config['file_upload_config_id'] event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) result = processor._get_file_upload_config(file_upload_config_id) assert result == mock_file_upload_config mock_get_file_upload_config.assert_called_once_with(file_upload_config_id) mock_logger.info.assert_called_once_with( f'Successfully obtained file_upload_config details for ID {file_upload_config_id}' ) @patch('file_upload_complete.processor.logger') @patch('file_upload_complete.processor.ows_royalties.get_file_upload_config') def test__create_abacus_event_handles_api_error( self, mock_get_file_upload_config, mock_logger ): """Test getting file_upload_config handles API error.""" mock_get_file_upload_config.side_effect = OwsServiceException( 'API connection failed' ) event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) processor = FileUploadCompleteProcessor(event) with pytest.raises(OwsServiceException) as exc_info: processor._get_file_upload_config(1) assert 'API connection failed' in str(exc_info.value) mock_logger.error.assert_called_once()