import sys, os, json, time, subprocess, signal sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'server')) import httpx from pathlib import Path BASE = "http://127.0.0.1:1589" passed = 0 failed = 0 def check(name, ok, detail=""): global passed, failed if ok: print(f" PASS {name}") passed += 1 else: print(f" FAIL {name} {detail}") failed += 1 def start_server(): env = os.environ.copy() env["PYTHONPATH"] = os.path.join(os.path.dirname(__file__), "server") proc = subprocess.Popen( [sys.executable, "-m", "uvicorn", "main:app", "--host", "127.0.0.1", "--port", "1589"], cwd=os.path.join(os.path.dirname(__file__), "server"), env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) for i in range(30): try: r = httpx.get(f"{BASE}/news", timeout=2) if r.status_code == 200: return proc except: time.sleep(1) return proc proc = None try: print("Starting server...") proc = start_server() print("Server started\n") client = httpx.Client(base_url=BASE, timeout=10) # ---------- NEWS ---------- print("=== NEWS ===") r = client.get("/news") check("GET /news", r.status_code == 200 and "news" in r.json()) data = r.json() check("news is array", isinstance(data.get("news"), list)) check("news has items", len(data["news"]) > 0, f"got {len(data['news'])} items") if data["news"]: first = data["news"][0] check("news item has title", "title" in first) check("news item has body", "body" in first) check("news item has type", "type" in first) # ---------- AUTH ---------- print("\n=== AUTH ===") test_user = f"test_{int(time.time())}" test_pass = "test123" r = client.post("/register", json={"username": test_user, "password": test_pass}) check("POST /register (new user)", r.status_code == 200) reg_data = r.json() check("register returns token", "access_token" in reg_data) r2 = client.post("/register", json={"username": test_user, "password": test_pass}) check("POST /register (duplicate)", r2.status_code == 200) r3 = client.post("/login", json={"username": test_user, "password": test_pass}) check("POST /login (valid)", r3.status_code == 200) login_data = r3.json() check("login returns access_token", "access_token" in login_data) token = login_data.get("access_token", "") r4 = client.post("/login", json={"username": test_user, "password": "wrong"}) check("POST /login (wrong pass)", r4.status_code == 401) r5 = client.post("/login", json={"username": "nonexistent", "password": "x"}) check("POST /login (nonexistent)", r5.status_code == 401) authed = httpx.Client(base_url=BASE, timeout=10, headers={"Authorization": f"Bearer {token}"}) # ---------- ACCOUNT ---------- print("\n=== ACCOUNT ===") r = authed.get("/account") check("GET /account (authed)", r.status_code == 200) acct = r.json() check("account has username", acct.get("username") == test_user, f"got {acct.get('username')}") check("account has role", "role" in acct) check("account has passActive", "passActive" in acct) r2 = client.get("/account") check("GET /account (unauthed)", r2.status_code == 401) # ---------- PACKS ---------- print("\n=== PACKS ===") r = client.get("/packs") check("GET /packs (unauthed)", r.status_code == 401 or r.status_code == 403, f"got {r.status_code}") r2 = authed.get("/packs") check("GET /packs (authed)", r2.status_code in (200, 403), f"got {r2.status_code}") if r2.status_code == 200: packs = r2.json() check("packs has packs key", "packs" in packs) check("packs is list", isinstance(packs.get("packs"), list)) # ---------- FRIENDS (no auth) ---------- print("\n=== FRIENDS ===") r = client.get("/api/friends/list") check("GET /friends/list (unauthed)", r.status_code == 401) r2 = authed.get("/api/friends/list") check("GET /friends/list (authed)", r2.status_code == 200) flist = r2.json() check("friends list has friends key", "friends" in flist) r3 = authed.get("/api/friends/requests") check("GET /friends/requests (authed)", r3.status_code == 200) freq = r3.json() check("friends requests has requests key", "requests" in freq) # ---------- FRIENDS add/search ---------- friend_user = f"friend_{int(time.time())}" r = client.post("/register", json={"username": friend_user, "password": "pass123"}) check("register friend user", r.status_code == 200) friend_token = r.json().get("access_token", "") friend_authed = httpx.Client(base_url=BASE, timeout=10, headers={"Authorization": f"Bearer {friend_token}"}) r_add = authed.post("/api/friends/add", json={"username": friend_user}) check("POST /friends/add", r_add.status_code == 200, f"got {r_add.status_code}: {r_add.text[:100]}") r_dup = authed.post("/api/friends/add", json={"username": friend_user}) check("POST /friends/add (duplicate)", r_dup.status_code == 400) r_self = authed.post("/api/friends/add", json={"username": test_user}) check("POST /friends/add (self)", r_self.status_code == 400) r_nonexist = authed.post("/api/friends/add", json={"username": "nonexistent_xyz"}) check("POST /friends/add (nonexistent)", r_nonexist.status_code == 404) # Friend accepts r_accept = friend_authed.post("/api/friends/accept", json={"user_id": acct.get("id", 0)}) check("POST /friends/accept", r_accept.status_code in (200, 404), f"got {r_accept.status_code}: {r_accept.text[:100]}") # Check friends list now shows each other r_list = authed.get("/api/friends/list") check("GET /friends/list (after accept)", r_list.status_code == 200) # Remove friend friend_acct = r.json() r_remove = authed.post("/api/friends/remove", json={"user_id": 9999}) check("POST /friends/remove (nonexistent)", r_remove.status_code == 404) # ---------- PLAYTIME ---------- print("\n=== PLAYTIME ===") r = authed.post("/api/playtime/sync", json={"pack_name": "test_pack", "minutes": 30}) check("POST /playtime/sync", r.status_code == 200, r.text[:100]) r2 = authed.get("/api/playtime/stats") check("GET /playtime/stats", r2.status_code == 200) stats = r2.json() check("playtime stats has total_minutes", "total_minutes" in stats) check("playtime stats has packs", "packs" in stats) r3 = client.get("/api/playtime/stats") check("GET /playtime/stats (unauthed)", r3.status_code == 401) # ---------- SUMMARY ---------- total = passed + failed print(f"\n{'='*40}") print(f"RESULTS: {passed}/{total} passed, {failed}/{total} failed") except Exception as e: print(f"\nTEST ERROR: {e}") import traceback traceback.print_exc() finally: if proc: proc.terminate() proc.wait(timeout=5) sys.exit(1 if failed > 0 else 0)