Change db to Mysql

This commit is contained in:
2025-08-10 10:51:19 -04:00
parent be9f913fbb
commit 0e517b110f
7 changed files with 4219 additions and 21 deletions
+586
View File
@@ -0,0 +1,586 @@
#!/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)