"""Unit tests for s3 logic.""" import json from unittest.mock import Mock from unittest.mock import patch from botocore.exceptions import ClientError import pytest from src.logic import s3 @patch('src.logic.s3.config.AWS_REGION', 'us-east-1') @patch('src.logic.s3.boto3.client') def test_get_client_returns_s3_client(mock_boto_client): """Test that get_client returns an S3 client.""" mock_client = Mock() mock_boto_client.return_value = mock_client # Clear cache before test s3.get_client.cache_clear() result = s3.get_client() assert result == mock_client mock_boto_client.assert_called_once_with('s3', 'us-east-1') @patch('src.logic.s3.config.AWS_BUCKET_NAME', 'test-bucket') @patch('src.logic.s3.get_client') def test_upload_snapshot_success(mock_get_client): """Test successful snapshot upload.""" # Arrange mock_s3_client = Mock() mock_response = { 'ResponseMetadata': { 'HTTPStatusCode': 200 }, 'ETag': '"abc123"', 'VersionId': 'v1' } mock_s3_client.put_object.return_value = mock_response mock_get_client.return_value = mock_s3_client product = { 'product_id': '12345', 'review_queue_id': '67890', 'title': 'Test Product', 'artist': 'Test Artist' } object_key = 'snapshots/product-12345-review-queue-67890.json' # Act result = s3.upload_snapshot(product, object_key) # Assert assert result == 200 mock_s3_client.put_object.assert_called_once() # Verify call arguments call_args = mock_s3_client.put_object.call_args assert call_args.kwargs['Bucket'] == 'test-bucket' assert call_args.kwargs['Key'] == 'snapshots/product-12345-review-queue-67890.json' # Verify body is correct JSON string body = call_args.kwargs['Body'] assert body == json.dumps(product) @patch('src.logic.s3.get_client') def test_upload_snapshot_with_different_product_ids(mock_get_client): """Test snapshot upload with different product IDs generates correct keys.""" mock_s3_client = Mock() mock_s3_client.put_object.return_value = { 'ResponseMetadata': {'HTTPStatusCode': 200}, 'ETag': '"test"' } mock_get_client.return_value = mock_s3_client product = { 'product_id': 'abc', 'review_queue_id': 'xyz', } object_key = 'snapshots/product-abc-review-queue-xyz.json' s3.upload_snapshot(product, object_key) call_args = mock_s3_client.put_object.call_args assert call_args.kwargs['Key'] == 'snapshots/product-abc-review-queue-xyz.json' @patch('src.logic.s3.get_client') def test_upload_snapshot_handles_s3_exception(mock_get_client): """Test that upload_snapshot raises ClientError when S3 fails.""" # Arrange mock_s3_client = Mock() error_response = { 'Error': {'Code': 'InternalError', 'Message': 'S3 connection error'}, 'ResponseMetadata': {'HTTPStatusCode': 500} } mock_s3_client.put_object.side_effect = ClientError(error_response, 'PutObject') mock_get_client.return_value = mock_s3_client product = { 'product_id': '12345', 'review_queue_id': '67890', } object_key = 'product-12345-review-queue-67890.json' # Act & Assert with pytest.raises(ClientError) as exc_info: s3.upload_snapshot(product, object_key) assert exc_info.value.response['Error']['Code'] == 'InternalError' assert exc_info.value.response['Error']['Message'] == 'S3 connection error' @patch('src.logic.s3.get_client') def test_upload_snapshot_handles_precondition_failed(mock_get_client): """Test that upload_snapshot returns 412 when object already exists.""" # Arrange mock_s3_client = Mock() error_response = { 'Error': {'Code': 'PreconditionFailed', 'Message': 'Object already exists'}, 'ResponseMetadata': {'HTTPStatusCode': 412} } mock_s3_client.put_object.side_effect = ClientError(error_response, 'PutObject') mock_get_client.return_value = mock_s3_client product = { 'product_id': '12345', 'review_queue_id': '67890', } object_key = 'snapshots/product-12345-review-queue-67890.json' # Act result = s3.upload_snapshot(product, object_key) # Assert - should not raise exception, just return 412 assert result == 412 def test_get_object_key_with_valid_ids(): """Test get_object_key with valid product and review IDs.""" result = s3.get_object_key('12345', '67890') assert result == 'snapshots/product-12345-review-queue-67890.json' def test_get_object_key_with_none_values(): """Test get_object_key handles None values.""" result = s3.get_object_key(None, '67890') assert result == 'snapshots/product-None-review-queue-67890.json' result = s3.get_object_key('12345', None) assert result == 'snapshots/product-12345-review-queue-None.json' def test_get_s3_url(): """Test get_s3_url returns correct S3 URI.""" result = s3.get_s3_url('my-bucket', 'snapshots/product-123-review-queue-456.json') assert result == 's3://my-bucket/snapshots/product-123-review-queue-456.json'