LinkDesk/backend/validate_migration_results.py

283 lines
10 KiB
Python

#!/usr/bin/env python3
"""
Validation script to verify migration results and file accessibility.
This script validates that:
1. All file paths in the database are now relative
2. All files are accessible via their relative paths
3. File serving endpoints will work correctly
Requirements addressed: 1.5, 4.3, 4.5
"""
import sys
import os
from pathlib import Path
from typing import List, Tuple
# Add the backend directory to the path
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from sqlalchemy.orm import sessionmaker
from database import engine
from models.task import Submission, TaskAttachment
from models.project import Project
class MigrationValidator:
"""Validates migration results and file accessibility."""
def __init__(self):
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
self.backend_dir = Path(__file__).parent.resolve()
self.issues = []
def is_relative_path(self, path: str) -> bool:
"""Check if a path is relative."""
if not path:
return False
return not Path(path).is_absolute()
def validate_file_accessibility(self, relative_path: str) -> Tuple[bool, str]:
"""Validate that a file is accessible via its relative path."""
try:
full_path = self.backend_dir / relative_path
exists = full_path.exists()
return exists, str(full_path)
except Exception as e:
return False, f"Error: {e}"
def validate_submissions(self) -> dict:
"""Validate submissions table."""
print("Validating submissions table...")
db = self.SessionLocal()
results = {
'total': 0,
'relative_paths': 0,
'accessible_files': 0,
'issues': []
}
try:
submissions = db.query(Submission).all()
results['total'] = len(submissions)
for submission in submissions:
# Check if path is relative
if self.is_relative_path(submission.file_path):
results['relative_paths'] += 1
# Check if file is accessible
accessible, full_path = self.validate_file_accessibility(submission.file_path)
if accessible:
results['accessible_files'] += 1
else:
issue = f"Submission {submission.id}: File not accessible at {full_path}"
results['issues'].append(issue)
self.issues.append(issue)
else:
issue = f"Submission {submission.id}: Still has absolute path: {submission.file_path}"
results['issues'].append(issue)
self.issues.append(issue)
finally:
db.close()
return results
def validate_attachments(self) -> dict:
"""Validate task_attachments table."""
print("Validating task_attachments table...")
db = self.SessionLocal()
results = {
'total': 0,
'relative_paths': 0,
'accessible_files': 0,
'issues': []
}
try:
attachments = db.query(TaskAttachment).all()
results['total'] = len(attachments)
for attachment in attachments:
# Check if path is relative
if self.is_relative_path(attachment.file_path):
results['relative_paths'] += 1
# Check if file is accessible
accessible, full_path = self.validate_file_accessibility(attachment.file_path)
if accessible:
results['accessible_files'] += 1
else:
issue = f"Attachment {attachment.id}: File not accessible at {full_path}"
results['issues'].append(issue)
self.issues.append(issue)
else:
issue = f"Attachment {attachment.id}: Still has absolute path: {attachment.file_path}"
results['issues'].append(issue)
self.issues.append(issue)
finally:
db.close()
return results
def validate_projects(self) -> dict:
"""Validate projects table."""
print("Validating projects table...")
db = self.SessionLocal()
results = {
'total': 0,
'relative_paths': 0,
'accessible_files': 0,
'issues': []
}
try:
projects = db.query(Project).filter(Project.thumbnail_path.isnot(None)).all()
results['total'] = len(projects)
for project in projects:
# Check if path is relative
if self.is_relative_path(project.thumbnail_path):
results['relative_paths'] += 1
# Check if file is accessible
accessible, full_path = self.validate_file_accessibility(project.thumbnail_path)
if accessible:
results['accessible_files'] += 1
else:
issue = f"Project {project.id}: Thumbnail not accessible at {full_path}"
results['issues'].append(issue)
self.issues.append(issue)
else:
issue = f"Project {project.id}: Still has absolute thumbnail path: {project.thumbnail_path}"
results['issues'].append(issue)
self.issues.append(issue)
finally:
db.close()
return results
def validate_path_format_consistency(self) -> dict:
"""Validate that all relative paths follow consistent format."""
print("Validating path format consistency...")
db = self.SessionLocal()
results = {
'consistent_format': 0,
'inconsistent_format': 0,
'issues': []
}
try:
# Expected format: uploads/[type]/[id]/[filename]
# Check submissions
submissions = db.query(Submission).all()
for submission in submissions:
if submission.file_path.startswith('uploads/submissions/'):
results['consistent_format'] += 1
else:
issue = f"Submission {submission.id}: Inconsistent path format: {submission.file_path}"
results['issues'].append(issue)
results['inconsistent_format'] += 1
# Check attachments
attachments = db.query(TaskAttachment).all()
for attachment in attachments:
if attachment.file_path.startswith('uploads/attachments/'):
results['consistent_format'] += 1
else:
issue = f"Attachment {attachment.id}: Inconsistent path format: {attachment.file_path}"
results['issues'].append(issue)
results['inconsistent_format'] += 1
# Check projects
projects = db.query(Project).filter(Project.thumbnail_path.isnot(None)).all()
for project in projects:
if project.thumbnail_path.startswith('uploads/project_thumbnails/'):
results['consistent_format'] += 1
else:
issue = f"Project {project.id}: Inconsistent thumbnail path format: {project.thumbnail_path}"
results['issues'].append(issue)
results['inconsistent_format'] += 1
finally:
db.close()
return results
def run_validation(self) -> bool:
"""Run complete validation and return success status."""
print("=" * 60)
print("MIGRATION VALIDATION REPORT")
print("=" * 60)
# Validate each table
submissions_results = self.validate_submissions()
attachments_results = self.validate_attachments()
projects_results = self.validate_projects()
format_results = self.validate_path_format_consistency()
# Print results
print("\nSUBMISSIONS:")
print(f" Total: {submissions_results['total']}")
print(f" Relative paths: {submissions_results['relative_paths']}")
print(f" Accessible files: {submissions_results['accessible_files']}")
print(f" Issues: {len(submissions_results['issues'])}")
print("\nATTACHMENTS:")
print(f" Total: {attachments_results['total']}")
print(f" Relative paths: {attachments_results['relative_paths']}")
print(f" Accessible files: {attachments_results['accessible_files']}")
print(f" Issues: {len(attachments_results['issues'])}")
print("\nPROJECTS:")
print(f" Total: {projects_results['total']}")
print(f" Relative paths: {projects_results['relative_paths']}")
print(f" Accessible files: {projects_results['accessible_files']}")
print(f" Issues: {len(projects_results['issues'])}")
print("\nPATH FORMAT:")
print(f" Consistent format: {format_results['consistent_format']}")
print(f" Inconsistent format: {format_results['inconsistent_format']}")
# Print issues if any
if self.issues:
print("\nISSUES FOUND:")
for issue in self.issues:
print(f" - {issue}")
# Determine overall success
total_issues = len(self.issues) + format_results['inconsistent_format']
print("\n" + "=" * 60)
if total_issues == 0:
print("✅ VALIDATION PASSED: All file paths are relative and accessible")
return True
else:
print(f"❌ VALIDATION FAILED: {total_issues} issues found")
return False
def main():
"""Main validation function."""
validator = MigrationValidator()
success = validator.run_validation()
if success:
print("\nMigration validation completed successfully!")
sys.exit(0)
else:
print("\nMigration validation found issues that need attention.")
sys.exit(1)
if __name__ == "__main__":
main()