""" Test Recovery Management Functionality Preservation This test suite verifies that all existing API endpoints, navigation, and component interfaces continue to work correctly after terminology changes. Requirements tested: - 4.1: Component interfaces remain unchanged - 4.2: API endpoints continue to work - 4.3: Navigation and routing functionality is preserved - 4.4: Service method signatures stay the same - 4.5: Database schemas remain unchanged """ import pytest import requests from fastapi.testclient import TestClient from sqlalchemy.orm import Session from datetime import datetime from main import app from database import get_db, engine from models.user import User, UserRole from models.project import Project from models.shot import Shot from models.asset import Asset, AssetCategory from models.task import Task, TaskStatus from utils.auth import create_access_token from services.recovery_service import RecoveryService client = TestClient(app) # Test data setup @pytest.fixture def db_session(): """Create a test database session""" from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from models import Base # Use in-memory SQLite for testing test_engine = create_engine("sqlite:///:memory:", echo=False) Base.metadata.create_all(bind=test_engine) TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=test_engine) session = TestingSessionLocal() yield session session.close() @pytest.fixture def admin_user(db_session): """Create an admin user for testing""" admin = User( username="admin_test", email="admin@test.com", full_name="Admin Test User", role=UserRole.admin, is_admin=True, is_approved=True, hashed_password="fake_hash" ) db_session.add(admin) db_session.commit() db_session.refresh(admin) return admin @pytest.fixture def test_project(db_session): """Create a test project""" project = Project( name="Test Project", description="Test project for recovery testing", status="active", created_by=1 ) db_session.add(project) db_session.commit() db_session.refresh(project) return project @pytest.fixture def deleted_shot(db_session, test_project, admin_user): """Create a soft-deleted shot for testing""" shot = Shot( name="TEST_001", episode_name="Episode 1", project_id=test_project.id, created_by=admin_user.id, is_deleted=True, deleted_at=datetime.utcnow(), deleted_by=admin_user.id ) db_session.add(shot) db_session.commit() db_session.refresh(shot) return shot @pytest.fixture def deleted_asset(db_session, test_project, admin_user): """Create a soft-deleted asset for testing""" asset = Asset( name="TestCharacter", category=AssetCategory.character, project_id=test_project.id, created_by=admin_user.id, is_deleted=True, deleted_at=datetime.utcnow(), deleted_by=admin_user.id ) db_session.add(asset) db_session.commit() db_session.refresh(asset) return asset @pytest.fixture def admin_token(admin_user): """Create an admin access token""" return create_access_token(data={"user_id": admin_user.id}) def override_get_db(db_session): """Override the database dependency for testing""" def _get_db(): try: yield db_session finally: pass return _get_db class TestAPIEndpointPreservation: """Test that all existing API endpoints continue to work""" def test_get_deleted_shots_endpoint(self, db_session, admin_token, deleted_shot): """Test that /admin/deleted-shots/ endpoint works correctly""" app.dependency_overrides[get_db] = override_get_db(db_session) response = client.get( "/api/admin/deleted-shots/", headers={"Authorization": f"Bearer {admin_token}"} ) assert response.status_code == 200 data = response.json() assert isinstance(data, list) if data: # If there are deleted shots shot_data = data[0] required_fields = [ "id", "name", "episode_name", "project_id", "project_name", "deleted_at", "deleted_by", "deleted_by_name", "task_count", "submission_count", "attachment_count", "note_count", "review_count" ] for field in required_fields: assert field in shot_data, f"Missing field: {field}" def test_get_deleted_assets_endpoint(self, db_session, admin_token, deleted_asset): """Test that /admin/deleted-assets/ endpoint works correctly""" app.dependency_overrides[get_db] = override_get_db(db_session) response = client.get( "/api/admin/deleted-assets/", headers={"Authorization": f"Bearer {admin_token}"} ) assert response.status_code == 200 data = response.json() assert isinstance(data, list) if data: # If there are deleted assets asset_data = data[0] required_fields = [ "id", "name", "category", "project_id", "project_name", "deleted_at", "deleted_by", "deleted_by_name", "task_count", "submission_count", "attachment_count", "note_count", "review_count" ] for field in required_fields: assert field in asset_data, f"Missing field: {field}" def test_shot_recovery_preview_endpoint(self, db_session, admin_token, deleted_shot): """Test that shot recovery preview endpoint works correctly""" app.dependency_overrides[get_db] = override_get_db(db_session) response = client.get( f"/api/admin/shots/{deleted_shot.id}/recovery-preview", headers={"Authorization": f"Bearer {admin_token}"} ) # Should return 200 or 404 (if shot not found in recovery data) assert response.status_code in [200, 404] if response.status_code == 200: data = response.json() required_fields = [ "shot_id", "name", "episode_name", "project_name", "task_count", "submission_count", "attachment_count", "note_count", "review_count", "deleted_at", "deleted_by", "deleted_by_name", "files_preserved", "file_count" ] for field in required_fields: assert field in data, f"Missing field: {field}" def test_asset_recovery_preview_endpoint(self, db_session, admin_token, deleted_asset): """Test that asset recovery preview endpoint works correctly""" app.dependency_overrides[get_db] = override_get_db(db_session) response = client.get( f"/api/admin/assets/{deleted_asset.id}/recovery-preview", headers={"Authorization": f"Bearer {admin_token}"} ) # Should return 200 or 404 (if asset not found in recovery data) assert response.status_code in [200, 404] if response.status_code == 200: data = response.json() required_fields = [ "asset_id", "name", "project_name", "task_count", "submission_count", "attachment_count", "note_count", "review_count", "deleted_at", "deleted_by", "deleted_by_name", "files_preserved", "file_count" ] for field in required_fields: assert field in data, f"Missing field: {field}" def test_recovery_stats_endpoint(self, db_session, admin_token): """Test that recovery stats endpoint works correctly""" app.dependency_overrides[get_db] = override_get_db(db_session) response = client.get( "/api/admin/recovery-stats/", headers={"Authorization": f"Bearer {admin_token}"} ) assert response.status_code == 200 data = response.json() required_fields = [ "deleted_shots_count", "deleted_assets_count", "total_deleted_tasks", "total_deleted_files" ] for field in required_fields: assert field in data, f"Missing field: {field}" def test_bulk_recovery_endpoints_structure(self, db_session, admin_token): """Test that bulk recovery endpoints have correct structure""" app.dependency_overrides[get_db] = override_get_db(db_session) # Test bulk shot recovery with empty list (should return 400) response = client.post( "/api/admin/shots/bulk-recover", json={"shot_ids": []}, headers={"Authorization": f"Bearer {admin_token}"} ) assert response.status_code == 400 # Test bulk asset recovery with empty list (should return 400) response = client.post( "/api/admin/assets/bulk-recover", json={"asset_ids": []}, headers={"Authorization": f"Bearer {admin_token}"} ) assert response.status_code == 400 def test_endpoint_authentication_required(self): """Test that all endpoints require authentication""" endpoints = [ "/api/admin/deleted-shots/", "/api/admin/deleted-assets/", "/api/admin/recovery-stats/" ] for endpoint in endpoints: response = client.get(endpoint) assert response.status_code == 401, f"Endpoint {endpoint} should require authentication" class TestServiceInterfacePreservation: """Test that service method signatures remain unchanged""" def test_recovery_service_interface(self, db_session): """Test that RecoveryService maintains its interface""" service = RecoveryService() # Test that all expected methods exist expected_methods = [ 'get_deleted_shots', 'get_deleted_assets', 'preview_shot_recovery', 'preview_asset_recovery', 'recover_shot', 'recover_asset', 'bulk_recover_shots', 'bulk_recover_assets', 'get_recovery_stats' ] for method_name in expected_methods: assert hasattr(service, method_name), f"Missing method: {method_name}" method = getattr(service, method_name) assert callable(method), f"Method {method_name} is not callable" def test_recovery_service_method_signatures(self, db_session): """Test that method signatures haven't changed""" service = RecoveryService() # Test get_deleted_shots signature try: # Should accept project_id and db parameters service.get_deleted_shots(None, db_session) except Exception as e: # Method should exist and accept these parameters # Any other error is acceptable for this interface test assert "missing" not in str(e).lower() # Test get_deleted_assets signature try: service.get_deleted_assets(None, db_session) except Exception as e: assert "missing" not in str(e).lower() class TestComponentInterfacePreservation: """Test that component interfaces remain unchanged""" def test_recovery_service_types_preserved(self): """Test that TypeScript interfaces are preserved""" # This test verifies that the service file structure is maintained import os service_file = "frontend/src/services/recovery.ts" assert os.path.exists(service_file), "Recovery service file should exist" with open(service_file, 'r') as f: content = f.read() # Check that key interfaces are still defined required_interfaces = [ "DeletedShot", "DeletedAsset", "RecoveryInfo", "RecoveryResult", "BulkRecoveryResult" ] for interface in required_interfaces: assert f"interface {interface}" in content, f"Missing interface: {interface}" # Check that key methods are still defined required_methods = [ "getDeletedShots", "getDeletedAssets", "previewShotRecovery", "previewAssetRecovery", "recoverShot", "recoverAsset", "bulkRecoverShots", "bulkRecoverAssets" ] for method in required_methods: assert method in content, f"Missing method: {method}" class TestNavigationAndRoutingPreservation: """Test that navigation and routing functionality is preserved""" def test_router_configuration_preserved(self): """Test that router configuration maintains existing routes""" import os router_file = "frontend/src/router/index.ts" assert os.path.exists(router_file), "Router file should exist" with open(router_file, 'r') as f: content = f.read() # Check that the recovery management route exists assert "/admin/deleted-items" in content, "Recovery management route should exist" assert "RecoveryManagement" in content, "RecoveryManagement route name should exist" assert "DeletedItemsManagementView" in content, "Component import should exist" def test_sidebar_navigation_preserved(self): """Test that sidebar navigation structure is preserved""" import os sidebar_file = "frontend/src/components/layout/AppSidebar.vue" assert os.path.exists(sidebar_file), "Sidebar file should exist" with open(sidebar_file, 'r') as f: content = f.read() # Check that admin navigation section exists assert "adminItems" in content, "Admin items should be defined" assert "/admin/deleted-items" in content, "Recovery management URL should exist" assert "RotateCcw" in content, "Recovery icon should be imported" def test_component_file_structure_preserved(self): """Test that component file structure is maintained""" import os # Check that the main component file exists component_file = "frontend/src/views/admin/DeletedItemsManagementView.vue" assert os.path.exists(component_file), "Main recovery component should exist" # Check that the component has the expected structure with open(component_file, 'r') as f: content = f.read() # Verify key component elements are present assert "