""" 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