WIP
This commit is contained in:
718
tests/shared/storage/test_azure.py
Normal file
718
tests/shared/storage/test_azure.py
Normal file
@@ -0,0 +1,718 @@
|
||||
"""
|
||||
Tests for AzureBlobStorageBackend.
|
||||
|
||||
TDD Phase 1: RED - Write tests first, then implement to pass.
|
||||
Uses mocking to avoid requiring actual Azure credentials.
|
||||
"""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_blob_service_client() -> MagicMock:
|
||||
"""Create a mock BlobServiceClient."""
|
||||
return MagicMock()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_container_client(mock_blob_service_client: MagicMock) -> MagicMock:
|
||||
"""Create a mock ContainerClient."""
|
||||
container_client = MagicMock()
|
||||
mock_blob_service_client.get_container_client.return_value = container_client
|
||||
return container_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_blob_client(mock_container_client: MagicMock) -> MagicMock:
|
||||
"""Create a mock BlobClient."""
|
||||
blob_client = MagicMock()
|
||||
mock_container_client.get_blob_client.return_value = blob_client
|
||||
return blob_client
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendCreation:
|
||||
"""Tests for AzureBlobStorageBackend instantiation."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_create_with_connection_string(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test creating backend with connection string."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
connection_string = "DefaultEndpointsProtocol=https;AccountName=test;..."
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string=connection_string,
|
||||
container_name="training-images",
|
||||
)
|
||||
|
||||
mock_service_class.from_connection_string.assert_called_once_with(
|
||||
connection_string
|
||||
)
|
||||
assert backend.container_name == "training-images"
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_create_creates_container_if_not_exists(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that container is created if it doesn't exist."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_container.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="new-container",
|
||||
create_container=True,
|
||||
)
|
||||
|
||||
mock_container.create_container.assert_called_once()
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_create_does_not_create_container_by_default(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that container is not created by default."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_container.exists.return_value = True
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="existing-container",
|
||||
)
|
||||
|
||||
mock_container.create_container.assert_not_called()
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_is_storage_backend_subclass(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that AzureBlobStorageBackend is a StorageBackend."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
from shared.storage.base import StorageBackend
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
assert isinstance(backend, StorageBackend)
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendUpload:
|
||||
"""Tests for AzureBlobStorageBackend.upload method."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_upload_file(self, mock_service_class: MagicMock) -> None:
|
||||
"""Test uploading a file."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as f:
|
||||
f.write(b"Hello, World!")
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
result = backend.upload(temp_path, "uploads/sample.txt")
|
||||
|
||||
assert result == "uploads/sample.txt"
|
||||
mock_container.get_blob_client.assert_called_with("uploads/sample.txt")
|
||||
mock_blob.upload_blob.assert_called_once()
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_upload_fails_if_blob_exists_without_overwrite(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that upload fails if blob exists and overwrite is False."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
from shared.storage.base import StorageError
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = True
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as f:
|
||||
f.write(b"content")
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
with pytest.raises(StorageError, match="already exists"):
|
||||
backend.upload(temp_path, "existing.txt", overwrite=False)
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_upload_succeeds_with_overwrite(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that upload succeeds with overwrite=True."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = True
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as f:
|
||||
f.write(b"content")
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
result = backend.upload(temp_path, "existing.txt", overwrite=True)
|
||||
|
||||
assert result == "existing.txt"
|
||||
mock_blob.upload_blob.assert_called_once()
|
||||
# Check overwrite=True was passed
|
||||
call_kwargs = mock_blob.upload_blob.call_args[1]
|
||||
assert call_kwargs.get("overwrite") is True
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_upload_nonexistent_file_fails(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that uploading nonexistent file fails."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
from shared.storage.base import FileNotFoundStorageError
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with pytest.raises(FileNotFoundStorageError):
|
||||
backend.upload(Path("/nonexistent/file.txt"), "sample.txt")
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendDownload:
|
||||
"""Tests for AzureBlobStorageBackend.download method."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_download_file(self, mock_service_class: MagicMock) -> None:
|
||||
"""Test downloading a file."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = True
|
||||
|
||||
# Mock download_blob to return stream
|
||||
mock_stream = MagicMock()
|
||||
mock_stream.readall.return_value = b"Hello, World!"
|
||||
mock_blob.download_blob.return_value = mock_stream
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
local_path = Path(temp_dir) / "downloaded.txt"
|
||||
result = backend.download("remote/sample.txt", local_path)
|
||||
|
||||
assert result == local_path
|
||||
assert local_path.exists()
|
||||
assert local_path.read_bytes() == b"Hello, World!"
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_download_creates_parent_directories(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that download creates parent directories."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = True
|
||||
|
||||
mock_stream = MagicMock()
|
||||
mock_stream.readall.return_value = b"content"
|
||||
mock_blob.download_blob.return_value = mock_stream
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
local_path = Path(temp_dir) / "deep" / "nested" / "downloaded.txt"
|
||||
result = backend.download("sample.txt", local_path)
|
||||
|
||||
assert local_path.exists()
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_download_nonexistent_blob_fails(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test that downloading nonexistent blob fails."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
from shared.storage.base import FileNotFoundStorageError
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with pytest.raises(FileNotFoundStorageError, match="nonexistent.txt"):
|
||||
backend.download("nonexistent.txt", Path("/tmp/file.txt"))
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendExists:
|
||||
"""Tests for AzureBlobStorageBackend.exists method."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_exists_returns_true_for_existing_blob(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test exists returns True for existing blob."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = True
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
assert backend.exists("existing.txt") is True
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_exists_returns_false_for_nonexistent_blob(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test exists returns False for nonexistent blob."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
assert backend.exists("nonexistent.txt") is False
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendListFiles:
|
||||
"""Tests for AzureBlobStorageBackend.list_files method."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_list_files_empty_container(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test listing files in empty container."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_container.list_blobs.return_value = []
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
assert backend.list_files("") == []
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_list_files_returns_all_blobs(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test listing all blobs."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
|
||||
# Create mock blob items
|
||||
mock_blob1 = MagicMock()
|
||||
mock_blob1.name = "file1.txt"
|
||||
mock_blob2 = MagicMock()
|
||||
mock_blob2.name = "file2.txt"
|
||||
mock_blob3 = MagicMock()
|
||||
mock_blob3.name = "subdir/file3.txt"
|
||||
mock_container.list_blobs.return_value = [mock_blob1, mock_blob2, mock_blob3]
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
files = backend.list_files("")
|
||||
|
||||
assert len(files) == 3
|
||||
assert "file1.txt" in files
|
||||
assert "file2.txt" in files
|
||||
assert "subdir/file3.txt" in files
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_list_files_with_prefix(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test listing files with prefix filter."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
|
||||
mock_blob1 = MagicMock()
|
||||
mock_blob1.name = "images/a.png"
|
||||
mock_blob2 = MagicMock()
|
||||
mock_blob2.name = "images/b.png"
|
||||
mock_container.list_blobs.return_value = [mock_blob1, mock_blob2]
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
files = backend.list_files("images/")
|
||||
|
||||
mock_container.list_blobs.assert_called_with(name_starts_with="images/")
|
||||
assert len(files) == 2
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendDelete:
|
||||
"""Tests for AzureBlobStorageBackend.delete method."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_delete_existing_blob(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test deleting an existing blob."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = True
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
result = backend.delete("sample.txt")
|
||||
|
||||
assert result is True
|
||||
mock_blob.delete_blob.assert_called_once()
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_delete_nonexistent_blob_returns_false(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test deleting nonexistent blob returns False."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
result = backend.delete("nonexistent.txt")
|
||||
|
||||
assert result is False
|
||||
mock_blob.delete_blob.assert_not_called()
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendGetUrl:
|
||||
"""Tests for AzureBlobStorageBackend.get_url method."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_get_url_returns_blob_url(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test get_url returns blob URL."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = True
|
||||
mock_blob.url = "https://account.blob.core.windows.net/container/sample.txt"
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
url = backend.get_url("sample.txt")
|
||||
|
||||
assert url == "https://account.blob.core.windows.net/container/sample.txt"
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_get_url_nonexistent_blob_fails(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test get_url for nonexistent blob fails."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
from shared.storage.base import FileNotFoundStorageError
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with pytest.raises(FileNotFoundStorageError):
|
||||
backend.get_url("nonexistent.txt")
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendUploadBytes:
|
||||
"""Tests for AzureBlobStorageBackend.upload_bytes method."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_upload_bytes(self, mock_service_class: MagicMock) -> None:
|
||||
"""Test uploading bytes directly."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
data = b"Binary content here"
|
||||
result = backend.upload_bytes(data, "binary.dat")
|
||||
|
||||
assert result == "binary.dat"
|
||||
mock_blob.upload_blob.assert_called_once()
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendDownloadBytes:
|
||||
"""Tests for AzureBlobStorageBackend.download_bytes method."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_download_bytes(self, mock_service_class: MagicMock) -> None:
|
||||
"""Test downloading blob as bytes."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = True
|
||||
|
||||
mock_stream = MagicMock()
|
||||
mock_stream.readall.return_value = b"Hello, World!"
|
||||
mock_blob.download_blob.return_value = mock_stream
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
data = backend.download_bytes("sample.txt")
|
||||
|
||||
assert data == b"Hello, World!"
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_download_bytes_nonexistent(
|
||||
self, mock_service_class: MagicMock
|
||||
) -> None:
|
||||
"""Test downloading nonexistent blob as bytes."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
from shared.storage.base import FileNotFoundStorageError
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with pytest.raises(FileNotFoundStorageError):
|
||||
backend.download_bytes("nonexistent.txt")
|
||||
|
||||
|
||||
class TestAzureBlobStorageBackendBatchOperations:
|
||||
"""Tests for batch operations in AzureBlobStorageBackend."""
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_upload_directory(self, mock_service_class: MagicMock) -> None:
|
||||
"""Test uploading an entire directory."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
mock_blob = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob
|
||||
mock_blob.exists.return_value = False
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
(temp_path / "file1.txt").write_text("content1")
|
||||
(temp_path / "subdir").mkdir()
|
||||
(temp_path / "subdir" / "file2.txt").write_text("content2")
|
||||
|
||||
results = backend.upload_directory(temp_path, "uploads/")
|
||||
|
||||
assert len(results) == 2
|
||||
assert "uploads/file1.txt" in results
|
||||
assert "uploads/subdir/file2.txt" in results
|
||||
|
||||
@patch("shared.storage.azure.BlobServiceClient")
|
||||
def test_download_directory(self, mock_service_class: MagicMock) -> None:
|
||||
"""Test downloading blobs matching a prefix."""
|
||||
from shared.storage.azure import AzureBlobStorageBackend
|
||||
|
||||
mock_service = MagicMock()
|
||||
mock_service_class.from_connection_string.return_value = mock_service
|
||||
mock_container = MagicMock()
|
||||
mock_service.get_container_client.return_value = mock_container
|
||||
|
||||
# Mock blob listing
|
||||
mock_blob1 = MagicMock()
|
||||
mock_blob1.name = "images/a.png"
|
||||
mock_blob2 = MagicMock()
|
||||
mock_blob2.name = "images/b.png"
|
||||
mock_container.list_blobs.return_value = [mock_blob1, mock_blob2]
|
||||
|
||||
# Mock blob clients
|
||||
mock_blob_client = MagicMock()
|
||||
mock_container.get_blob_client.return_value = mock_blob_client
|
||||
mock_blob_client.exists.return_value = True
|
||||
mock_stream = MagicMock()
|
||||
mock_stream.readall.return_value = b"image content"
|
||||
mock_blob_client.download_blob.return_value = mock_stream
|
||||
|
||||
backend = AzureBlobStorageBackend(
|
||||
connection_string="connection_string",
|
||||
container_name="container",
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
local_path = Path(temp_dir)
|
||||
results = backend.download_directory("images/", local_path)
|
||||
|
||||
assert len(results) == 2
|
||||
# Files should be created relative to prefix
|
||||
assert (local_path / "a.png").exists() or (local_path / "images" / "a.png").exists()
|
||||
Reference in New Issue
Block a user