extends SceneTree ## End-to-end headless smoke test: register -> login -> fetch worlds list. ## ## Run with: ## godot --headless --path . --script res://scripts/smoke_test_e2e.gd ## ## Requires the backend to be running (default base URL ## `application/agp/api_base_url` -> http://127.0.0.1:8000, overridable via ## the AGP_API_BASE_URL environment variable). ## ## Steps: ## 1. Health check GET /api/health -> 200, {"status": "ok"} ## 2. Register POST /api/auth/register -> 201, access_token present ## 3. Login POST /api/auth/login -> 200, access_token present ## 4. Worlds list GET /api/worlds -> 200, JSON array ## ## Exits 0 if all steps pass, 1 otherwise. If the backend is unreachable the ## health check fails with a clear message and the script exits 1 immediately. # Fixed credentials from the task spec. The username gets a unique numeric # suffix so the test stays re-runnable (a second run would otherwise hit # "Username already registered" on step 2). const TEST_USERNAME_BASE := "smoke_test_user" const TEST_EMAIL := "smoke@test.com" const TEST_PASSWORD := "TestPass123" const TEST_DOB := "2000-01-01" func _initialize() -> void: _run() func _run() -> void: # _initialize() fires before the first frame; wait one frame so freshly # created HTTPRequest nodes are reliably inside the tree. await process_frame # In --script mode the autoload is reachable via get_node(), not as a # bare identifier (bare identifiers fail to resolve at compile time here). var api := root.get_node("ApiClient") assert(api != null, "ApiClient autoload not found in the scene tree") var unique_id := int(Time.get_unix_time_from_system() * 1000) var username := "%s_%d" % [TEST_USERNAME_BASE, unique_id] var email := "smoke_%d@test.com" % unique_id var results := {} # --- Step 1: Health check ------------------------------------------------- print("\n=== Step 1: Health check (GET /api/health) ===") var health: Dictionary = await api.health_check() var health_ok: bool = ( health.ok and health.status == 200 and health.data is Dictionary and str((health.data as Dictionary).get("status", "")) == "ok" ) _record(results, "health", health_ok, "status=%d data=%s" % [ health.status, health.data ]) if not health.ok or health.status == 0: printerr("\nERROR: Backend is not reachable at the configured base URL.") printerr(" detail: %s" % health.detail) printerr(" Start the backend first (see backend/README), then re-run:") printerr(" godot --headless --path . --script res://scripts/smoke_test_e2e.gd") _summarize(results) quit(1) return # --- Step 2: Register ----------------------------------------------------- print("\n=== Step 2: Register (POST /api/auth/register) ===") var reg: Dictionary = await api.register(username, email, TEST_PASSWORD, TEST_DOB) var reg_token := _extract_token(reg) var reg_ok: bool = ( reg.ok and reg.status == 201 and reg_token != "" ) _record(results, "register", reg_ok, "status=%d token=%s detail=%s" % [ reg.status, "yes" if reg_token != "" else "no", reg.detail ]) # --- Step 3: Login -------------------------------------------------------- print("\n=== Step 3: Login (POST /api/auth/login) ===") var login: Dictionary = await api.login(username, TEST_PASSWORD) var login_token := _extract_token(login) var login_ok: bool = ( login.ok and login.status == 200 and login_token != "" ) _record(results, "login", login_ok, "status=%d token=%s detail=%s" % [ login.status, "yes" if login_token != "" else "no", login.detail ]) # --- Step 4: Worlds list --------------------------------------------------- print("\n=== Step 4: Worlds list (GET /api/worlds) ===") # login/register already stored the token; make the auth header explicit by # re-applying the extracted token so step 4 is independent of storage order. if login_token != "": api.set_token(login_token) var worlds: Dictionary = await api.get_worlds() var worlds_ok: bool = ( worlds.ok and worlds.status == 200 and worlds.data is Array ) _record(results, "worlds", worlds_ok, "status=%d is_array=%s count=%d detail=%s" % [ worlds.status, "yes" if (worlds.data is Array) else "no", (worlds.data as Array).size() if (worlds.data is Array) else -1, worlds.detail, ]) # --- Summary --------------------------------------------------------------- _summarize(results) func _extract_token(result: Dictionary) -> String: if result.ok and result.data is Dictionary: return str((result.data as Dictionary).get("access_token", "")) return "" func _record(results: Dictionary, name: String, ok: bool, detail: String) -> void: results[name] = {"ok": ok, "detail": detail} if ok: print(" PASS: %s" % detail) else: printerr(" FAIL: %s" % detail) func _summarize(results: Dictionary) -> void: var all_pass := true print("\n=== Smoke test summary ===") for step in ["health", "register", "login", "worlds"]: var entry: Dictionary = results.get(step, {"ok": false, "detail": "not run"}) if not entry["ok"]: all_pass = false print(" %-8s : %s" % [step, "PASS" if entry["ok"] else "FAIL"]) if all_pass: print("SMOKE RESULT: ALL PASS (4/4)") quit(0) else: printerr("SMOKE RESULT: FAIL") quit(1)