Init Repo

This commit is contained in:
2026-02-28 03:22:04 +08:00
commit de59b57ee7
883 changed files with 156857 additions and 0 deletions
@@ -0,0 +1,455 @@
"""
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 "<template>" in content, "Component should have template section"
assert "<script setup" in content, "Component should use composition API"
assert "recoveryService" in content, "Component should use recovery service"
class TestDatabaseSchemaPreservation:
"""Test that database schemas remain unchanged"""
def test_shot_model_schema_preserved(self, db_session):
"""Test that Shot model maintains required fields"""
from models.shot import Shot
from sqlalchemy import inspect
inspector = inspect(Shot)
column_names = [column.name for column in inspector.columns]
required_fields = [
'id', 'name', 'episode_name', 'project_id',
'is_deleted', 'deleted_at', 'deleted_by'
]
for field in required_fields:
assert field in column_names, f"Shot model missing field: {field}"
def test_asset_model_schema_preserved(self, db_session):
"""Test that Asset model maintains required fields"""
from models.asset import Asset
from sqlalchemy import inspect
inspector = inspect(Asset)
column_names = [column.name for column in inspector.columns]
required_fields = [
'id', 'name', 'category', 'project_id',
'is_deleted', 'deleted_at', 'deleted_by'
]
for field in required_fields:
assert field in column_names, f"Asset model missing field: {field}"
if __name__ == "__main__":
# Run the tests
pytest.main([__file__, "-v"])