Files
resolutionflow/backend/scripts/seed_test_users.py
chihlasm 01a062169e chore: add test user seed script and fix seed flow validation
Add seed_test_users.py for creating 4 dev accounts (super admin, pro
solo, team admin, team engineer) via direct SQL. Fix seed scripts to
create flows as drafts to bypass publish validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 22:48:51 -05:00

189 lines
6.6 KiB
Python
Raw Permalink 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
"""
Create test user accounts for local development.
Creates 4 accounts:
1. Super Admin platform-wide admin (manages everything)
2. Pro Solo User single user on a "pro" plan
3. Team Admin admin of a team account ("team" plan)
4. Team Engineer regular engineer on the same team account
Usage:
cd backend
python -m scripts.seed_test_users
"""
import asyncio
import random
import string
import uuid
from datetime import datetime, timezone
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
from app.core.config import settings
from app.core.security import get_password_hash
# ---------------------------------------------------------------------------
# Configuration change passwords here if you like
# ---------------------------------------------------------------------------
SHARED_PASSWORD = "TestPass123!"
USERS = [
{
"key": "super_admin",
"name": "RF Super Admin",
"email": "admin@resolutionflow.example.com",
"is_super_admin": True,
"is_team_admin": False,
"account_name": "ResolutionFlow Platform",
"account_role": "owner",
"plan": "team",
},
{
"key": "pro_solo",
"name": "Pat Solo",
"email": "pro@resolutionflow.example.com",
"is_super_admin": False,
"is_team_admin": False,
"account_name": "Pat's MSP",
"account_role": "owner",
"plan": "pro",
},
{
"key": "team_admin",
"name": "Alex Manager",
"email": "teamadmin@resolutionflow.example.com",
"is_super_admin": False,
"is_team_admin": True,
"account_name": "Acme MSP",
"account_role": "owner", # owns the account; is_team_admin=True gives admin powers
"plan": "team",
},
{
"key": "team_engineer",
"name": "Jordan Tech",
"email": "engineer@resolutionflow.example.com",
"is_super_admin": False,
"is_team_admin": False,
"account_name": "Acme MSP", # same shared account
"account_role": "engineer",
"plan": None, # uses the team_admin's account & subscription
},
]
def _display_code() -> str:
return "".join(random.choices(string.ascii_uppercase + string.digits, k=8))
async def main() -> None:
engine = create_async_engine(settings.DATABASE_URL, echo=False)
password_hash = get_password_hash(SHARED_PASSWORD)
now = datetime.now(timezone.utc)
team_account_id: uuid.UUID | None = None
async with engine.begin() as conn:
for cfg in USERS:
# Check if user already exists
result = await conn.execute(
text("SELECT id, account_id FROM users WHERE email = :email"),
{"email": cfg["email"]},
)
row = result.first()
if row:
print(f" [SKIP] {cfg['email']} already exists")
if cfg["key"] == "team_admin":
team_account_id = row.account_id
continue
# ---- Create or reuse Account ----
if cfg["key"] == "team_engineer":
if team_account_id is None:
result = await conn.execute(
text("SELECT id FROM accounts WHERE name = :name"),
{"name": "Acme MSP"},
)
acme = result.first()
if acme:
team_account_id = acme.id
if team_account_id is None:
print(f" [ERROR] Cannot create {cfg['email']} — Acme MSP account not found.")
continue
account_id = team_account_id
else:
account_id = uuid.uuid4()
await conn.execute(
text("""
INSERT INTO accounts (id, name, display_code, created_at, updated_at)
VALUES (:id, :name, :code, :now, :now)
"""),
{"id": account_id, "name": cfg["account_name"], "code": _display_code(), "now": now},
)
if cfg["key"] == "team_admin":
team_account_id = account_id
# ---- Create User ----
user_id = uuid.uuid4()
await conn.execute(
text("""
INSERT INTO users (id, email, password_hash, name, role, is_super_admin,
is_team_admin, is_active, account_id, account_role, created_at)
VALUES (:id, :email, :pw, :name, 'engineer', :is_sa, :is_ta, true,
:account_id, :account_role, :now)
"""),
{
"id": user_id,
"email": cfg["email"],
"pw": password_hash,
"name": cfg["name"],
"is_sa": cfg["is_super_admin"],
"is_ta": cfg["is_team_admin"],
"account_id": account_id,
"account_role": cfg["account_role"],
"now": now,
},
)
# Set account owner (skip for team_engineer — they don't own the account)
if cfg["key"] != "team_engineer":
await conn.execute(
text("UPDATE accounts SET owner_id = :uid WHERE id = :aid"),
{"uid": user_id, "aid": account_id},
)
# ---- Create Subscription (once per account) ----
if cfg["plan"] is not None:
await conn.execute(
text("""
INSERT INTO subscriptions (id, account_id, plan, status, created_at, updated_at)
VALUES (:id, :aid, :plan, 'active', :now, :now)
"""),
{"id": uuid.uuid4(), "aid": account_id, "plan": cfg["plan"], "now": now},
)
print(f" [OK] {cfg['email']:40s} account_role={cfg['account_role']:<10s} plan={cfg['plan'] or '(shared)'}")
await engine.dispose()
print()
print("=" * 60)
print(" Test accounts ready!")
print(f" Password for all accounts: {SHARED_PASSWORD}")
print("=" * 60)
print()
print(" Accounts:")
print(f" Super Admin : admin@resolutionflow.example.com")
print(f" Pro Solo : pro@resolutionflow.example.com")
print(f" Team Admin : teamadmin@resolutionflow.example.com")
print(f" Team Engineer: engineer@resolutionflow.example.com")
print()
if __name__ == "__main__":
print("\n[*] ResolutionFlow — Test User Seeder")
print("=" * 60)
asyncio.run(main())