"""Unit tests for S3 connector.""" from datetime import datetime from unittest.mock import Mock, patch import pytest from botocore.client import Config from botocore.exceptions import ClientError from file_upload_initialize.connectors.s3 import ( S3Connector, get_s3_client, get_s3_connector, ) class TestS3Connector: """Tests for S3Connector class.""" def test_init(self): """Test S3Connector initialization.""" mock_client = Mock() connector = S3Connector(mock_client) assert connector.s3_client == mock_client def test_get_object_metadata_success(self): """Test successful retrieval of object metadata.""" mock_client = Mock() test_bucket = 'test-bucket' test_key = 'test/path/file.txt' test_metadata = { 'filekey': 'abc-123', 'user': 'test-user', } test_etag = '"abc123def456"' test_last_modified = datetime(2024, 1, 1, 12, 0, 0) test_content_length = 1024 mock_client.head_object.return_value = { 'ContentLength': test_content_length, 'LastModified': test_last_modified, 'ETag': test_etag, 'Metadata': test_metadata, } connector = S3Connector(mock_client) result = connector.get_object_metadata(test_bucket, test_key) assert result['size'] == test_content_length assert result['last_modified'] == test_last_modified assert result['etag'] == test_etag assert result['metadata'] == test_metadata mock_client.head_object.assert_called_once_with( Bucket=test_bucket, Key=test_key ) def test_get_object_metadata_minimal_response(self): """Test get_object_metadata with minimal response fields.""" mock_client = Mock() test_bucket = 'test-bucket' test_key = 'test/path/file.txt' # Minimal response without optional fields mock_client.head_object.return_value = {} connector = S3Connector(mock_client) result = connector.get_object_metadata(test_bucket, test_key) assert result['size'] == 0 assert result['last_modified'] is None assert result['etag'] is None assert result['metadata'] == {} def test_get_object_metadata_with_empty_metadata(self): """Test get_object_metadata when metadata field is missing.""" mock_client = Mock() test_bucket = 'test-bucket' test_key = 'test/path/file.txt' mock_client.head_object.return_value = { 'ContentLength': 500, } connector = S3Connector(mock_client) result = connector.get_object_metadata(test_bucket, test_key) assert result['size'] == 500 assert result['metadata'] == {} def test_get_object_metadata_raises_error(self): """Test get_object_metadata when S3 raises an error.""" mock_client = Mock() test_bucket = 'test-bucket' test_key = 'nonexistent/file.txt' error_response = {'Error': {'Code': '404', 'Message': 'Not Found'}} mock_client.head_object.side_effect = ClientError(error_response, 'HeadObject') connector = S3Connector(mock_client) with pytest.raises(ClientError) as exc_info: connector.get_object_metadata(test_bucket, test_key) assert exc_info.value.response['Error']['Code'] == '404' class TestGetS3Client: """Tests for get_s3_client function.""" @patch('file_upload_initialize.connectors.s3.boto3.client') def test_get_s3_client_default_config(self, mock_boto_client): """Test get_s3_client with default config.""" mock_client = Mock() mock_boto_client.return_value = mock_client result = get_s3_client() assert result == mock_client mock_boto_client.assert_called_once() call_args = mock_boto_client.call_args assert call_args[0][0] == 's3' assert isinstance(call_args[1]['config'], Config) assert call_args[1]['config'].signature_version == 's3v4' @patch('file_upload_initialize.connectors.s3.boto3.client') def test_get_s3_client_custom_config(self, mock_boto_client): """Test get_s3_client with custom config.""" mock_client = Mock() mock_boto_client.return_value = mock_client custom_config = Config(signature_version='s3v2', region_name='us-west-2') result = get_s3_client(config=custom_config) assert result == mock_client mock_boto_client.assert_called_once_with('s3', config=custom_config) class TestGetS3Connector: """Tests for get_s3_connector function.""" @patch('file_upload_initialize.connectors.s3.get_s3_client') def test_get_s3_connector_without_client(self, mock_get_client): """Test get_s3_connector creates client if not provided.""" mock_client = Mock() mock_get_client.return_value = mock_client result = get_s3_connector() assert isinstance(result, S3Connector) assert result.s3_client == mock_client mock_get_client.assert_called_once() def test_get_s3_connector_with_client(self): """Test get_s3_connector uses provided client.""" mock_client = Mock() result = get_s3_connector(s3_client=mock_client) assert isinstance(result, S3Connector) assert result.s3_client == mock_client @patch('file_upload_initialize.connectors.s3.get_s3_client') def test_get_s3_connector_returns_new_instance(self, mock_get_client): """Test get_s3_connector returns new instance each time.""" mock_client = Mock() mock_get_client.return_value = mock_client connector1 = get_s3_connector() connector2 = get_s3_connector() assert connector1 is not connector2 assert connector1.s3_client == connector2.s3_client