Files
GOV_QR_Codes_Management/db_verification.py
T
2025-08-10 10:51:19 -04:00

586 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
MySQL Database Verification Script for QR Attendance System
This script verifies that the MySQL database migration was successful
and all functionality is working correctly.
Usage:
python verify_mysql_database.py
This script will:
1. Test MySQL database connection
2. Verify all tables exist
3. Check data integrity
4. Test application functionality
5. Validate performance
Author: QR Attendance System Verification Team
Version: 1.0
"""
import sys
import os
from datetime import datetime, date, timedelta
import time
# Add your app directory to path
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
try:
from app import app, db, User, QRCode, AttendanceData
from app import get_location_accuracy_level_enhanced, calculate_location_accuracy_enhanced
from sqlalchemy import text, inspect
except ImportError as e:
print(f"❌ Error importing modules: {e}")
sys.exit(1)
class MySQLVerifier:
"""MySQL database verification class"""
def __init__(self):
self.app = app
self.issues = []
self.test_results = {}
def log_result(self, test_name, passed, message=""):
"""Log test result"""
self.test_results[test_name] = {
'passed': passed,
'message': message,
'timestamp': datetime.now()
}
if not passed:
self.issues.append(f"{test_name}: {message}")
def test_database_connection(self):
"""Test basic MySQL database connectivity"""
print("🔌 Testing MySQL Database Connection...")
try:
with self.app.app_context():
# Test basic connection
result = db.session.execute(text("SELECT 1 as test")).fetchone()
if result.test == 1:
print(" ✅ MySQL connection successful")
# Get MySQL version
version_result = db.session.execute(text("SELECT VERSION() as version")).fetchone()
print(f" 📊 MySQL Version: {version_result.version}")
# Get database name
db_result = db.session.execute(text("SELECT DATABASE() as db_name")).fetchone()
print(f" 📊 Database: {db_result.db_name}")
self.log_result("database_connection", True, "MySQL connection working")
return True
else:
self.log_result("database_connection", False, "Connection test failed")
return False
except Exception as e:
print(f" ❌ MySQL connection failed: {e}")
self.log_result("database_connection", False, str(e))
return False
def test_table_structure(self):
"""Verify all required tables exist with correct structure"""
print("\n📋 Testing Table Structure...")
try:
with self.app.app_context():
inspector = inspect(db.engine)
tables = inspector.get_table_names()
required_tables = ['users', 'qr_codes', 'attendance_data']
print(f" 📊 Found tables: {', '.join(tables)}")
all_tables_exist = True
for table in required_tables:
if table in tables:
print(f" ✅ {table} table exists")
# Check key columns
columns = inspector.get_columns(table)
column_names = [col['name'] for col in columns]
if table == 'users':
required_cols = ['id', 'username', 'email', 'password_hash', 'role']
elif table == 'qr_codes':
required_cols = ['id', 'name', 'location', 'qr_code_image']
elif table == 'attendance_data':
required_cols = ['id', 'qr_code_id', 'employee_id', 'check_in_date']
missing_cols = [col for col in required_cols if col not in column_names]
if missing_cols:
print(f" ⚠️ Missing columns in {table}: {missing_cols}")
all_tables_exist = False
else:
print(f" ✅ {table} has all required columns")
else:
print(f" ❌ {table} table missing")
all_tables_exist = False
self.log_result("table_structure", all_tables_exist,
"All tables exist" if all_tables_exist else "Missing tables/columns")
return all_tables_exist
except Exception as e:
print(f" ❌ Error checking table structure: {e}")
self.log_result("table_structure", False, str(e))
return False
def test_data_integrity(self):
"""Test data integrity and relationships"""
print("\n🔍 Testing Data Integrity...")
try:
with self.app.app_context():
# Count records
user_count = User.query.count()
qr_count = QRCode.query.count()
attendance_count = AttendanceData.query.count()
print(f" 📊 Record counts:")
print(f" Users: {user_count:,}")
print(f" QR Codes: {qr_count:,}")
print(f" Attendance: {attendance_count:,}")
# Test relationships
relationship_issues = []
# Check foreign key relationships
orphaned_qr_codes = db.session.execute(text("""
SELECT COUNT(*) as count FROM qr_codes qr
LEFT JOIN users u ON qr.created_by = u.id
WHERE u.id IS NULL AND qr.created_by IS NOT NULL
""")).fetchone().count
if orphaned_qr_codes > 0:
relationship_issues.append(f"{orphaned_qr_codes} QR codes with invalid creator")
print(f" ⚠️ {orphaned_qr_codes} QR codes with invalid creator references")
orphaned_attendance = db.session.execute(text("""
SELECT COUNT(*) as count FROM attendance_data ad
LEFT JOIN qr_codes qr ON ad.qr_code_id = qr.id
WHERE qr.id IS NULL
""")).fetchone().count
if orphaned_attendance > 0:
relationship_issues.append(f"{orphaned_attendance} attendance records with invalid QR code")
print(f" ⚠️ {orphaned_attendance} attendance records with invalid QR code references")
# Check for duplicate usernames/emails
duplicate_usernames = db.session.execute(text("""
SELECT username, COUNT(*) as count FROM users
GROUP BY username HAVING COUNT(*) > 1
""")).fetchall()
if duplicate_usernames:
relationship_issues.append(f"Duplicate usernames found")
print(f" ⚠️ Duplicate usernames: {[row.username for row in duplicate_usernames]}")
# Check location accuracy data
attendance_with_location = AttendanceData.query.filter(
AttendanceData.latitude.isnot(None)
).count()
print(f" 📊 Attendance records with location data: {attendance_with_location:,}")
if len(relationship_issues) == 0:
print(" ✅ All data integrity checks passed")
self.log_result("data_integrity", True, "Data integrity verified")
return True
else:
print(f" ❌ Data integrity issues found")
self.log_result("data_integrity", False, "; ".join(relationship_issues))
return False
except Exception as e:
print(f" ❌ Error checking data integrity: {e}")
self.log_result("data_integrity", False, str(e))
return False
def test_mysql_specific_features(self):
"""Test MySQL-specific features and compatibility"""
print("\n🔧 Testing MySQL-Specific Features...")
try:
with self.app.app_context():
# Test MySQL engine type
engine_result = db.session.execute(text("""
SELECT ENGINE FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users'
""")).fetchone()
if engine_result:
engine = engine_result.ENGINE
print(f" 📊 MySQL Storage Engine: {engine}")
if engine == 'InnoDB':
print(" ✅ Using InnoDB engine (supports transactions)")
else:
print(f" ⚠️ Using {engine} engine (consider InnoDB for ACID compliance)")
# Test character set
charset_result = db.session.execute(text("""
SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME = DATABASE()
""")).fetchone()
if charset_result:
charset = charset_result.DEFAULT_CHARACTER_SET_NAME
collation = charset_result.DEFAULT_COLLATION_NAME
print(f" 📊 Character Set: {charset}, Collation: {collation}")
if charset in ['utf8mb4', 'utf8']:
print(" ✅ UTF-8 character set configured")
else:
print(f" ⚠️ Consider using utf8mb4 character set")
# Test auto-increment functionality
test_user = User(
full_name='Test User',
email='test@example.com',
username=f'testuser_{int(time.time())}',
role='staff'
)
test_user.set_password('testpass')
db.session.add(test_user)
db.session.commit()
if test_user.id:
print(f" ✅ Auto-increment working (generated ID: {test_user.id})")
# Clean up test user
db.session.delete(test_user)
db.session.commit()
self.log_result("mysql_features", True, "MySQL features working correctly")
return True
else:
print(" ❌ Auto-increment not working")
self.log_result("mysql_features", False, "Auto-increment failed")
return False
except Exception as e:
print(f" ❌ Error testing MySQL features: {e}")
self.log_result("mysql_features", False, str(e))
return False
def test_application_functionality(self):
"""Test core application functionality"""
print("\n🧪 Testing Application Functionality...")
try:
with self.app.app_context():
# Test user authentication functions
admin_user = User.query.filter_by(role='admin').first()
if admin_user:
print(f" ✅ Admin user found: {admin_user.username}")
# Test password hashing
if admin_user.check_password('admin123'): # Default password
print(" ⚠️ Default admin password detected - please change it")
print(" ✅ Password hashing functionality working")
else:
print(" ⚠️ No admin user found")
# Test QR code functionality
qr_code_sample = QRCode.query.first()
if qr_code_sample:
print(f" ✅ QR code data accessible")
# Test coordinate functionality if available
if qr_code_sample.has_coordinates:
print(f" ✅ QR code coordinates available: {qr_code_sample.coordinates_display}")
else:
print(" ️ QR code coordinates not set (optional feature)")
# Test attendance functionality
attendance_sample = AttendanceData.query.first()
if attendance_sample:
print(f" ✅ Attendance data accessible")
# Test location accuracy if available
if attendance_sample.has_location_data:
accuracy_level = attendance_sample.location_accuracy_level
print(f" ✅ Location accuracy calculation working: {accuracy_level}")
else:
print(" ️ No location data in sample (feature works when GPS available)")
# Test relationships
if qr_code_sample and attendance_sample:
qr_with_attendance = QRCode.query.join(AttendanceData).first()
if qr_with_attendance:
print(" ✅ Database relationships working correctly")
else:
print(" ⚠️ No QR codes with attendance records found")
self.log_result("application_functionality", True, "Core functionality verified")
return True
except Exception as e:
print(f" ❌ Error testing application functionality: {e}")
self.log_result("application_functionality", False, str(e))
return False
def test_performance(self):
"""Test basic database performance"""
print("\n⚡ Testing Database Performance...")
try:
with self.app.app_context():
# Test query performance
start_time = time.time()
# Complex join query
complex_query = db.session.query(AttendanceData, QRCode, User).join(
QRCode, AttendanceData.qr_code_id == QRCode.id
).join(
User, QRCode.created_by == User.id
).limit(100).all()
query_time = time.time() - start_time
print(f" 📊 Complex join query time: {query_time:.3f} seconds")
if query_time < 1.0:
print(" ✅ Query performance acceptable")
performance_ok = True
else:
print(" ⚠️ Query performance slow - consider adding indexes")
performance_ok = False
# Test bulk operations
start_time = time.time()
bulk_count = AttendanceData.query.count()
count_time = time.time() - start_time
print(f" 📊 Count query time ({bulk_count:,} records): {count_time:.3f} seconds")
# Test connection pooling
start_time = time.time()
for i in range(10):
db.session.execute(text("SELECT 1")).fetchone()
pool_time = time.time() - start_time
print(f" 📊 Connection pool test (10 queries): {pool_time:.3f} seconds")
self.log_result("performance", performance_ok,
f"Query time: {query_time:.3f}s" if performance_ok else "Performance issues detected")
return performance_ok
except Exception as e:
print(f" ❌ Error testing performance: {e}")
self.log_result("performance", False, str(e))
return False
def test_migration_completeness(self):
"""Test that migration preserved all data correctly"""
print("\n📊 Testing Migration Completeness...")
try:
with self.app.app_context():
# Check for common migration issues
issues_found = []
# Test datetime handling
recent_records = AttendanceData.query.filter(
AttendanceData.created_timestamp >= datetime.now() - timedelta(days=30)
).count()
if recent_records > 0:
print(f" ✅ Recent timestamps preserved ({recent_records} records)")
else:
print(" ️ No recent records found (expected if migrating old data)")
# Test text field preservation
qr_with_long_text = QRCode.query.filter(
db.func.length(QRCode.qr_code_image) > 1000
).first()
if qr_with_long_text:
print(" ✅ Large text fields (QR images) preserved correctly")
else:
print(" ⚠️ No large text fields found - check QR code images")
issues_found.append("QR code images may not be preserved")
# Test float/decimal precision
attendance_with_coords = AttendanceData.query.filter(
AttendanceData.latitude.isnot(None)
).first()
if attendance_with_coords:
lat_precision = len(str(attendance_with_coords.latitude).split('.')[-1]) if '.' in str(attendance_with_coords.latitude) else 0
if lat_precision >= 6:
print(f" ✅ Coordinate precision preserved ({lat_precision} decimal places)")
else:
print(f" ⚠️ Coordinate precision may be reduced ({lat_precision} decimal places)")
issues_found.append("Coordinate precision reduced")
# Test boolean field handling
active_users = User.query.filter_by(active_status=True).count()
inactive_users = User.query.filter_by(active_status=False).count()
print(f" 📊 User status: {active_users} active, {inactive_users} inactive")
if active_users > 0:
print(" ✅ Boolean fields working correctly")
# Test foreign key constraints
try:
# Try to insert invalid foreign key
invalid_attendance = AttendanceData(
qr_code_id=99999, # Non-existent QR code
employee_id='TEST',
location_name='Test',
check_in_date=date.today(),
check_in_time=datetime.now().time()
)
db.session.add(invalid_attendance)
db.session.commit()
# If we get here, foreign key constraint failed
db.session.delete(invalid_attendance)
db.session.commit()
print(" ⚠️ Foreign key constraints not enforced")
issues_found.append("Foreign key constraints not working")
except Exception:
# This is expected - foreign key constraint should prevent the insert
db.session.rollback()
print(" ✅ Foreign key constraints working correctly")
migration_complete = len(issues_found) == 0
self.log_result("migration_completeness", migration_complete,
"Migration complete" if migration_complete else "; ".join(issues_found))
return migration_complete
except Exception as e:
print(f" ❌ Error testing migration completeness: {e}")
self.log_result("migration_completeness", False, str(e))
return False
def generate_report(self):
"""Generate comprehensive verification report"""
print("\n📋 GENERATING VERIFICATION REPORT")
print("=" * 60)
passed_tests = sum(1 for result in self.test_results.values() if result['passed'])
total_tests = len(self.test_results)
success_rate = (passed_tests / total_tests * 100) if total_tests > 0 else 0
print(f"📊 SUMMARY:")
print(f" Tests Passed: {passed_tests}/{total_tests} ({success_rate:.1f}%)")
print(f" Issues Found: {len(self.issues)}")
print(f"\n📝 DETAILED RESULTS:")
for test_name, result in self.test_results.items():
status = "✅ PASS" if result['passed'] else "❌ FAIL"
print(f" {status} {test_name}")
if result['message']:
print(f" {result['message']}")
if self.issues:
print(f"\n⚠️ ISSUES TO ADDRESS:")
for i, issue in enumerate(self.issues, 1):
print(f" {i}. {issue}")
print(f"\n🎯 RECOMMENDATIONS:")
if success_rate >= 90:
print(" ✅ Migration appears successful - ready for production use")
print(" ✅ Perform additional application testing")
print(" ✅ Set up regular MySQL backups")
print(" ✅ Monitor performance in production")
elif success_rate >= 70:
print(" ⚠️ Migration mostly successful but has issues")
print(" ⚠️ Address the issues listed above before production use")
print(" ⚠️ Consider additional testing")
else:
print(" ❌ Migration has significant issues")
print(" ❌ Do not use in production until issues are resolved")
print(" ❌ Consider re-running migration process")
# Save report to file
report_filename = f"mysql_verification_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
try:
with open(report_filename, 'w') as f:
f.write(f"MySQL Database Verification Report\n")
f.write(f"Generated: {datetime.now().isoformat()}\n")
f.write(f"=" * 50 + "\n\n")
f.write(f"Summary:\n")
f.write(f"Tests Passed: {passed_tests}/{total_tests} ({success_rate:.1f}%)\n")
f.write(f"Issues Found: {len(self.issues)}\n\n")
f.write(f"Detailed Results:\n")
for test_name, result in self.test_results.items():
status = "PASS" if result['passed'] else "FAIL"
f.write(f"{status}: {test_name}\n")
if result['message']:
f.write(f" Message: {result['message']}\n")
f.write(f" Time: {result['timestamp'].isoformat()}\n\n")
if self.issues:
f.write(f"Issues:\n")
for i, issue in enumerate(self.issues, 1):
f.write(f"{i}. {issue}\n")
print(f"\n📄 Report saved to: {report_filename}")
except Exception as e:
print(f"⚠️ Could not save report to file: {e}")
return success_rate >= 90
def main():
"""Main verification process"""
print("MYSQL DATABASE VERIFICATION")
print("=" * 60)
print(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print()
verifier = MySQLVerifier()
# Run all verification tests
tests = [
verifier.test_database_connection,
verifier.test_table_structure,
verifier.test_data_integrity,
verifier.test_mysql_specific_features,
verifier.test_application_functionality,
verifier.test_performance,
verifier.test_migration_completeness
]
try:
for test in tests:
if not test():
print(f"\n⚠️ Test failed: {test.__name__}")
except KeyboardInterrupt:
print("\n\n⚠️ Verification interrupted by user")
return False
except Exception as e:
print(f"\n❌ Unexpected error during verification: {e}")
import traceback
traceback.print_exc()
return False
# Generate final report
success = verifier.generate_report()
print(f"\nVerification completed at {datetime.now().strftime('%H:%M:%S')}")
return success
if __name__ == '__main__':
success = main()
sys.exit(0 if success else 1)