from io import BytesIO from unittest.mock import MagicMock, patch from pytest import fixture, mark, raises from model_manager.activities.minio import MinIO @patch('model_manager.activities.minio.boto3.client') def test___init__(mock_boto3_client): """Test MinIO initialization with correct configuration.""" mock_client = MagicMock() mock_boto3_client.return_value = mock_client logger = MagicMock() notification_handler = MagicMock() minio = MinIO( endpoint_url='http://localhost:9000', access_key='minioadmin', secret_key='minioadmin', region='us-east-1', use_ssl=False, max_retry_attempts=3, retry_mode='adaptive', connect_timeout=10, read_timeout=60, logger=logger, notification_handler=notification_handler, ) assert minio.endpoint_url == 'http://localhost:9000' assert minio.access_key == 'minioadmin' assert minio.secret_key == 'minioadmin' assert minio.region == 'us-east-1' assert minio.use_ssl is False assert minio.max_retry_attempts == 3 assert minio.retry_mode == 'adaptive' assert minio.connect_timeout == 10 assert minio.read_timeout == 60 # Verify boto3 client was created with correct parameters mock_boto3_client.assert_called_once() call_kwargs = mock_boto3_client.call_args[1] assert call_kwargs['endpoint_url'] == 'http://localhost:9000' assert call_kwargs['aws_access_key_id'] == 'minioadmin' assert call_kwargs['aws_secret_access_key'] == 'minioadmin' assert call_kwargs['use_ssl'] is False @patch('model_manager.activities.minio.boto3.client') def test___init___failure(mock_boto3_client): """Test MinIO initialization failure handling.""" mock_boto3_client.side_effect = Exception('Connection failed') logger = MagicMock() notification_handler = MagicMock() with raises(ConnectionError, match='Failed to initialize MinIO client'): MinIO( endpoint_url='http://localhost:9000', access_key='minioadmin', secret_key='minioadmin', region='us-east-1', use_ssl=False, max_retry_attempts=3, retry_mode='adaptive', connect_timeout=10, read_timeout=60, logger=logger, notification_handler=notification_handler, ) @fixture @patch('model_manager.activities.minio.boto3.client') def minio(mock_boto3_client): """Fixture to create a MinIO instance for testing.""" mock_client = MagicMock() mock_boto3_client.return_value = mock_client logger = MagicMock() notification_handler = MagicMock() minio_instance = MinIO( endpoint_url='http://localhost:9000', access_key='minioadmin', secret_key='minioadmin', region='us-east-1', use_ssl=False, max_retry_attempts=3, retry_mode='adaptive', connect_timeout=10, read_timeout=60, logger=logger, notification_handler=notification_handler, ) minio_instance.send_notification = MagicMock() minio_instance.minio_client = mock_client return minio_instance metadata = { 'metadata': { 'workflow_name': 'test_workflow', 'model_name': 'test_model', 'model_id': 'test_model_id', } } @mark.asyncio async def test_fetch_file_from_minio_success(minio): """Test successful file fetch from MinIO.""" # Arrange test_content = b'test file content' mock_response = {'Body': MagicMock()} mock_response['Body'].__enter__ = MagicMock( return_value=MagicMock(read=MagicMock(return_value=test_content)) ) mock_response['Body'].__exit__ = MagicMock(return_value=None) minio.minio_client.get_object.return_value = mock_response input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'test-file.txt', } # Act result = await minio.fetch_file_from_minio(input_data) # Assert assert isinstance(result, BytesIO) result.seek(0) assert result.read() == test_content minio.minio_client.get_object.assert_called_once_with(Bucket='test-bucket', Key='test-file.txt') @mark.asyncio async def test_fetch_file_from_minio_file_not_found(minio): """Test file fetch when file doesn't exist.""" # Arrange minio.minio_client.get_object.side_effect = Exception( 'NoSuchKey: The specified key does not exist' ) input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'nonexistent.txt', } # Act & Assert with raises(OSError, match='Error fetching file from MinIO'): await minio.fetch_file_from_minio(input_data) # Verify notification was sent minio.send_notification.assert_called_once() call_kwargs = minio.send_notification.call_args[1] assert call_kwargs['notification_id'] == 'FETCH_FILE_FROM_MINIO_ERROR' assert call_kwargs['block'] == 'fetch_file_from_minio' @mark.asyncio async def test_fetch_file_from_minio_network_error(minio): """Test file fetch with network error.""" # Arrange minio.minio_client.get_object.side_effect = Exception('Network timeout') input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'test-file.txt', } # Act & Assert with raises(OSError, match='Error fetching file from MinIO'): await minio.fetch_file_from_minio(input_data) minio.send_notification.assert_called_once() @mark.asyncio async def test_delete_file_from_minio_success(minio): """Test successful file deletion from MinIO.""" # Arrange minio.minio_client.delete_object.return_value = None input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'test-file.txt', } # Act result = await minio.delete_file_from_minio(input_data) # Assert assert result is None minio.minio_client.delete_object.assert_called_once_with( Bucket='test-bucket', Key='test-file.txt' ) @mark.asyncio async def test_delete_file_from_minio_idempotent(minio): """Test that delete is idempotent (no error if file doesn't exist).""" # Arrange # MinIO delete_object is idempotent - no error if file doesn't exist minio.minio_client.delete_object.return_value = None input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'nonexistent.txt', } # Act result = await minio.delete_file_from_minio(input_data) # Assert assert result is None minio.minio_client.delete_object.assert_called_once() @mark.asyncio async def test_delete_file_from_minio_access_denied(minio): """Test file deletion with access denied error.""" # Arrange minio.minio_client.delete_object.side_effect = Exception('AccessDenied: Access Denied') input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'test-file.txt', } # Act & Assert with raises(OSError, match='Error deleting file from MinIO'): await minio.delete_file_from_minio(input_data) # Verify notification was sent minio.send_notification.assert_called_once() call_kwargs = minio.send_notification.call_args[1] assert call_kwargs['notification_id'] == 'DELETE_FILE_FROM_MINIO_ERROR' assert call_kwargs['block'] == 'delete_file_from_minio' @mark.asyncio async def test_delete_file_from_minio_network_error(minio): """Test file deletion with network error.""" # Arrange minio.minio_client.delete_object.side_effect = Exception('Connection timeout') input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'test-file.txt', } # Act & Assert with raises(OSError, match='Error deleting file from MinIO'): await minio.delete_file_from_minio(input_data) minio.send_notification.assert_called_once() @mark.asyncio async def test_fetch_file_from_minio_large_file(minio): """Test fetching a large file from MinIO.""" # Arrange # Simulate a 10MB file large_content = b'x' * (10 * 1024 * 1024) mock_response = {'Body': MagicMock()} mock_response['Body'].__enter__ = MagicMock( return_value=MagicMock(read=MagicMock(return_value=large_content)) ) mock_response['Body'].__exit__ = MagicMock(return_value=None) minio.minio_client.get_object.return_value = mock_response input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'large-file.bin', } # Act result = await minio.fetch_file_from_minio(input_data) # Assert assert isinstance(result, BytesIO) result.seek(0) assert len(result.read()) == 10 * 1024 * 1024 @mark.asyncio async def test_fetch_file_from_minio_empty_file(minio): """Test fetching an empty file from MinIO.""" # Arrange empty_content = b'' mock_response = {'Body': MagicMock()} mock_response['Body'].__enter__ = MagicMock( return_value=MagicMock(read=MagicMock(return_value=empty_content)) ) mock_response['Body'].__exit__ = MagicMock(return_value=None) minio.minio_client.get_object.return_value = mock_response input_data = { 'metadata': metadata['metadata'], 'bucket_name': 'test-bucket', 'file_name': 'empty-file.txt', } # Act result = await minio.fetch_file_from_minio(input_data) # Assert assert isinstance(result, BytesIO) result.seek(0) assert result.read() == b''