170 lines
6.2 KiB
Python
170 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test script to verify the shot project_id migration.
|
|
Tests all requirements: 2.1, 2.2, 2.3, 2.4, 2.5
|
|
"""
|
|
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def test_migration():
|
|
"""Test that the migration meets all requirements."""
|
|
|
|
db_path = "vfx_project_management.db"
|
|
if not Path(db_path).exists():
|
|
print("❌ Database not found")
|
|
return False
|
|
|
|
try:
|
|
conn = sqlite3.connect(db_path)
|
|
cursor = conn.cursor()
|
|
|
|
print("Testing Shot Project ID Migration Requirements")
|
|
print("=" * 50)
|
|
|
|
# Requirement 2.1: Non-nullable project_id column
|
|
print("Testing Requirement 2.1: Non-nullable project_id column...")
|
|
cursor.execute("PRAGMA table_info(shots)")
|
|
columns = {col[1]: col for col in cursor.fetchall()}
|
|
|
|
if 'project_id' not in columns:
|
|
print("❌ project_id column not found")
|
|
return False
|
|
|
|
project_id_col = columns['project_id']
|
|
if project_id_col[3] != 1: # NOT NULL constraint
|
|
print("❌ project_id column is nullable")
|
|
return False
|
|
|
|
print("✅ project_id column exists and is NOT NULL")
|
|
|
|
# Requirement 2.2: Foreign key constraint to projects table
|
|
print("\nTesting Requirement 2.2: Foreign key constraint...")
|
|
cursor.execute("PRAGMA foreign_key_list(shots)")
|
|
foreign_keys = cursor.fetchall()
|
|
|
|
project_fk_found = False
|
|
for fk in foreign_keys:
|
|
if fk[2] == 'projects' and fk[3] == 'project_id':
|
|
project_fk_found = True
|
|
break
|
|
|
|
if not project_fk_found:
|
|
print("❌ Foreign key constraint to projects table not found")
|
|
return False
|
|
|
|
print("✅ Foreign key constraint to projects table exists")
|
|
|
|
# Requirement 2.3: Index on project_id column
|
|
print("\nTesting Requirement 2.3: Performance indexes...")
|
|
cursor.execute("PRAGMA index_list(shots)")
|
|
indexes = [index[1] for index in cursor.fetchall()]
|
|
|
|
required_indexes = ['idx_shots_project_id']
|
|
for index_name in required_indexes:
|
|
if index_name not in indexes:
|
|
print(f"❌ Index {index_name} not found")
|
|
return False
|
|
|
|
print("✅ Performance indexes exist")
|
|
|
|
# Requirement 2.4: Data preservation - all existing shots have project_id
|
|
print("\nTesting Requirement 2.4: Data preservation...")
|
|
cursor.execute("SELECT COUNT(*) FROM shots WHERE project_id IS NULL")
|
|
null_count = cursor.fetchone()[0]
|
|
|
|
if null_count > 0:
|
|
print(f"❌ {null_count} shots have NULL project_id")
|
|
return False
|
|
|
|
cursor.execute("SELECT COUNT(*) FROM shots")
|
|
total_shots = cursor.fetchone()[0]
|
|
print(f"✅ All {total_shots} shots have project_id populated")
|
|
|
|
# Requirement 2.5: Data integrity - project_id matches episode's project
|
|
print("\nTesting Requirement 2.5: Data integrity...")
|
|
cursor.execute("""
|
|
SELECT COUNT(*) FROM shots s
|
|
JOIN episodes e ON s.episode_id = e.id
|
|
WHERE s.project_id != e.project_id
|
|
""")
|
|
|
|
inconsistent_count = cursor.fetchone()[0]
|
|
if inconsistent_count > 0:
|
|
print(f"❌ {inconsistent_count} shots have inconsistent project_id")
|
|
return False
|
|
|
|
print("✅ All shots have consistent project_id with their episodes")
|
|
|
|
# Additional test: Unique constraint for project-scoped names
|
|
print("\nTesting project-scoped name uniqueness...")
|
|
|
|
# Check that unique index exists
|
|
if 'idx_shots_project_name_unique' not in indexes:
|
|
print("❌ Unique constraint index not found")
|
|
return False
|
|
|
|
# Test the constraint by trying to insert a duplicate
|
|
try:
|
|
# Get a sample shot to duplicate
|
|
cursor.execute("SELECT project_id, episode_id, name FROM shots LIMIT 1")
|
|
sample = cursor.fetchone()
|
|
if sample:
|
|
cursor.execute("""
|
|
INSERT INTO shots (project_id, episode_id, name, frame_start, frame_end)
|
|
VALUES (?, ?, ?, 1001, 1100)
|
|
""", sample)
|
|
print("❌ Duplicate shot name was allowed in same project")
|
|
return False
|
|
except sqlite3.IntegrityError:
|
|
print("✅ Unique constraint prevents duplicate names in same project")
|
|
|
|
# Performance test: Check query performance with indexes
|
|
print("\nTesting query performance with indexes...")
|
|
|
|
# Test project filtering query
|
|
cursor.execute("EXPLAIN QUERY PLAN SELECT * FROM shots WHERE project_id = 1")
|
|
query_plan = cursor.fetchall()
|
|
|
|
# Check if index is being used (should contain "idx_shots_project_id")
|
|
uses_index = any("idx_shots_project_id" in str(step) for step in query_plan)
|
|
if uses_index:
|
|
print("✅ Project filtering queries use index")
|
|
else:
|
|
print("⚠️ Project filtering queries may not be optimized")
|
|
|
|
print("\n" + "=" * 50)
|
|
print("✅ ALL REQUIREMENTS PASSED")
|
|
print("✅ Migration completed successfully")
|
|
|
|
# Summary
|
|
cursor.execute("""
|
|
SELECT p.name, COUNT(s.id) as shot_count
|
|
FROM projects p
|
|
LEFT JOIN shots s ON p.id = s.project_id AND s.deleted_at IS NULL
|
|
GROUP BY p.id, p.name
|
|
ORDER BY shot_count DESC
|
|
""")
|
|
|
|
print("\nShots per project:")
|
|
for project_name, shot_count in cursor.fetchall():
|
|
print(f" {project_name}: {shot_count} shots")
|
|
|
|
return True
|
|
|
|
except sqlite3.Error as e:
|
|
print(f"❌ Database error: {e}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"❌ Unexpected error: {e}")
|
|
return False
|
|
finally:
|
|
if conn:
|
|
conn.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
success = test_migration()
|
|
sys.exit(0 if success else 1) |