307 lines
10 KiB
Python
307 lines
10 KiB
Python
"""
|
|
Tests for storage backend integration in web application.
|
|
|
|
TDD Phase 1: RED - Write tests first, then implement to pass.
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestStorageBackendInitialization:
|
|
"""Tests for storage backend initialization in web config."""
|
|
|
|
def test_get_storage_backend_returns_backend(self, tmp_path: Path) -> None:
|
|
"""Test that get_storage_backend returns a StorageBackend instance."""
|
|
from shared.storage.base import StorageBackend
|
|
|
|
from inference.web.config import get_storage_backend
|
|
|
|
env = {
|
|
"STORAGE_BACKEND": "local",
|
|
"STORAGE_BASE_PATH": str(tmp_path / "storage"),
|
|
}
|
|
|
|
with patch.dict(os.environ, env, clear=False):
|
|
backend = get_storage_backend()
|
|
|
|
assert isinstance(backend, StorageBackend)
|
|
|
|
def test_get_storage_backend_uses_config_file_if_exists(
|
|
self, tmp_path: Path
|
|
) -> None:
|
|
"""Test that storage config file is used when present."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
from inference.web.config import get_storage_backend
|
|
|
|
config_file = tmp_path / "storage.yaml"
|
|
storage_path = tmp_path / "storage"
|
|
config_file.write_text(f"""
|
|
backend: local
|
|
|
|
local:
|
|
base_path: {storage_path}
|
|
""")
|
|
|
|
backend = get_storage_backend(config_path=config_file)
|
|
|
|
assert isinstance(backend, LocalStorageBackend)
|
|
|
|
def test_get_storage_backend_falls_back_to_env(self, tmp_path: Path) -> None:
|
|
"""Test fallback to environment variables when no config file."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
from inference.web.config import get_storage_backend
|
|
|
|
env = {
|
|
"STORAGE_BACKEND": "local",
|
|
"STORAGE_BASE_PATH": str(tmp_path / "storage"),
|
|
}
|
|
|
|
with patch.dict(os.environ, env, clear=False):
|
|
backend = get_storage_backend(config_path=None)
|
|
|
|
assert isinstance(backend, LocalStorageBackend)
|
|
|
|
def test_app_config_has_storage_backend(self, tmp_path: Path) -> None:
|
|
"""Test that AppConfig can be created with storage backend."""
|
|
from shared.storage.base import StorageBackend
|
|
|
|
from inference.web.config import AppConfig, create_app_config
|
|
|
|
env = {
|
|
"STORAGE_BACKEND": "local",
|
|
"STORAGE_BASE_PATH": str(tmp_path / "storage"),
|
|
}
|
|
|
|
with patch.dict(os.environ, env, clear=False):
|
|
config = create_app_config()
|
|
|
|
assert hasattr(config, "storage_backend")
|
|
assert isinstance(config.storage_backend, StorageBackend)
|
|
|
|
|
|
class TestStorageBackendInDocumentUpload:
|
|
"""Tests for storage backend usage in document upload."""
|
|
|
|
def test_upload_document_uses_storage_backend(
|
|
self, tmp_path: Path, mock_admin_db: MagicMock
|
|
) -> None:
|
|
"""Test that document upload uses storage backend."""
|
|
from unittest.mock import AsyncMock
|
|
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
storage_path = tmp_path / "storage"
|
|
storage_path.mkdir(parents=True, exist_ok=True)
|
|
backend = LocalStorageBackend(str(storage_path))
|
|
|
|
# Create a mock upload file
|
|
pdf_content = b"%PDF-1.4 test content"
|
|
|
|
from inference.web.services.document_service import DocumentService
|
|
|
|
service = DocumentService(admin_db=mock_admin_db, storage_backend=backend)
|
|
|
|
# Upload should use storage backend
|
|
result = service.upload_document(
|
|
content=pdf_content,
|
|
filename="test.pdf",
|
|
dataset_id="dataset-1",
|
|
)
|
|
|
|
assert result is not None
|
|
# Verify file was stored via storage backend
|
|
assert backend.exists(f"documents/{result.id}.pdf")
|
|
|
|
def test_upload_document_stores_logical_path(
|
|
self, tmp_path: Path, mock_admin_db: MagicMock
|
|
) -> None:
|
|
"""Test that document stores logical path, not absolute path."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
storage_path = tmp_path / "storage"
|
|
storage_path.mkdir(parents=True, exist_ok=True)
|
|
backend = LocalStorageBackend(str(storage_path))
|
|
|
|
pdf_content = b"%PDF-1.4 test content"
|
|
|
|
from inference.web.services.document_service import DocumentService
|
|
|
|
service = DocumentService(admin_db=mock_admin_db, storage_backend=backend)
|
|
|
|
result = service.upload_document(
|
|
content=pdf_content,
|
|
filename="test.pdf",
|
|
dataset_id="dataset-1",
|
|
)
|
|
|
|
# Path should be logical (relative), not absolute
|
|
assert not result.file_path.startswith("/")
|
|
assert not result.file_path.startswith("C:")
|
|
assert result.file_path.startswith("documents/")
|
|
|
|
|
|
class TestStorageBackendInDocumentDownload:
|
|
"""Tests for storage backend usage in document download/serving."""
|
|
|
|
def test_get_document_url_returns_presigned_url(
|
|
self, tmp_path: Path, mock_admin_db: MagicMock
|
|
) -> None:
|
|
"""Test that document URL uses presigned URL from storage backend."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
storage_path = tmp_path / "storage"
|
|
storage_path.mkdir(parents=True, exist_ok=True)
|
|
backend = LocalStorageBackend(str(storage_path))
|
|
|
|
# Create a test file
|
|
doc_path = "documents/test-doc.pdf"
|
|
backend.upload_bytes(b"%PDF-1.4 test", doc_path)
|
|
|
|
from inference.web.services.document_service import DocumentService
|
|
|
|
service = DocumentService(admin_db=mock_admin_db, storage_backend=backend)
|
|
|
|
url = service.get_document_url(doc_path)
|
|
|
|
# Should return a URL (file:// for local, https:// for cloud)
|
|
assert url is not None
|
|
assert "test-doc.pdf" in url
|
|
|
|
def test_download_document_uses_storage_backend(
|
|
self, tmp_path: Path, mock_admin_db: MagicMock
|
|
) -> None:
|
|
"""Test that document download uses storage backend."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
storage_path = tmp_path / "storage"
|
|
storage_path.mkdir(parents=True, exist_ok=True)
|
|
backend = LocalStorageBackend(str(storage_path))
|
|
|
|
# Create a test file
|
|
doc_path = "documents/test-doc.pdf"
|
|
original_content = b"%PDF-1.4 test content"
|
|
backend.upload_bytes(original_content, doc_path)
|
|
|
|
from inference.web.services.document_service import DocumentService
|
|
|
|
service = DocumentService(admin_db=mock_admin_db, storage_backend=backend)
|
|
|
|
content = service.download_document(doc_path)
|
|
|
|
assert content == original_content
|
|
|
|
|
|
class TestStorageBackendInImageServing:
|
|
"""Tests for storage backend usage in image serving."""
|
|
|
|
def test_get_page_image_url_returns_presigned_url(
|
|
self, tmp_path: Path, mock_admin_db: MagicMock
|
|
) -> None:
|
|
"""Test that page image URL uses presigned URL."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
storage_path = tmp_path / "storage"
|
|
storage_path.mkdir(parents=True, exist_ok=True)
|
|
backend = LocalStorageBackend(str(storage_path))
|
|
|
|
# Create a test image
|
|
image_path = "images/doc-123/page_1.png"
|
|
backend.upload_bytes(b"fake png content", image_path)
|
|
|
|
from inference.web.services.document_service import DocumentService
|
|
|
|
service = DocumentService(admin_db=mock_admin_db, storage_backend=backend)
|
|
|
|
url = service.get_page_image_url("doc-123", 1)
|
|
|
|
assert url is not None
|
|
assert "page_1.png" in url
|
|
|
|
def test_save_page_image_uses_storage_backend(
|
|
self, tmp_path: Path, mock_admin_db: MagicMock
|
|
) -> None:
|
|
"""Test that page image saving uses storage backend."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
storage_path = tmp_path / "storage"
|
|
storage_path.mkdir(parents=True, exist_ok=True)
|
|
backend = LocalStorageBackend(str(storage_path))
|
|
|
|
from inference.web.services.document_service import DocumentService
|
|
|
|
service = DocumentService(admin_db=mock_admin_db, storage_backend=backend)
|
|
|
|
image_content = b"fake png content"
|
|
service.save_page_image("doc-123", 1, image_content)
|
|
|
|
# Verify image was stored
|
|
assert backend.exists("images/doc-123/page_1.png")
|
|
|
|
|
|
class TestStorageBackendInDocumentDeletion:
|
|
"""Tests for storage backend usage in document deletion."""
|
|
|
|
def test_delete_document_removes_from_storage(
|
|
self, tmp_path: Path, mock_admin_db: MagicMock
|
|
) -> None:
|
|
"""Test that document deletion removes file from storage."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
storage_path = tmp_path / "storage"
|
|
storage_path.mkdir(parents=True, exist_ok=True)
|
|
backend = LocalStorageBackend(str(storage_path))
|
|
|
|
# Create test files
|
|
doc_path = "documents/test-doc.pdf"
|
|
backend.upload_bytes(b"%PDF-1.4 test", doc_path)
|
|
|
|
from inference.web.services.document_service import DocumentService
|
|
|
|
service = DocumentService(admin_db=mock_admin_db, storage_backend=backend)
|
|
|
|
service.delete_document_files(doc_path)
|
|
|
|
assert not backend.exists(doc_path)
|
|
|
|
def test_delete_document_removes_images(
|
|
self, tmp_path: Path, mock_admin_db: MagicMock
|
|
) -> None:
|
|
"""Test that document deletion removes associated images."""
|
|
from shared.storage.local import LocalStorageBackend
|
|
|
|
storage_path = tmp_path / "storage"
|
|
storage_path.mkdir(parents=True, exist_ok=True)
|
|
backend = LocalStorageBackend(str(storage_path))
|
|
|
|
# Create test files
|
|
doc_id = "test-doc-123"
|
|
backend.upload_bytes(b"img1", f"images/{doc_id}/page_1.png")
|
|
backend.upload_bytes(b"img2", f"images/{doc_id}/page_2.png")
|
|
|
|
from inference.web.services.document_service import DocumentService
|
|
|
|
service = DocumentService(admin_db=mock_admin_db, storage_backend=backend)
|
|
|
|
service.delete_document_images(doc_id)
|
|
|
|
assert not backend.exists(f"images/{doc_id}/page_1.png")
|
|
assert not backend.exists(f"images/{doc_id}/page_2.png")
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_admin_db() -> MagicMock:
|
|
"""Create a mock AdminDB for testing."""
|
|
mock = MagicMock()
|
|
mock.get_document.return_value = None
|
|
mock.create_document.return_value = MagicMock(
|
|
id="test-doc-id",
|
|
file_path="documents/test-doc-id.pdf",
|
|
)
|
|
return mock
|