3d3d68f7a7
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
238 lines
9.8 KiB
Python
238 lines
9.8 KiB
Python
"""Tests for app/tasks/upload_to_s3.py module."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from botocore.exceptions import ClientError
|
|
|
|
from app.tasks.upload_to_s3 import upload_to_s3
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestUploadToS3:
|
|
"""Tests for S3 upload functionality."""
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.boto3.client")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_success_with_folder_prefix(self, mock_settings, mock_boto_client, mock_log, tmp_path):
|
|
"""Test successful S3 upload with folder prefix."""
|
|
# Setup
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = "AKIAIOSFODNN7EXAMPLE"
|
|
mock_settings.aws_secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
mock_settings.aws_region = "us-east-1"
|
|
mock_settings.s3_folder_prefix = "documents"
|
|
mock_settings.s3_storage_class = "STANDARD"
|
|
mock_settings.s3_acl = "private"
|
|
|
|
mock_s3 = MagicMock()
|
|
mock_boto_client.return_value = mock_s3
|
|
|
|
# Execute
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
|
|
# Verify
|
|
assert result.result["status"] == "Completed"
|
|
assert result.result["s3_bucket"] == "my-bucket"
|
|
assert result.result["s3_key"] == "documents/test.pdf"
|
|
|
|
mock_s3.upload_file.assert_called_once()
|
|
call_args = mock_s3.upload_file.call_args
|
|
assert call_args[0][0] == str(test_file)
|
|
assert call_args[0][1] == "my-bucket"
|
|
assert call_args[0][2] == "documents/test.pdf"
|
|
assert call_args[1]["ExtraArgs"]["StorageClass"] == "STANDARD"
|
|
assert call_args[1]["ExtraArgs"]["ACL"] == "private"
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.boto3.client")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_without_folder_prefix(self, mock_settings, mock_boto_client, mock_log, tmp_path):
|
|
"""Test S3 upload without folder prefix."""
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = "key"
|
|
mock_settings.aws_secret_access_key = "secret"
|
|
mock_settings.aws_region = "us-west-2"
|
|
mock_settings.s3_folder_prefix = None
|
|
mock_settings.s3_storage_class = "STANDARD"
|
|
mock_settings.s3_acl = None
|
|
|
|
mock_s3 = MagicMock()
|
|
mock_boto_client.return_value = mock_s3
|
|
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
|
|
assert result.result["s3_key"] == "test.pdf"
|
|
# Verify ACL not included when None
|
|
extra_args = mock_s3.upload_file.call_args[1]["ExtraArgs"]
|
|
assert "ACL" not in extra_args
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.boto3.client")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_with_folder_prefix_no_trailing_slash(self, mock_settings, mock_boto_client, mock_log, tmp_path):
|
|
"""Test that folder prefix gets trailing slash added."""
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = "key"
|
|
mock_settings.aws_secret_access_key = "secret"
|
|
mock_settings.aws_region = "us-west-2"
|
|
mock_settings.s3_folder_prefix = "uploads/docs" # No trailing slash
|
|
mock_settings.s3_storage_class = "STANDARD"
|
|
mock_settings.s3_acl = None
|
|
|
|
mock_s3 = MagicMock()
|
|
mock_boto_client.return_value = mock_s3
|
|
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
|
|
# Should add trailing slash
|
|
assert result.result["s3_key"] == "uploads/docs/test.pdf"
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_file_not_found(self, mock_settings, mock_log):
|
|
"""Test S3 upload with non-existent file."""
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = "key"
|
|
mock_settings.aws_secret_access_key = "secret"
|
|
|
|
result = upload_to_s3.apply(args=["/nonexistent/file.pdf"])
|
|
assert isinstance(result.result, FileNotFoundError)
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_missing_bucket_name(self, mock_settings, mock_log, tmp_path):
|
|
"""Test S3 upload with missing bucket name."""
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = None
|
|
mock_settings.aws_access_key_id = "key"
|
|
mock_settings.aws_secret_access_key = "secret"
|
|
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
assert isinstance(result.result, ValueError)
|
|
assert "bucket name" in str(result.result)
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_missing_credentials(self, mock_settings, mock_log, tmp_path):
|
|
"""Test S3 upload with missing AWS credentials."""
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = None
|
|
mock_settings.aws_secret_access_key = None
|
|
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
assert isinstance(result.result, ValueError)
|
|
assert "credentials" in str(result.result)
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.boto3.client")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_client_error(self, mock_settings, mock_boto_client, mock_log, tmp_path):
|
|
"""Test S3 upload with boto3 ClientError."""
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = "key"
|
|
mock_settings.aws_secret_access_key = "secret"
|
|
mock_settings.aws_region = "us-east-1"
|
|
mock_settings.s3_folder_prefix = None
|
|
mock_settings.s3_storage_class = "STANDARD"
|
|
mock_settings.s3_acl = None
|
|
|
|
mock_s3 = MagicMock()
|
|
error_response = {"Error": {"Code": "NoSuchBucket", "Message": "The specified bucket does not exist"}}
|
|
mock_s3.upload_file.side_effect = ClientError(error_response, "upload_file")
|
|
mock_boto_client.return_value = mock_s3
|
|
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
assert isinstance(result.result, Exception)
|
|
assert "Failed to upload" in str(result.result)
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.boto3.client")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_generic_exception(self, mock_settings, mock_boto_client, mock_log, tmp_path):
|
|
"""Test S3 upload with generic exception."""
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = "key"
|
|
mock_settings.aws_secret_access_key = "secret"
|
|
mock_settings.aws_region = "us-east-1"
|
|
mock_settings.s3_folder_prefix = None
|
|
mock_settings.s3_storage_class = "STANDARD"
|
|
mock_settings.s3_acl = None
|
|
|
|
mock_s3 = MagicMock()
|
|
mock_s3.upload_file.side_effect = Exception("Network error")
|
|
mock_boto_client.return_value = mock_s3
|
|
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
assert isinstance(result.result, Exception)
|
|
assert "Error uploading" in str(result.result)
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.boto3.client")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_with_different_storage_classes(self, mock_settings, mock_boto_client, mock_log, tmp_path):
|
|
"""Test S3 upload with different storage classes."""
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = "key"
|
|
mock_settings.aws_secret_access_key = "secret"
|
|
mock_settings.aws_region = "us-east-1"
|
|
mock_settings.s3_folder_prefix = None
|
|
mock_settings.s3_acl = None
|
|
|
|
mock_s3 = MagicMock()
|
|
mock_boto_client.return_value = mock_s3
|
|
|
|
# Test INTELLIGENT_TIERING
|
|
mock_settings.s3_storage_class = "INTELLIGENT_TIERING"
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
extra_args = mock_s3.upload_file.call_args[1]["ExtraArgs"]
|
|
assert extra_args["StorageClass"] == "INTELLIGENT_TIERING"
|
|
|
|
@patch("app.tasks.upload_to_s3.log_task_progress")
|
|
@patch("app.tasks.upload_to_s3.boto3.client")
|
|
@patch("app.tasks.upload_to_s3.settings")
|
|
def test_upload_url_generation(self, mock_settings, mock_boto_client, mock_log, tmp_path):
|
|
"""Test that the S3 URL is properly generated."""
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_text("test content")
|
|
|
|
mock_settings.s3_bucket_name = "my-bucket"
|
|
mock_settings.aws_access_key_id = "key"
|
|
mock_settings.aws_secret_access_key = "secret"
|
|
mock_settings.aws_region = "eu-west-1"
|
|
mock_settings.s3_folder_prefix = "docs"
|
|
mock_settings.s3_storage_class = "STANDARD"
|
|
mock_settings.s3_acl = None
|
|
|
|
mock_s3 = MagicMock()
|
|
mock_boto_client.return_value = mock_s3
|
|
|
|
result = upload_to_s3.apply(args=[str(test_file)])
|
|
|
|
expected_url = "https://my-bucket.s3.eu-west-1.amazonaws.com/docs/test.pdf"
|
|
assert result.result["s3_url"] == expected_url
|