chore: Backend codebase cleanup and archiving of legacy scripts
This commit is contained in:
388
backend/archive_v1_scripts/create_sandbox_user.py
Normal file
388
backend/archive_v1_scripts/create_sandbox_user.py
Normal file
@@ -0,0 +1,388 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Sandbox Seeder Script - Creates a persistent sandbox user in the live/dev database
|
||||
for manual testing via Swagger.
|
||||
|
||||
Steps:
|
||||
1. Register via POST /api/v1/auth/register
|
||||
2. Extract verification token from Mailpit API
|
||||
3. Verify email via POST /api/v1/auth/verify-email
|
||||
4. Login via POST /api/v1/auth/login to get JWT
|
||||
5. Complete KYC via POST /api/v1/auth/complete-kyc
|
||||
6. Create organization via POST /api/v1/organizations/onboard
|
||||
7. Add a test vehicle/asset via appropriate endpoint
|
||||
8. Add a fuel expense (15,000 HUF) via POST /api/v1/expenses/add
|
||||
|
||||
Prints credentials and IDs for immediate use.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import httpx
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from datetime import date, datetime, timedelta
|
||||
import uuid
|
||||
|
||||
# Configuration
|
||||
API_BASE = "http://localhost:8000" # FastAPI server (runs inside sf_api container)
|
||||
MAILPIT_API = "http://sf_mailpit:8025/api/v1/messages"
|
||||
MAILPIT_DELETE_ALL = "http://sf_mailpit:8025/api/v1/messages"
|
||||
|
||||
# Generate unique email each run to avoid duplicate key errors
|
||||
unique_id = int(time.time())
|
||||
SANDBOX_EMAIL = f"sandbox_{unique_id}@test.com"
|
||||
SANDBOX_PASSWORD = "Sandbox123!"
|
||||
SANDBOX_FIRST_NAME = "Sandbox"
|
||||
SANDBOX_LAST_NAME = "User"
|
||||
|
||||
# Dummy KYC data
|
||||
DUMMY_KYC = {
|
||||
"phone_number": "+36123456789",
|
||||
"birth_place": "Budapest",
|
||||
"birth_date": "1990-01-01",
|
||||
"mothers_last_name": "Kovács",
|
||||
"mothers_first_name": "Éva",
|
||||
"address_zip": "1051",
|
||||
"address_city": "Budapest",
|
||||
"address_street_name": "Váci",
|
||||
"address_street_type": "utca",
|
||||
"address_house_number": "1",
|
||||
"address_stairwell": None,
|
||||
"address_floor": None,
|
||||
"address_door": None,
|
||||
"address_hrsz": None,
|
||||
"identity_docs": {
|
||||
"ID_CARD": {
|
||||
"number": "123456AB",
|
||||
"expiry_date": "2030-12-31"
|
||||
}
|
||||
},
|
||||
"ice_contact": {
|
||||
"name": "John Doe",
|
||||
"phone": "+36198765432",
|
||||
"relationship": "friend"
|
||||
},
|
||||
"preferred_language": "hu",
|
||||
"preferred_currency": "HUF"
|
||||
}
|
||||
|
||||
# Dummy organization data
|
||||
DUMMY_ORG = {
|
||||
"full_name": "Sandbox Test Kft.",
|
||||
"name": "Sandbox Kft.",
|
||||
"display_name": "Sandbox Test",
|
||||
"tax_number": f"{unique_id}"[:8] + "-1-42",
|
||||
"reg_number": f"01-09-{unique_id}"[:6],
|
||||
"country_code": "HU",
|
||||
"language": "hu",
|
||||
"default_currency": "HUF",
|
||||
"address_zip": "1051",
|
||||
"address_city": "Budapest",
|
||||
"address_street_name": "Váci",
|
||||
"address_street_type": "utca",
|
||||
"address_house_number": "2",
|
||||
"address_stairwell": None,
|
||||
"address_floor": None,
|
||||
"address_door": None,
|
||||
"address_hrsz": None,
|
||||
"contacts": [
|
||||
{
|
||||
"full_name": "Sandbox User",
|
||||
"email": SANDBOX_EMAIL,
|
||||
"phone": "+36123456789",
|
||||
"contact_type": "primary"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Dummy vehicle data
|
||||
DUMMY_VEHICLE = {
|
||||
"catalog_id": 1, # Assuming there's at least one catalog entry
|
||||
"license_plate": f"SBX-{uuid.uuid4().hex[:4]}".upper(),
|
||||
"vin": f"VIN{uuid.uuid4().hex[:10]}".upper(),
|
||||
"nickname": "Sandbox Car",
|
||||
"purchase_date": "2025-01-01",
|
||||
"initial_mileage": 5000,
|
||||
"fuel_type": "petrol",
|
||||
"transmission": "manual"
|
||||
}
|
||||
|
||||
# Dummy expense data
|
||||
DUMMY_EXPENSE = {
|
||||
"asset_id": None, # Will be filled after vehicle creation
|
||||
"category": "fuel",
|
||||
"amount": 15000.0,
|
||||
"date": date.today().isoformat()
|
||||
}
|
||||
|
||||
async def clean_mailpit():
|
||||
"""Delete all messages in Mailpit before registration to ensure clean state."""
|
||||
print(" [DEBUG] Entering clean_mailpit()")
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
print(f" [DEBUG] Sending DELETE to {MAILPIT_DELETE_ALL}")
|
||||
resp = await client.delete(MAILPIT_DELETE_ALL)
|
||||
print(f" [DEBUG] DELETE response status: {resp.status_code}")
|
||||
if resp.status_code == 200:
|
||||
print("🗑️ Mailpit cleaned (all messages deleted).")
|
||||
else:
|
||||
print(f"⚠️ Mailpit clean returned {resp.status_code}, continuing anyway.")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Mailpit clean failed: {e}, continuing anyway.")
|
||||
|
||||
async def fetch_mailpit_token():
|
||||
"""Fetch the latest verification token from Mailpit with polling."""
|
||||
import re
|
||||
import sys
|
||||
max_attempts = 5
|
||||
wait_seconds = 3
|
||||
|
||||
print(f"[DEBUG] Starting fetch_mailpit_token() with max_attempts={max_attempts}", flush=True)
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
print(f"[DEBUG] Fetching Mailpit messages (attempt {attempt}/{max_attempts})...", flush=True)
|
||||
resp = await client.get(MAILPIT_API)
|
||||
resp.raise_for_status()
|
||||
messages = resp.json()
|
||||
|
||||
# Debug: print raw response summary
|
||||
total = messages.get("total", 0)
|
||||
count = messages.get("count", 0)
|
||||
print(f"[DEBUG] Mailpit response: total={total}, count={count}", flush=True)
|
||||
|
||||
if not messages.get("messages"):
|
||||
print(f"⚠️ No emails in Mailpit (attempt {attempt}/{max_attempts}). Waiting {wait_seconds}s...", flush=True)
|
||||
await asyncio.sleep(wait_seconds)
|
||||
continue
|
||||
|
||||
# Print each message's subject and recipients for debugging
|
||||
for idx, msg in enumerate(messages.get("messages", [])):
|
||||
subject = msg.get("Subject", "No Subject")
|
||||
to_list = msg.get("To", [])
|
||||
from_list = msg.get("From", [])
|
||||
print(f"[DEBUG] Message {idx}: Subject='{subject}', To={to_list}, From={from_list}", flush=True)
|
||||
|
||||
print(f"[DEBUG] Looking for email to {SANDBOX_EMAIL}...", flush=True)
|
||||
|
||||
# Find the latest email to our sandbox email
|
||||
for msg in messages.get("messages", []):
|
||||
# Check if email is in To field (which is a list of dicts)
|
||||
to_list = msg.get("To", [])
|
||||
email_found = False
|
||||
for recipient in to_list:
|
||||
if isinstance(recipient, dict) and recipient.get("Address") == SANDBOX_EMAIL:
|
||||
email_found = True
|
||||
break
|
||||
elif isinstance(recipient, str) and recipient == SANDBOX_EMAIL:
|
||||
email_found = True
|
||||
break
|
||||
|
||||
if email_found:
|
||||
msg_id = msg.get("ID")
|
||||
print(f"[DEBUG] Found email to {SANDBOX_EMAIL}, message ID: {msg_id}")
|
||||
|
||||
# Fetch full message details (Text and HTML are empty in list response)
|
||||
if msg_id:
|
||||
try:
|
||||
# Correct endpoint: /api/v1/message/{id} (singular)
|
||||
detail_resp = await client.get(f"http://sf_mailpit:8025/api/v1/message/{msg_id}")
|
||||
detail_resp.raise_for_status()
|
||||
detail = detail_resp.json()
|
||||
body = detail.get("Text", "")
|
||||
html_body = detail.get("HTML", "")
|
||||
print(f"[DEBUG] Fetched full message details, body length: {len(body)}, HTML length: {len(html_body)}")
|
||||
except Exception as e:
|
||||
print(f"[DEBUG] Failed to fetch message details: {e}")
|
||||
body = msg.get("Text", "")
|
||||
html_body = msg.get("HTML", "")
|
||||
else:
|
||||
body = msg.get("Text", "")
|
||||
html_body = msg.get("HTML", "")
|
||||
|
||||
if body:
|
||||
print(f"[DEBUG] Body preview (first 500 chars): {body[:500]}...")
|
||||
|
||||
# Try to find token using patterns from test suite
|
||||
patterns = [
|
||||
r"token=([a-zA-Z0-9\-_]+)",
|
||||
r"/verify/([a-zA-Z0-9\-_]+)",
|
||||
r"verification code: ([a-zA-Z0-9\-_]+)",
|
||||
r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", # UUID pattern
|
||||
r"[0-9a-f]{32}", # UUID without hyphens
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
if body:
|
||||
token_match = re.search(pattern, body, re.I)
|
||||
if token_match:
|
||||
token = token_match.group(1) if token_match.groups() else token_match.group(0)
|
||||
print(f"✅ Token found with pattern '{pattern}' on attempt {attempt}: {token}")
|
||||
return token
|
||||
|
||||
# If not found in text, try HTML body
|
||||
if html_body:
|
||||
for pattern in patterns:
|
||||
html_token_match = re.search(pattern, html_body, re.I)
|
||||
if html_token_match:
|
||||
token = html_token_match.group(1) if html_token_match.groups() else html_token_match.group(0)
|
||||
print(f"✅ Token found in HTML with pattern '{pattern}' on attempt {attempt}: {token}")
|
||||
return token
|
||||
|
||||
print(f"[DEBUG] No token pattern found. Body length: {len(body)}, HTML length: {len(html_body)}")
|
||||
if body:
|
||||
print(f"[DEBUG] Full body (first 1000 chars): {body[:1000]}")
|
||||
if html_body:
|
||||
print(f"[DEBUG] HTML body snippet (first 500 chars): {html_body[:500]}")
|
||||
|
||||
print(f"⚠️ Email found but no token (attempt {attempt}/{max_attempts}). Waiting {wait_seconds}s...")
|
||||
await asyncio.sleep(wait_seconds)
|
||||
except Exception as e:
|
||||
print(f"❌ Mailpit API error on attempt {attempt}: {e}")
|
||||
await asyncio.sleep(wait_seconds)
|
||||
|
||||
print("❌ Could not retrieve token after all attempts.")
|
||||
return None
|
||||
|
||||
async def main():
|
||||
print("🚀 Starting Sandbox User Creation...")
|
||||
async with httpx.AsyncClient(base_url=API_BASE, timeout=30.0) as client:
|
||||
# Step 0: Clean Mailpit to ensure only new emails
|
||||
print("0. Cleaning Mailpit...")
|
||||
await clean_mailpit()
|
||||
|
||||
# Step 1: Register
|
||||
print("1. Registering user...")
|
||||
register_data = {
|
||||
"email": SANDBOX_EMAIL,
|
||||
"password": SANDBOX_PASSWORD,
|
||||
"first_name": SANDBOX_FIRST_NAME,
|
||||
"last_name": SANDBOX_LAST_NAME,
|
||||
"region_code": "HU",
|
||||
"lang": "hu",
|
||||
"timezone": "Europe/Budapest"
|
||||
}
|
||||
resp = await client.post("/api/v1/auth/register", json=register_data)
|
||||
if resp.status_code not in (200, 201):
|
||||
print(f"❌ Registration failed: {resp.status_code} {resp.text}")
|
||||
return
|
||||
print("✅ Registration successful.")
|
||||
|
||||
# Step 2: Get token from Mailpit
|
||||
print("2. Fetching verification token from Mailpit...")
|
||||
token = await fetch_mailpit_token()
|
||||
if not token:
|
||||
print("❌ Could not retrieve token. Exiting.")
|
||||
return
|
||||
print(f"✅ Token found: {token}")
|
||||
|
||||
# Step 3: Verify email
|
||||
print("3. Verifying email...")
|
||||
resp = await client.post("/api/v1/auth/verify-email", json={"token": token})
|
||||
if resp.status_code != 200:
|
||||
print(f"❌ Email verification failed: {resp.status_code} {resp.text}")
|
||||
return
|
||||
print("✅ Email verified.")
|
||||
|
||||
# Step 4: Login
|
||||
print("4. Logging in...")
|
||||
resp = await client.post("/api/v1/auth/login", data={
|
||||
"username": SANDBOX_EMAIL,
|
||||
"password": SANDBOX_PASSWORD
|
||||
})
|
||||
if resp.status_code != 200:
|
||||
print(f"❌ Login failed: {resp.status_code} {resp.text}")
|
||||
return
|
||||
login_data = resp.json()
|
||||
access_token = login_data.get("access_token")
|
||||
if not access_token:
|
||||
print("❌ No access token in login response.")
|
||||
return
|
||||
print("✅ Login successful.")
|
||||
|
||||
# Update client headers with JWT
|
||||
client.headers.update({"Authorization": f"Bearer {access_token}"})
|
||||
|
||||
# Step 5: Complete KYC
|
||||
print("5. Completing KYC...")
|
||||
resp = await client.post("/api/v1/auth/complete-kyc", json=DUMMY_KYC)
|
||||
if resp.status_code != 200:
|
||||
print(f"❌ KYC completion failed: {resp.status_code} {resp.text}")
|
||||
# Continue anyway (maybe KYC optional)
|
||||
else:
|
||||
print("✅ KYC completed.")
|
||||
|
||||
# Step 6: Create organization
|
||||
print("6. Creating organization...")
|
||||
resp = await client.post("/api/v1/organizations/onboard", json=DUMMY_ORG)
|
||||
if resp.status_code not in (200, 201):
|
||||
print(f"❌ Organization creation failed: {resp.status_code} {resp.text}")
|
||||
# Continue anyway (maybe optional)
|
||||
org_id = None
|
||||
else:
|
||||
org_data = resp.json()
|
||||
org_id = org_data.get("organization_id")
|
||||
print(f"✅ Organization created with ID: {org_id}")
|
||||
|
||||
# Step 7: Add vehicle/asset
|
||||
print("7. Adding vehicle/asset...")
|
||||
asset_id = None
|
||||
# Try POST /api/v1/assets
|
||||
resp = await client.post("/api/v1/assets", json=DUMMY_VEHICLE)
|
||||
if resp.status_code in (200, 201):
|
||||
asset_data = resp.json()
|
||||
asset_id = asset_data.get("asset_id") or asset_data.get("id")
|
||||
print(f"✅ Asset created via /api/v1/assets, ID: {asset_id}")
|
||||
else:
|
||||
# Try POST /api/v1/vehicles
|
||||
resp = await client.post("/api/v1/vehicles", json=DUMMY_VEHICLE)
|
||||
if resp.status_code in (200, 201):
|
||||
asset_data = resp.json()
|
||||
asset_id = asset_data.get("vehicle_id") or asset_data.get("id")
|
||||
print(f"✅ Vehicle created via /api/v1/vehicles, ID: {asset_id}")
|
||||
else:
|
||||
# Try POST /api/v1/catalog/claim
|
||||
resp = await client.post("/api/v1/catalog/claim", json={
|
||||
"catalog_id": DUMMY_VEHICLE["catalog_id"],
|
||||
"license_plate": DUMMY_VEHICLE["license_plate"]
|
||||
})
|
||||
if resp.status_code in (200, 201):
|
||||
asset_data = resp.json()
|
||||
asset_id = asset_data.get("asset_id") or asset_data.get("id")
|
||||
print(f"✅ Asset claimed via /api/v1/catalog/claim, ID: {asset_id}")
|
||||
else:
|
||||
print(f"⚠️ Could not create vehicle/asset. Skipping. Status: {resp.status_code}, Response: {resp.text}")
|
||||
|
||||
# Step 8: Add expense (if asset created)
|
||||
if asset_id:
|
||||
print("8. Adding expense (15,000 HUF fuel)...")
|
||||
expense_data = DUMMY_EXPENSE.copy()
|
||||
expense_data["asset_id"] = asset_id
|
||||
resp = await client.post("/api/v1/expenses/add", json=expense_data)
|
||||
if resp.status_code in (200, 201):
|
||||
print("✅ Expense added.")
|
||||
else:
|
||||
print(f"⚠️ Expense addition failed: {resp.status_code} {resp.text}")
|
||||
else:
|
||||
print("⚠️ Skipping expense because no asset ID.")
|
||||
|
||||
# Final output
|
||||
print("\n" + "="*60)
|
||||
print("🎉 SANDBOX USER CREATION COMPLETE!")
|
||||
print("="*60)
|
||||
print(f"Email: {SANDBOX_EMAIL}")
|
||||
print(f"Password: {SANDBOX_PASSWORD}")
|
||||
print(f"JWT Access Token: {access_token}")
|
||||
print(f"Organization ID: {org_id}")
|
||||
print(f"Asset/Vehicle ID: {asset_id}")
|
||||
print(f"Login via Swagger: {API_BASE}/docs")
|
||||
print("="*60)
|
||||
print("\nYou can now use these credentials for manual testing.")
|
||||
print("Note: The user is fully verified and has a dummy organization,")
|
||||
print("a dummy vehicle, and a fuel expense of 15,000 HUF.")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user