demo_user_run_store.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. """End-to-end demo: UserStore + RunStore against real MySQL.
  2. Setup (raw SQL, since ServerStore/ZoneStore are fold 2):
  3. 1. Open connection
  4. 2. Insert a test server (idempotent on server_name)
  5. 3. Insert a test zone (idempotent on server_id+zone_id+open_time)
  6. Then exercise the new Stores:
  7. 4. UserStore.upsert_by_username -> save a fake token
  8. 5. UserStore.get_by_username -> read it back, verify id
  9. 6. RunStore.create -> create a run for that user
  10. 7. RunStore.update_status -> flip to success after "completion"
  11. 8. RunStore.list_recent -> read it back, verify
  12. 9. Print a summary table
  13. Cleanup (--cleanup flag):
  14. 10. Delete the test user and run
  15. """
  16. from __future__ import annotations
  17. import argparse
  18. import os
  19. import sys
  20. from datetime import datetime, timedelta
  21. from pathlib import Path
  22. _HERE = Path(__file__).resolve().parent
  23. sys.path.insert(0, str(_HERE.parent / "src"))
  24. # load .env from project root (not CWD) so script works regardless of where it's invoked
  25. from cq2_sdk.env_loader import load_env # noqa: E402
  26. _PROJECT_ROOT = _HERE.parent
  27. load_env(_PROJECT_ROOT / ".env")
  28. from cq2_sdk.db_models import Run, User # noqa: E402
  29. from cq2_sdk.store import RunStore, ServerStore, UserStore, ZoneStore, connection # noqa: E402
  30. from cq2_sdk.db_models import Zone as DbZone # noqa: E402
  31. TEST_USERNAME = "demo_user_run_store"
  32. TEST_SERVER = "DEMO_SERVER_玖扬复古176"
  33. TEST_ZONE_ID = "demo_zone_001"
  34. def setup_server_and_zone(servers: ServerStore, zones: ZoneStore) -> tuple[int, int]:
  35. """Insert test server + zone via the new stores (no raw SQL)."""
  36. srv = servers.upsert(TEST_SERVER, 99999999, game_id=206923)
  37. open_time = datetime.now().replace(microsecond=0)
  38. zones.bulk_upsert(srv.id, [
  39. DbZone(zone_id=TEST_ZONE_ID, zone_name="Demo Zone",
  40. host="demo.host", port=12345, open_time=open_time, status="open"),
  41. ])
  42. zone = zones.get_latest_openable(srv.id)
  43. if zone is None:
  44. raise RuntimeError("setup failed: zone not openable after bulk_upsert")
  45. return srv.id, zone.id
  46. def cleanup(cur, user_id: int, run_id: int | None) -> None:
  47. if run_id is not None:
  48. cur.execute("DELETE FROM runs WHERE id = %s", (run_id,))
  49. if user_id:
  50. cur.execute("DELETE FROM users WHERE id = %s", (user_id,))
  51. def main() -> int:
  52. parser = argparse.ArgumentParser(description="Demo UserStore + RunStore")
  53. parser.add_argument("--cleanup", action="store_true",
  54. help="Delete the test user and run after demo")
  55. args = parser.parse_args()
  56. print("=" * 70)
  57. print("UserStore + RunStore end-to-end demo")
  58. print("=" * 70)
  59. with connection() as conn:
  60. users = UserStore(conn)
  61. runs = RunStore(conn)
  62. servers = ServerStore(conn)
  63. zones = ZoneStore(conn)
  64. # 0. setup server + zone via the new Stores (no raw SQL)
  65. print("\n[0] setup test server + zone via ServerStore/ZoneStore...")
  66. server_id, zone_id = setup_server_and_zone(servers, zones)
  67. print(f" server_id = {server_id}, zone_id = {zone_id}")
  68. # 1. user upsert
  69. print("\n[1] UserStore.upsert_by_username...")
  70. expires = datetime.now() + timedelta(hours=24)
  71. user = users.upsert_by_username(TEST_USERNAME, "demo_jwt_token_xyz", expires)
  72. print(f" user.id = {user.id}")
  73. print(f" user.username = {user.username}")
  74. print(f" user.token = {user.token!r}")
  75. print(f" user.last_login = {user.last_login_at}")
  76. # 2. user read back
  77. print("\n[2] UserStore.get_by_username...")
  78. refetched = users.get_by_username(TEST_USERNAME)
  79. assert refetched is not None, "upsert succeeded but can't read back"
  80. assert refetched.id == user.id, "id changed after upsert"
  81. assert refetched.token == "demo_jwt_token_xyz"
  82. print(f" refetched.id = {refetched.id} (matches)")
  83. print(f" refetched.token = {refetched.token!r} (matches)")
  84. # 3. run create
  85. print("\n[3] RunStore.create...")
  86. run = runs.create(user_id=user.id, server_id=server_id, zone_id=zone_id)
  87. print(f" run.id = {run.id}")
  88. print(f" run.user_id = {run.user_id}")
  89. print(f" run.status = {run.status}")
  90. print(f" run.started_at = {run.started_at}")
  91. # 4. run update (running -> success)
  92. print("\n[4] RunStore.update_status(running -> success)...")
  93. updated = runs.update_status(run.id, "success", current_task="t13")
  94. print(f" run.status = {updated.status}")
  95. print(f" run.current_task = {updated.current_task}")
  96. print(f" run.completed_at = {updated.completed_at}")
  97. # 5. list recent
  98. print("\n[5] RunStore.list_recent(user_id)...")
  99. recent = runs.list_recent(user.id, limit=5)
  100. print(f" found {len(recent)} run(s) for user_id={user.id}")
  101. for r in recent:
  102. print(f" - run.id={r.id} status={r.status} task={r.current_task} "
  103. f"started={r.started_at} completed={r.completed_at}")
  104. # 6. summary
  105. print("\n[summary] persisted state in MySQL 996cq:")
  106. print(f" users.id = {user.id} username='{user.username}'")
  107. print(f" runs.id = {run.id} user_id={run.user_id} status='{updated.status}'")
  108. if args.cleanup:
  109. print("\n[6] --cleanup: deleting test rows...")
  110. with conn.cursor() as cur:
  111. cleanup(cur, user.id, run.id)
  112. print(" done")
  113. else:
  114. print("\n (use --cleanup to delete the test rows)")
  115. print("\n[OK] demo complete.")
  116. return 0
  117. if __name__ == "__main__":
  118. sys.exit(main())