2026.06.04 frontend építés közben
This commit is contained in:
66
backend/scripts/check_person_schema.py
Normal file
66
backend/scripts/check_person_schema.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
async def check_person_schema():
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# Get columns of identity.persons
|
||||
query = text("""
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'identity' AND table_name = 'persons'
|
||||
ORDER BY ordinal_position;
|
||||
""")
|
||||
result = await session.execute(query)
|
||||
columns = result.fetchall()
|
||||
print("Columns in identity.persons:")
|
||||
for col in columns:
|
||||
print(f" {col[0]} ({col[1]})")
|
||||
|
||||
# Get columns of identity.users
|
||||
query2 = text("""
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'identity' AND table_name = 'users'
|
||||
ORDER BY ordinal_position;
|
||||
""")
|
||||
result2 = await session.execute(query2)
|
||||
columns2 = result2.fetchall()
|
||||
print("\nColumns in identity.users:")
|
||||
for col in columns2:
|
||||
print(f" {col[0]} ({col[1]})")
|
||||
|
||||
# Find relationship
|
||||
query3 = text("""
|
||||
SELECT u.email, u.person_id, p.id, p.first_name, p.last_name
|
||||
FROM identity.users u
|
||||
JOIN identity.persons p ON u.person_id = p.id
|
||||
WHERE u.email = 'tester_pro@profibot.hu'
|
||||
LIMIT 1;
|
||||
""")
|
||||
try:
|
||||
result3 = await session.execute(query3)
|
||||
user = result3.fetchone()
|
||||
if user:
|
||||
print(f"\nTest user found: email={user[0]}, user.person_id={user[1]}, persons.id={user[2]}, name={user[3]} {user[4]}")
|
||||
else:
|
||||
print("\nTest user not found")
|
||||
except Exception as e:
|
||||
print(f"\nError joining: {e}")
|
||||
# Try alternative column names
|
||||
pass
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_person_schema())
|
||||
52
backend/scripts/check_schema.py
Normal file
52
backend/scripts/check_schema.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sqlalchemy import text, inspect
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
async def check_schema():
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# Get columns of vehicle.assets
|
||||
query = text("""
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'vehicle' AND table_name = 'assets'
|
||||
ORDER BY ordinal_position;
|
||||
""")
|
||||
result = await session.execute(query)
|
||||
columns = result.fetchall()
|
||||
print("Columns in vehicle.assets:")
|
||||
for col in columns:
|
||||
print(f" {col[0]} ({col[1]})")
|
||||
|
||||
# Check if owner_user_id exists
|
||||
owner_exists = any(col[0] == 'owner_user_id' for col in columns)
|
||||
print(f"\nowner_user_id exists: {owner_exists}")
|
||||
|
||||
# Find alternative column names
|
||||
alt_columns = [col[0] for col in columns if 'user' in col[0] or 'owner' in col[0]]
|
||||
print(f"\nPossible owner/user columns: {alt_columns}")
|
||||
|
||||
# Also check identity.users email column
|
||||
query2 = text("""
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'identity' AND table_name = 'users'
|
||||
AND column_name = 'email';
|
||||
""")
|
||||
result2 = await session.execute(query2)
|
||||
email_col = result2.fetchone()
|
||||
print(f"\nidentity.users email column: {email_col}")
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_schema())
|
||||
121
backend/scripts/cleanup_test_vehicles.py
Normal file
121
backend/scripts/cleanup_test_vehicles.py
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cleanup script to delete test vehicles from the database.
|
||||
|
||||
Deletes vehicles that belong to the test user (tester_pro@profibot.hu)
|
||||
OR have status 'draft' OR have license plate starting with "TEST".
|
||||
|
||||
Handles foreign key constraints by deleting child records first.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the backend directory to the path so we can import app modules
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sqlalchemy import text, delete, select, and_, or_
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
async def cleanup_test_vehicles():
|
||||
"""Delete test vehicles and related records."""
|
||||
# Create async engine
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
deleted_counts = {
|
||||
'asset_events': 0,
|
||||
'asset_assignments': 0,
|
||||
'asset_telemetry': 0,
|
||||
'asset_financials': 0,
|
||||
'asset_costs': 0,
|
||||
'asset_inspections': 0,
|
||||
'asset_reviews': 0,
|
||||
'vehicle_expenses': 0,
|
||||
'vehicle_logbook': 0,
|
||||
'vehicle_ownership_history': 0,
|
||||
'vehicle_transfer_requests': 0,
|
||||
'service_requests': 0,
|
||||
'vehicle_assets': 0
|
||||
}
|
||||
|
||||
async with async_session() as session:
|
||||
try:
|
||||
# First, find all vehicle asset IDs that match our criteria
|
||||
# We need to join with identity.users and identity.persons to find test user's vehicles
|
||||
query = text("""
|
||||
SELECT va.id
|
||||
FROM vehicle.assets va
|
||||
LEFT JOIN identity.persons p ON va.owner_person_id = p.id
|
||||
LEFT JOIN identity.users u ON p.id = u.person_id
|
||||
WHERE u.email = 'tester_pro@profibot.hu'
|
||||
OR va.status = 'draft'
|
||||
OR va.license_plate LIKE 'TEST%'
|
||||
""")
|
||||
|
||||
result = await session.execute(query)
|
||||
asset_ids = [row[0] for row in result.fetchall()]
|
||||
|
||||
if not asset_ids:
|
||||
print("No test vehicles found matching criteria.")
|
||||
return deleted_counts
|
||||
|
||||
print(f"Found {len(asset_ids)} test vehicles to delete.")
|
||||
|
||||
# Delete from child tables first (order matters due to foreign keys)
|
||||
# List of tables with foreign keys to vehicle.assets.id and their column names
|
||||
tables_to_clean = [
|
||||
('fleet.asset_assignments', 'asset_id', 'asset_assignments'),
|
||||
('vehicle.asset_costs', 'asset_id', 'asset_costs'),
|
||||
('vehicle.asset_events', 'asset_id', 'asset_events'),
|
||||
('vehicle.asset_financials', 'asset_id', 'asset_financials'),
|
||||
('vehicle.asset_inspections', 'asset_id', 'asset_inspections'),
|
||||
('vehicle.asset_reviews', 'asset_id', 'asset_reviews'),
|
||||
('vehicle.asset_telemetry', 'asset_id', 'asset_telemetry'),
|
||||
('marketplace.service_requests', 'asset_id', 'service_requests'),
|
||||
('vehicle.vehicle_expenses', 'vehicle_id', 'vehicle_expenses'),
|
||||
('vehicle.vehicle_logbook', 'asset_id', 'vehicle_logbook'),
|
||||
('vehicle.vehicle_ownership_history', 'asset_id', 'vehicle_ownership_history'),
|
||||
('vehicle.vehicle_transfer_requests', 'asset_id', 'vehicle_transfer_requests'),
|
||||
]
|
||||
|
||||
for table, column, key in tables_to_clean:
|
||||
try:
|
||||
delete_query = text(f"DELETE FROM {table} WHERE {column} = ANY(:asset_ids)")
|
||||
result = await session.execute(delete_query, {"asset_ids": asset_ids})
|
||||
deleted_counts[key] = result.rowcount
|
||||
print(f"Deleted {result.rowcount} records from {table}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not delete from {table}: {e}")
|
||||
# Continue with other tables
|
||||
|
||||
# Finally, delete from vehicle.assets
|
||||
delete_assets = text("DELETE FROM vehicle.assets WHERE id = ANY(:asset_ids)")
|
||||
result = await session.execute(delete_assets, {"asset_ids": asset_ids})
|
||||
deleted_counts['vehicle_assets'] = result.rowcount
|
||||
print(f"Deleted {result.rowcount} records from vehicle.assets")
|
||||
|
||||
# Commit the transaction
|
||||
await session.commit()
|
||||
print("Cleanup completed successfully.")
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
print(f"Error during cleanup: {e}")
|
||||
raise
|
||||
|
||||
await engine.dispose()
|
||||
return deleted_counts
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Starting cleanup of test vehicles...")
|
||||
counts = asyncio.run(cleanup_test_vehicles())
|
||||
print("\nCleanup summary:")
|
||||
for table, count in counts.items():
|
||||
print(f" {table}: {count}")
|
||||
total = sum(counts.values())
|
||||
print(f"Total records deleted: {total}")
|
||||
56
backend/scripts/find_test_user.py
Normal file
56
backend/scripts/find_test_user.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
async def find_test_user():
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# Find user with email tester_pro@profibot.hu
|
||||
query = text("""
|
||||
SELECT u.user_id, u.email, p.person_id, p.first_name, p.last_name
|
||||
FROM identity.users u
|
||||
JOIN identity.persons p ON u.person_id = p.person_id
|
||||
WHERE u.email = 'tester_pro@profibot.hu'
|
||||
""")
|
||||
result = await session.execute(query)
|
||||
user = result.fetchone()
|
||||
if user:
|
||||
print(f"Test user found: user_id={user[0]}, email={user[1]}, person_id={user[2]}, name={user[3]} {user[4]}")
|
||||
else:
|
||||
print("Test user not found")
|
||||
|
||||
# Count vehicles owned by this person
|
||||
if user:
|
||||
query2 = text("""
|
||||
SELECT COUNT(*) FROM vehicle.assets
|
||||
WHERE owner_person_id = :person_id
|
||||
""")
|
||||
result2 = await session.execute(query2, {"person_id": user[2]})
|
||||
count = result2.scalar()
|
||||
print(f"Vehicles owned by this person: {count}")
|
||||
|
||||
# Count draft vehicles
|
||||
query3 = text("SELECT COUNT(*) FROM vehicle.assets WHERE status = 'draft'")
|
||||
result3 = await session.execute(query3)
|
||||
draft_count = result3.scalar()
|
||||
print(f"Draft vehicles: {draft_count}")
|
||||
|
||||
# Count vehicles with license plate starting with TEST
|
||||
query4 = text("SELECT COUNT(*) FROM vehicle.assets WHERE license_plate LIKE 'TEST%'")
|
||||
result4 = await session.execute(query4)
|
||||
test_plate_count = result4.scalar()
|
||||
print(f"Vehicles with TEST license plate: {test_plate_count}")
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(find_test_user())
|
||||
63
backend/scripts/list_related_tables.py
Normal file
63
backend/scripts/list_related_tables.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
async def list_related_tables():
|
||||
engine = create_async_engine(settings.DATABASE_URL, echo=False)
|
||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
async with async_session() as session:
|
||||
# Find foreign key constraints referencing vehicle.assets
|
||||
query = text("""
|
||||
SELECT
|
||||
tc.table_schema,
|
||||
tc.table_name,
|
||||
kcu.column_name,
|
||||
ccu.table_schema AS foreign_table_schema,
|
||||
ccu.table_name AS foreign_table_name,
|
||||
ccu.column_name AS foreign_column_name,
|
||||
rc.delete_rule
|
||||
FROM information_schema.table_constraints AS tc
|
||||
JOIN information_schema.key_column_usage AS kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
AND tc.table_schema = kcu.table_schema
|
||||
JOIN information_schema.constraint_column_usage AS ccu
|
||||
ON ccu.constraint_name = tc.constraint_name
|
||||
JOIN information_schema.referential_constraints AS rc
|
||||
ON rc.constraint_name = tc.constraint_name
|
||||
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
AND ccu.table_schema = 'vehicle'
|
||||
AND ccu.table_name = 'assets'
|
||||
AND ccu.column_name = 'id'
|
||||
ORDER BY tc.table_name;
|
||||
""")
|
||||
result = await session.execute(query)
|
||||
fks = result.fetchall()
|
||||
print("Foreign keys referencing vehicle.assets (id):")
|
||||
for fk in fks:
|
||||
print(f" {fk[0]}.{fk[1]}.{fk[2]} -> {fk[3]}.{fk[4]}.{fk[5]} (ON DELETE {fk[6]})")
|
||||
|
||||
# Also list tables in vehicle schema
|
||||
query2 = text("""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'vehicle'
|
||||
ORDER BY table_name;
|
||||
""")
|
||||
result2 = await session.execute(query2)
|
||||
tables = result2.fetchall()
|
||||
print("\nTables in vehicle schema:")
|
||||
for tbl in tables:
|
||||
print(f" {tbl[0]}")
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(list_related_tables())
|
||||
305
backend/scripts/purge_test_data.py
Normal file
305
backend/scripts/purge_test_data.py
Normal file
@@ -0,0 +1,305 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Ground Zero Database Purge Script
|
||||
|
||||
Deletes ALL test users (e.g., tester_pro@profibot.hu, noreply@...),
|
||||
and cascade deletes all their Organizations, Branches, and Assets.
|
||||
Outputs the exact number of deleted rows for each table.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from sqlalchemy import text, delete, select
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
# Import settings
|
||||
sys.path.insert(0, '/app')
|
||||
from app.core.config import settings
|
||||
|
||||
async def purge_test_data():
|
||||
"""Main purge function"""
|
||||
# Create async engine
|
||||
engine = create_async_engine(
|
||||
str(settings.SQLALCHEMY_DATABASE_URI),
|
||||
echo=False,
|
||||
pool_size=5,
|
||||
max_overflow=10,
|
||||
)
|
||||
|
||||
async_session = sessionmaker(
|
||||
engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
deleted_counts = {}
|
||||
|
||||
async with async_session() as session:
|
||||
try:
|
||||
# Start transaction
|
||||
await session.begin()
|
||||
|
||||
print("🔍 Starting Ground Zero database purge...")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. First, identify test users to delete
|
||||
test_email_patterns = [
|
||||
'%test%',
|
||||
'%noreply%',
|
||||
'%profibot%',
|
||||
'tester_pro@profibot.hu',
|
||||
'test@example.com',
|
||||
'%example.com'
|
||||
]
|
||||
|
||||
# Build WHERE clause for test users
|
||||
where_conditions = []
|
||||
for pattern in test_email_patterns:
|
||||
where_conditions.append(f"email ILIKE '{pattern}'")
|
||||
|
||||
where_clause = " OR ".join(where_conditions)
|
||||
|
||||
# 2. Get user IDs to delete for cascade operations
|
||||
query = text(f"""
|
||||
SELECT id, email
|
||||
FROM identity.users
|
||||
WHERE {where_clause}
|
||||
AND is_deleted = false
|
||||
""")
|
||||
|
||||
result = await session.execute(query)
|
||||
test_users = result.fetchall()
|
||||
|
||||
if not test_users:
|
||||
print("✅ No test users found to delete.")
|
||||
await session.commit()
|
||||
return deleted_counts
|
||||
|
||||
user_ids = [str(user[0]) for user in test_users]
|
||||
user_emails = [user[1] for user in test_users]
|
||||
|
||||
print(f"📋 Found {len(test_users)} test users to delete:")
|
||||
for email in user_emails[:10]: # Show first 10
|
||||
print(f" - {email}")
|
||||
if len(test_users) > 10:
|
||||
print(f" ... and {len(test_users) - 10} more")
|
||||
|
||||
user_ids_str = ",".join(user_ids)
|
||||
|
||||
# 3. Delete in reverse cascade order to respect foreign keys
|
||||
|
||||
# First, get person IDs for these users
|
||||
person_query = text(f"""
|
||||
SELECT DISTINCT person_id
|
||||
FROM identity.users
|
||||
WHERE id IN ({user_ids_str})
|
||||
AND person_id IS NOT NULL
|
||||
""")
|
||||
person_result = await session.execute(person_query)
|
||||
person_ids = [str(row[0]) for row in person_result.fetchall()]
|
||||
person_ids_str = ",".join(person_ids) if person_ids else "NULL"
|
||||
|
||||
# Delete from tables that reference users/persons
|
||||
|
||||
# a) Delete from gamification tables
|
||||
gamification_tables = [
|
||||
("gamification.user_stats", "user_id"),
|
||||
("gamification.user_badges", "user_id"),
|
||||
("gamification.points_ledger", "user_id"),
|
||||
("gamification.user_contributions", "user_id"),
|
||||
]
|
||||
|
||||
for table, column in gamification_tables:
|
||||
delete_query = text(f"""
|
||||
DELETE FROM {table}
|
||||
WHERE {column} IN ({user_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts[table] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} rows from {table}")
|
||||
|
||||
# b) Delete from social tables - with error handling for missing tables
|
||||
social_tables = [
|
||||
("identity.social_accounts", "user_id"),
|
||||
("marketplace.service_requests", "user_id"),
|
||||
]
|
||||
|
||||
# Try to delete from service_reviews if it exists
|
||||
social_tables_to_try = social_tables + [("identity.service_reviews", "user_id")]
|
||||
|
||||
for table, column in social_tables_to_try:
|
||||
try:
|
||||
delete_query = text(f"""
|
||||
DELETE FROM {table}
|
||||
WHERE {column} IN ({user_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts[table] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} rows from {table}")
|
||||
except Exception as e:
|
||||
if "does not exist" in str(e) or "UndefinedTableError" in str(e):
|
||||
print(f" ⚠️ Table {table} doesn't exist, skipping...")
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
# c) Delete from organization-related tables
|
||||
# First get organization IDs owned by these users
|
||||
org_query = text(f"""
|
||||
SELECT id FROM marketplace.organizations
|
||||
WHERE owner_id IN ({user_ids_str})
|
||||
""")
|
||||
org_result = await session.execute(org_query)
|
||||
org_ids = [str(row[0]) for row in org_result.fetchall()]
|
||||
org_ids_str = ",".join(org_ids) if org_ids else "NULL"
|
||||
|
||||
if org_ids:
|
||||
# Delete branches for these organizations
|
||||
branch_query = text(f"""
|
||||
DELETE FROM marketplace.branches
|
||||
WHERE organization_id IN ({org_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(branch_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["marketplace.branches"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} branches")
|
||||
|
||||
# Delete organization members
|
||||
org_member_query = text(f"""
|
||||
DELETE FROM marketplace.organization_members
|
||||
WHERE organization_id IN ({org_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(org_member_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["marketplace.organization_members"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} organization members")
|
||||
|
||||
# Delete organizations
|
||||
org_delete_query = text(f"""
|
||||
DELETE FROM marketplace.organizations
|
||||
WHERE id IN ({org_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(org_delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["marketplace.organizations"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} organizations")
|
||||
|
||||
# d) Delete from asset-related tables
|
||||
# Get asset IDs owned by these users
|
||||
asset_query = text(f"""
|
||||
SELECT id FROM data.assets
|
||||
WHERE owner_id IN ({user_ids_str})
|
||||
""")
|
||||
asset_result = await session.execute(asset_query)
|
||||
asset_ids = [str(row[0]) for row in asset_result.fetchall()]
|
||||
asset_ids_str = ",".join(asset_ids) if asset_ids else "NULL"
|
||||
|
||||
if asset_ids:
|
||||
# Delete asset costs
|
||||
asset_cost_query = text(f"""
|
||||
DELETE FROM data.asset_costs
|
||||
WHERE asset_id IN ({asset_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(asset_cost_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["data.asset_costs"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} asset costs")
|
||||
|
||||
# Delete asset events
|
||||
asset_event_query = text(f"""
|
||||
DELETE FROM data.asset_events
|
||||
WHERE asset_id IN ({asset_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(asset_event_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["data.asset_events"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} asset events")
|
||||
|
||||
# Delete assets
|
||||
asset_delete_query = text(f"""
|
||||
DELETE FROM data.assets
|
||||
WHERE id IN ({asset_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(asset_delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["data.assets"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} assets")
|
||||
|
||||
# e) Delete wallets
|
||||
wallet_query = text(f"""
|
||||
DELETE FROM identity.wallets
|
||||
WHERE user_id IN ({user_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(wallet_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["identity.wallets"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} wallets")
|
||||
|
||||
# f) Finally delete users
|
||||
user_delete_query = text(f"""
|
||||
DELETE FROM identity.users
|
||||
WHERE id IN ({user_ids_str})
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(user_delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["identity.users"] = deleted
|
||||
print(f" 🗑️ Deleted {deleted} users")
|
||||
|
||||
# g) Delete persons (if they have no other users)
|
||||
if person_ids:
|
||||
person_delete_query = text(f"""
|
||||
DELETE FROM identity.persons
|
||||
WHERE id IN ({person_ids_str})
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM identity.users
|
||||
WHERE person_id = persons.id
|
||||
)
|
||||
RETURNING *
|
||||
""")
|
||||
result = await session.execute(person_delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["identity.persons"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" 🗑️ Deleted {deleted} persons")
|
||||
|
||||
# Commit transaction
|
||||
await session.commit()
|
||||
|
||||
print("=" * 60)
|
||||
print("✅ Ground Zero purge completed successfully!")
|
||||
print("\n📊 Deletion Summary:")
|
||||
for table, count in deleted_counts.items():
|
||||
print(f" {table}: {count} rows")
|
||||
|
||||
total_deleted = sum(deleted_counts.values())
|
||||
print(f"\n📈 Total rows deleted: {total_deleted}")
|
||||
|
||||
return deleted_counts
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
print(f"❌ Error during purge: {e}")
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(purge_test_data())
|
||||
91
backend/scripts/purge_test_data.sql
Normal file
91
backend/scripts/purge_test_data.sql
Normal file
@@ -0,0 +1,91 @@
|
||||
-- Ground Zero Database Purge Script
|
||||
-- Deletes ALL test users and related data
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. First, identify and count test users
|
||||
SELECT 'Test users to delete:' as info;
|
||||
SELECT id, email
|
||||
FROM identity.users
|
||||
WHERE (
|
||||
email ILIKE '%test%' OR
|
||||
email ILIKE '%noreply%' OR
|
||||
email ILIKE '%profibot%' OR
|
||||
email ILIKE '%example.com' OR
|
||||
email ILIKE '%@gmail.com'
|
||||
) AND is_deleted = false
|
||||
ORDER BY id;
|
||||
|
||||
-- 2. Delete from gamification tables (if they exist)
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Check if table exists before deleting
|
||||
IF EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'gamification' AND table_name = 'user_stats') THEN
|
||||
DELETE FROM gamification.user_stats
|
||||
WHERE user_id IN (
|
||||
SELECT id FROM identity.users
|
||||
WHERE (
|
||||
email ILIKE '%test%' OR
|
||||
email ILIKE '%noreply%' OR
|
||||
email ILIKE '%profibot%' OR
|
||||
email ILIKE '%example.com' OR
|
||||
email ILIKE '%@gmail.com'
|
||||
) AND is_deleted = false
|
||||
);
|
||||
RAISE NOTICE 'Deleted from gamification.user_stats';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'gamification' AND table_name = 'points_ledger') THEN
|
||||
DELETE FROM gamification.points_ledger
|
||||
WHERE user_id IN (
|
||||
SELECT id FROM identity.users
|
||||
WHERE (
|
||||
email ILIKE '%test%' OR
|
||||
email ILIKE '%noreply%' OR
|
||||
email ILIKE '%profibot%' OR
|
||||
email ILIKE '%example.com' OR
|
||||
email ILIKE '%@gmail.com'
|
||||
) AND is_deleted = false
|
||||
);
|
||||
RAISE NOTICE 'Deleted from gamification.points_ledger';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 3. Delete wallets (must be done before users due to FK constraint)
|
||||
DELETE FROM identity.wallets
|
||||
WHERE user_id IN (
|
||||
SELECT id FROM identity.users
|
||||
WHERE (
|
||||
email ILIKE '%test%' OR
|
||||
email ILIKE '%noreply%' OR
|
||||
email ILIKE '%profibot%' OR
|
||||
email ILIKE '%example.com' OR
|
||||
email ILIKE '%@gmail.com'
|
||||
) AND is_deleted = false
|
||||
);
|
||||
|
||||
-- 4. Finally delete the test users
|
||||
DELETE FROM identity.users
|
||||
WHERE (
|
||||
email ILIKE '%test%' OR
|
||||
email ILIKE '%noreply%' OR
|
||||
email ILIKE '%profibot%' OR
|
||||
email ILIKE '%example.com' OR
|
||||
email ILIKE '%@gmail.com'
|
||||
) AND is_deleted = false;
|
||||
|
||||
-- 5. Delete orphaned persons
|
||||
DELETE FROM identity.persons
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM identity.users
|
||||
WHERE users.person_id = persons.id
|
||||
);
|
||||
|
||||
-- 6. Show results
|
||||
SELECT 'Purge completed. Summary:' as info;
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM identity.users WHERE email ILIKE '%test%' AND is_deleted = false) as remaining_test_users,
|
||||
(SELECT COUNT(*) FROM identity.users WHERE email ILIKE '%noreply%' AND is_deleted = false) as remaining_noreply_users,
|
||||
(SELECT COUNT(*) FROM identity.users WHERE email ILIKE '%profibot%' AND is_deleted = false) as remaining_profibot_users;
|
||||
|
||||
COMMIT;
|
||||
325
backend/scripts/purge_test_data_final.py
Normal file
325
backend/scripts/purge_test_data_final.py
Normal file
@@ -0,0 +1,325 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Final Ground Zero Database Purge Script
|
||||
|
||||
Deletes ALL test users in correct cascade order.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
# Import settings
|
||||
sys.path.insert(0, '/app')
|
||||
from app.core.config import settings
|
||||
|
||||
async def purge_test_data_final():
|
||||
"""Final purge function with proper cascade order"""
|
||||
# Create async engine
|
||||
engine = create_async_engine(
|
||||
str(settings.SQLALCHEMY_DATABASE_URI),
|
||||
echo=False,
|
||||
pool_size=5,
|
||||
max_overflow=10,
|
||||
)
|
||||
|
||||
async_session = sessionmaker(
|
||||
engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
async with async_session() as session:
|
||||
try:
|
||||
print("🔍 Starting Ground Zero database purge (final)...")
|
||||
print("=" * 60)
|
||||
|
||||
# Start transaction
|
||||
await session.begin()
|
||||
|
||||
# 1. First, identify test users to delete
|
||||
test_email_patterns = [
|
||||
'%test%',
|
||||
'%noreply%',
|
||||
'%profibot%',
|
||||
'tester_pro@profibot.hu',
|
||||
'test@example.com',
|
||||
'%example.com',
|
||||
'%@gmail.com'
|
||||
]
|
||||
|
||||
# Build WHERE clause for test users
|
||||
where_conditions = []
|
||||
for pattern in test_email_patterns:
|
||||
where_conditions.append(f"email ILIKE '{pattern}'")
|
||||
|
||||
where_clause = " OR ".join(where_conditions)
|
||||
|
||||
# 2. Get user IDs to delete
|
||||
get_users_query = text(f"""
|
||||
SELECT id, email
|
||||
FROM identity.users
|
||||
WHERE {where_clause}
|
||||
AND is_deleted = false
|
||||
ORDER BY id
|
||||
""")
|
||||
|
||||
result = await session.execute(get_users_query)
|
||||
test_users = result.fetchall()
|
||||
|
||||
if not test_users:
|
||||
print("✅ No test users found to delete.")
|
||||
await session.commit()
|
||||
return
|
||||
|
||||
user_ids = [str(user[0]) for user in test_users]
|
||||
user_emails = [user[1] for user in test_users]
|
||||
user_ids_str = ",".join(user_ids)
|
||||
|
||||
print(f"📋 Found {len(test_users)} test users to delete:")
|
||||
for email in user_emails[:10]:
|
||||
print(f" - {email}")
|
||||
if len(test_users) > 10:
|
||||
print(f" ... and {len(test_users) - 10} more")
|
||||
|
||||
# 3. Delete in correct cascade order
|
||||
|
||||
# a) First delete from tables that reference users but have no further dependencies
|
||||
print("\n🗑️ Deleting related records in cascade order...")
|
||||
|
||||
# Tables to delete (in order of dependencies)
|
||||
tables_to_clean = [
|
||||
# Gamification tables
|
||||
("gamification.user_stats", "user_id"),
|
||||
("gamification.user_badges", "user_id"),
|
||||
("gamification.points_ledger", "user_id"),
|
||||
("gamification.user_contributions", "user_id"),
|
||||
|
||||
# Social tables (skip if they don't exist)
|
||||
("identity.social_accounts", "user_id"),
|
||||
("marketplace.service_requests", "user_id"),
|
||||
|
||||
# Organization members first (before organizations)
|
||||
("marketplace.organization_members", "user_id"),
|
||||
|
||||
# Payment intents
|
||||
("marketplace.payment_intents", "payer_id"),
|
||||
("marketplace.payment_intents", "beneficiary_id"),
|
||||
|
||||
# Withdrawal requests
|
||||
("marketplace.withdrawal_requests", "user_id"),
|
||||
|
||||
# Service reviews (if table exists)
|
||||
("identity.service_reviews", "user_id"),
|
||||
|
||||
# Vehicle ratings
|
||||
("vehicle.vehicle_user_ratings", "user_id"),
|
||||
|
||||
# Vehicle ownership
|
||||
("vehicle.vehicle_ownership", "user_id"),
|
||||
]
|
||||
|
||||
deleted_counts = {}
|
||||
|
||||
for table, column in tables_to_clean:
|
||||
try:
|
||||
# Check if table exists by trying to count
|
||||
check_query = text(f"""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = '{table.split('.')[0]}'
|
||||
AND table_name = '{table.split('.')[1]}'
|
||||
)
|
||||
""")
|
||||
exists_result = await session.execute(check_query)
|
||||
exists = exists_result.scalar()
|
||||
|
||||
if not exists:
|
||||
print(f" ⚠️ Table {table} doesn't exist, skipping...")
|
||||
continue
|
||||
|
||||
delete_query = text(f"""
|
||||
DELETE FROM {table}
|
||||
WHERE {column} IN ({user_ids_str})
|
||||
""")
|
||||
result = await session.execute(delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts[f"{table}.{column}"] = deleted
|
||||
if deleted > 0:
|
||||
print(f" ✅ Deleted {deleted} rows from {table}")
|
||||
|
||||
except Exception as e:
|
||||
if "does not exist" in str(e) or "UndefinedTableError" in str(e):
|
||||
print(f" ⚠️ Table {table} doesn't exist, skipping...")
|
||||
continue
|
||||
else:
|
||||
print(f" ⚠️ Error deleting from {table}: {e}")
|
||||
continue
|
||||
|
||||
# b) Delete wallets (references users)
|
||||
try:
|
||||
wallet_query = text(f"""
|
||||
DELETE FROM identity.wallets
|
||||
WHERE user_id IN ({user_ids_str})
|
||||
""")
|
||||
result = await session.execute(wallet_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["identity.wallets"] = deleted
|
||||
print(f" ✅ Deleted {deleted} wallets")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error deleting wallets: {e}")
|
||||
|
||||
# c) Delete organizations owned by these users
|
||||
try:
|
||||
# Get organization IDs
|
||||
org_query = text(f"""
|
||||
SELECT id FROM marketplace.organizations
|
||||
WHERE owner_id IN ({user_ids_str})
|
||||
""")
|
||||
org_result = await session.execute(org_query)
|
||||
org_ids = [str(row[0]) for row in org_result.fetchall()]
|
||||
|
||||
if org_ids:
|
||||
org_ids_str = ",".join(org_ids)
|
||||
|
||||
# Delete branches for these organizations
|
||||
branch_query = text(f"""
|
||||
DELETE FROM marketplace.branches
|
||||
WHERE organization_id IN ({org_ids_str})
|
||||
""")
|
||||
result = await session.execute(branch_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["marketplace.branches"] = deleted
|
||||
print(f" ✅ Deleted {deleted} branches")
|
||||
|
||||
# Delete organization members for these organizations
|
||||
org_member_query = text(f"""
|
||||
DELETE FROM marketplace.organization_members
|
||||
WHERE organization_id IN ({org_ids_str})
|
||||
""")
|
||||
result = await session.execute(org_member_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["marketplace.organization_members_org"] = deleted
|
||||
print(f" ✅ Deleted {deleted} organization members (by org)")
|
||||
|
||||
# Delete organizations
|
||||
org_delete_query = text(f"""
|
||||
DELETE FROM marketplace.organizations
|
||||
WHERE id IN ({org_ids_str})
|
||||
""")
|
||||
result = await session.execute(org_delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["marketplace.organizations"] = deleted
|
||||
print(f" ✅ Deleted {deleted} organizations")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error deleting organizations: {e}")
|
||||
|
||||
# d) Delete assets owned by these users
|
||||
try:
|
||||
# Get asset IDs
|
||||
asset_query = text(f"""
|
||||
SELECT id FROM data.assets
|
||||
WHERE owner_id IN ({user_ids_str})
|
||||
""")
|
||||
asset_result = await session.execute(asset_query)
|
||||
asset_ids = [str(row[0]) for row in asset_result.fetchall()]
|
||||
|
||||
if asset_ids:
|
||||
asset_ids_str = ",".join(asset_ids)
|
||||
|
||||
# Delete asset costs
|
||||
asset_cost_query = text(f"""
|
||||
DELETE FROM data.asset_costs
|
||||
WHERE asset_id IN ({asset_ids_str})
|
||||
""")
|
||||
result = await session.execute(asset_cost_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["data.asset_costs"] = deleted
|
||||
print(f" ✅ Deleted {deleted} asset costs")
|
||||
|
||||
# Delete asset events
|
||||
asset_event_query = text(f"""
|
||||
DELETE FROM data.asset_events
|
||||
WHERE asset_id IN ({asset_ids_str})
|
||||
""")
|
||||
result = await session.execute(asset_event_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["data.asset_events"] = deleted
|
||||
print(f" ✅ Deleted {deleted} asset events")
|
||||
|
||||
# Delete assets
|
||||
asset_delete_query = text(f"""
|
||||
DELETE FROM data.assets
|
||||
WHERE id IN ({asset_ids_str})
|
||||
""")
|
||||
result = await session.execute(asset_delete_query)
|
||||
deleted = result.rowcount
|
||||
deleted_counts["data.assets"] = deleted
|
||||
print(f" ✅ Deleted {deleted} assets")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error deleting assets: {e}")
|
||||
|
||||
# e) Finally delete users
|
||||
print("\n🗑️ Deleting test users...")
|
||||
user_delete_query = text(f"""
|
||||
DELETE FROM identity.users
|
||||
WHERE id IN ({user_ids_str})
|
||||
AND is_deleted = false
|
||||
""")
|
||||
result = await session.execute(user_delete_query)
|
||||
deleted_users = result.rowcount
|
||||
deleted_counts["identity.users"] = deleted_users
|
||||
print(f" ✅ Deleted {deleted_users} users")
|
||||
|
||||
# f) Delete orphaned persons
|
||||
person_delete_query = text("""
|
||||
DELETE FROM identity.persons
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM identity.users
|
||||
WHERE users.person_id = persons.id
|
||||
)
|
||||
""")
|
||||
result = await session.execute(person_delete_query)
|
||||
deleted_persons = result.rowcount
|
||||
deleted_counts["identity.persons"] = deleted_persons
|
||||
if deleted_persons > 0:
|
||||
print(f" ✅ Deleted {deleted_persons} orphaned persons")
|
||||
|
||||
# Commit transaction
|
||||
await session.commit()
|
||||
|
||||
print("=" * 60)
|
||||
print("✅ Ground Zero purge completed successfully!")
|
||||
print("\n📊 Deletion Summary:")
|
||||
total_deleted = 0
|
||||
for key, count in deleted_counts.items():
|
||||
print(f" {key}: {count} rows")
|
||||
total_deleted += count
|
||||
|
||||
print(f"\n📈 Total rows deleted: {total_deleted}")
|
||||
|
||||
# Verify cleanup
|
||||
print("\n🔍 Verifying cleanup...")
|
||||
verify_query = text(f"""
|
||||
SELECT COUNT(*)
|
||||
FROM identity.users
|
||||
WHERE {where_clause}
|
||||
AND is_deleted = false
|
||||
""")
|
||||
result = await session.execute(verify_query)
|
||||
remaining = result.scalar()
|
||||
|
||||
if remaining == 0:
|
||||
print(f"✅ Verification: All test users have been removed.")
|
||||
else:
|
||||
print(f"⚠️ Verification: {remaining} test users still remain.")
|
||||
|
||||
return deleted_counts
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
print(f"❌ Error during purge: {e}")
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(purge_test_data_final())
|
||||
146
backend/scripts/purge_test_data_simple.py
Normal file
146
backend/scripts/purge_test_data_simple.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simplified Ground Zero Database Purge Script
|
||||
|
||||
Deletes ALL test users and lets database cascade constraints handle the rest.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
# Import settings
|
||||
sys.path.insert(0, '/app')
|
||||
from app.core.config import settings
|
||||
|
||||
async def purge_test_data_simple():
|
||||
"""Simple purge function using cascade delete"""
|
||||
# Create async engine
|
||||
engine = create_async_engine(
|
||||
str(settings.SQLALCHEMY_DATABASE_URI),
|
||||
echo=False,
|
||||
pool_size=5,
|
||||
max_overflow=10,
|
||||
)
|
||||
|
||||
async_session = sessionmaker(
|
||||
engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
async with async_session() as session:
|
||||
try:
|
||||
print("🔍 Starting Ground Zero database purge (simplified)...")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. First, identify test users to delete
|
||||
test_email_patterns = [
|
||||
'%test%',
|
||||
'%noreply%',
|
||||
'%profibot%',
|
||||
'tester_pro@profibot.hu',
|
||||
'test@example.com',
|
||||
'%example.com',
|
||||
'%@gmail.com' # Also delete gmail test users
|
||||
]
|
||||
|
||||
# Build WHERE clause for test users
|
||||
where_conditions = []
|
||||
for pattern in test_email_patterns:
|
||||
where_conditions.append(f"email ILIKE '{pattern}'")
|
||||
|
||||
where_clause = " OR ".join(where_conditions)
|
||||
|
||||
# 2. Get count of test users
|
||||
count_query = text(f"""
|
||||
SELECT COUNT(*)
|
||||
FROM identity.users
|
||||
WHERE {where_clause}
|
||||
AND is_deleted = false
|
||||
""")
|
||||
|
||||
result = await session.execute(count_query)
|
||||
test_user_count = result.scalar()
|
||||
|
||||
if test_user_count == 0:
|
||||
print("✅ No test users found to delete.")
|
||||
return
|
||||
|
||||
# 3. Get list of test users
|
||||
list_query = text(f"""
|
||||
SELECT id, email
|
||||
FROM identity.users
|
||||
WHERE {where_clause}
|
||||
AND is_deleted = false
|
||||
LIMIT 20
|
||||
""")
|
||||
|
||||
result = await session.execute(list_query)
|
||||
test_users = result.fetchall()
|
||||
|
||||
print(f"📋 Found {test_user_count} test users to delete:")
|
||||
for user in test_users:
|
||||
print(f" - {user[1]} (ID: {user[0]})")
|
||||
if test_user_count > 20:
|
||||
print(f" ... and {test_user_count - 20} more")
|
||||
|
||||
# 4. Delete users (cascade will handle related records)
|
||||
delete_query = text(f"""
|
||||
DELETE FROM identity.users
|
||||
WHERE {where_clause}
|
||||
AND is_deleted = false
|
||||
RETURNING id, email
|
||||
""")
|
||||
|
||||
print("\n🗑️ Deleting test users (cascade delete will handle related records)...")
|
||||
result = await session.execute(delete_query)
|
||||
deleted_users = result.fetchall()
|
||||
|
||||
# 5. Also delete any orphaned persons
|
||||
cleanup_persons_query = text("""
|
||||
DELETE FROM identity.persons
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM identity.users
|
||||
WHERE users.person_id = persons.id
|
||||
)
|
||||
RETURNING id
|
||||
""")
|
||||
|
||||
result = await session.execute(cleanup_persons_query)
|
||||
orphaned_persons = result.rowcount
|
||||
|
||||
# Commit transaction
|
||||
await session.commit()
|
||||
|
||||
print("=" * 60)
|
||||
print("✅ Ground Zero purge completed successfully!")
|
||||
print(f"\n📊 Deletion Summary:")
|
||||
print(f" Test users deleted: {len(deleted_users)}")
|
||||
print(f" Orphaned persons cleaned up: {orphaned_persons}")
|
||||
|
||||
# 6. Verify cleanup
|
||||
verify_query = text(f"""
|
||||
SELECT COUNT(*)
|
||||
FROM identity.users
|
||||
WHERE {where_clause}
|
||||
AND is_deleted = false
|
||||
""")
|
||||
|
||||
result = await session.execute(verify_query)
|
||||
remaining = result.scalar()
|
||||
|
||||
if remaining == 0:
|
||||
print(f"\n✅ Verification: All test users have been removed.")
|
||||
else:
|
||||
print(f"\n⚠️ Verification: {remaining} test users still remain.")
|
||||
|
||||
return len(deleted_users)
|
||||
|
||||
except Exception as e:
|
||||
await session.rollback()
|
||||
print(f"❌ Error during purge: {e}")
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(purge_test_data_simple())
|
||||
38
backend/scripts/test_makes_filter.py
Normal file
38
backend/scripts/test_makes_filter.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
async def test_makes_filter():
|
||||
base_url = "http://localhost:8000"
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Test without vehicle_class
|
||||
async with session.get(f"{base_url}/api/v1/catalog/makes") as resp:
|
||||
print(f"GET /api/v1/catalog/makes status: {resp.status}")
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
print(f" Total makes: {len(data)}")
|
||||
print(f" First few: {data[:5]}")
|
||||
|
||||
# Test with vehicle_class=motorcycle
|
||||
async with session.get(f"{base_url}/api/v1/catalog/makes?vehicle_class=motorcycle") as resp:
|
||||
print(f"\nGET /api/v1/catalog/makes?vehicle_class=motorcycle status: {resp.status}")
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
print(f" Makes for motorcycle: {len(data)}")
|
||||
print(f" First few: {data[:5]}")
|
||||
|
||||
# Test with vehicle_class=passenger_car
|
||||
async with session.get(f"{base_url}/api/v1/catalog/makes?vehicle_class=passenger_car") as resp:
|
||||
print(f"\nGET /api/v1/catalog/makes?vehicle_class=passenger_car status: {resp.status}")
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
print(f" Makes for passenger_car: {len(data)}")
|
||||
print(f" First few: {data[:5]}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(test_makes_filter())
|
||||
160
backend/scripts/test_vehicle_registration.py
Normal file
160
backend/scripts/test_vehicle_registration.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Authenticated E2E test for vehicle registration endpoint.
|
||||
Reads credentials from backend/tests/test_credentials.json,
|
||||
authenticates via /api/v1/auth/login, obtains JWT token,
|
||||
and tests POST /api/v1/assets/vehicles endpoint.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
# Add parent directory to path to import app modules if needed
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CREDENTIALS_PATH = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"tests",
|
||||
"test_credentials.json"
|
||||
)
|
||||
|
||||
async def load_credentials() -> Dict[str, Any]:
|
||||
"""Load test credentials from JSON file."""
|
||||
try:
|
||||
with open(CREDENTIALS_PATH, 'r') as f:
|
||||
creds = json.load(f)
|
||||
logger.info(f"Loaded credentials for {creds.get('email')}")
|
||||
return creds
|
||||
except FileNotFoundError:
|
||||
logger.error(f"Credentials file not found at {CREDENTIALS_PATH}")
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Invalid JSON in credentials file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
async def authenticate(session: aiohttp.ClientSession, base_url: str, email: str, password: str) -> str:
|
||||
"""Authenticate and return JWT token."""
|
||||
login_url = f"{base_url}/api/v1/auth/login"
|
||||
# Use form data as required by OAuth2PasswordRequestForm
|
||||
form_data = aiohttp.FormData()
|
||||
form_data.add_field('username', email)
|
||||
form_data.add_field('password', password)
|
||||
|
||||
try:
|
||||
async with session.post(login_url, data=form_data) as resp:
|
||||
if resp.status != 200:
|
||||
text = await resp.text()
|
||||
logger.error(f"Authentication failed: {resp.status} - {text}")
|
||||
raise ValueError(f"Authentication failed: {resp.status}")
|
||||
data = await resp.json()
|
||||
token = data.get("access_token")
|
||||
if not token:
|
||||
logger.error(f"No access_token in response: {data}")
|
||||
raise ValueError("No access_token in response")
|
||||
logger.info("Authentication successful")
|
||||
return token
|
||||
except aiohttp.ClientError as e:
|
||||
logger.error(f"Network error during authentication: {e}")
|
||||
raise
|
||||
|
||||
async def test_vehicle_creation(session: aiohttp.ClientSession, base_url: str, token: str):
|
||||
"""Test POST /api/v1/assets/vehicles endpoint."""
|
||||
url = f"{base_url}/api/v1/assets/vehicles"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# Realistic payload with valid catalog_id and required fields
|
||||
payload = {
|
||||
"vin": "TESTVIN1234567890",
|
||||
"license_plate": "TEST-001",
|
||||
"catalog_id": 5, # Valid catalog_id from database
|
||||
"brand": "BMW",
|
||||
"model": "X5",
|
||||
"vehicle_class": "car",
|
||||
"fuel_type": "petrol",
|
||||
"engine_capacity": 3000,
|
||||
"power_kw": 200,
|
||||
"transmission_type": "automatic",
|
||||
"drive_type": "awd"
|
||||
}
|
||||
|
||||
logger.info(f"Testing vehicle creation with payload: {payload}")
|
||||
|
||||
try:
|
||||
async with session.post(url, json=payload, headers=headers) as resp:
|
||||
status = resp.status
|
||||
text = await resp.text()
|
||||
logger.info(f"Response status: {status}")
|
||||
logger.info(f"Response body: {text}")
|
||||
|
||||
if status == 201:
|
||||
logger.info("✅ Vehicle creation test PASSED")
|
||||
return True
|
||||
elif status == 404:
|
||||
logger.error("❌ Endpoint not found (404). Check API route.")
|
||||
return False
|
||||
elif status == 400:
|
||||
logger.warning("⚠️ Bad request (400). Might be due to missing catalog_id or validation.")
|
||||
# Try to parse error details
|
||||
try:
|
||||
error_data = json.loads(text)
|
||||
logger.warning(f"Error details: {error_data}")
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
elif status == 500:
|
||||
logger.error("❌ Internal server error (500). Check server logs.")
|
||||
# Try to parse error details
|
||||
try:
|
||||
error_data = json.loads(text)
|
||||
logger.error(f"Error details: {error_data}")
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
else:
|
||||
logger.error(f"❌ Unexpected status: {status}")
|
||||
return False
|
||||
except aiohttp.ClientError as e:
|
||||
logger.error(f"Network error during vehicle creation: {e}")
|
||||
return False
|
||||
|
||||
async def main():
|
||||
"""Main test execution."""
|
||||
logger.info("Starting authenticated E2E test for vehicle registration")
|
||||
|
||||
# Load credentials
|
||||
creds = await load_credentials()
|
||||
base_url = creds.get("base_url", "http://localhost:8000")
|
||||
email = creds["email"]
|
||||
password = creds["password"]
|
||||
|
||||
# Create aiohttp session
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Authenticate
|
||||
try:
|
||||
token = await authenticate(session, base_url, email, password)
|
||||
except Exception as e:
|
||||
logger.error(f"Authentication failed: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Test vehicle creation
|
||||
success = await test_vehicle_creation(session, base_url, token)
|
||||
|
||||
if success:
|
||||
logger.info("🎉 All tests passed!")
|
||||
sys.exit(0)
|
||||
else:
|
||||
logger.error("💥 Test failed!")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user