From e2e771d2f88d3d38c6bc91ae2f6dbf40fbdefffb Mon Sep 17 00:00:00 2001 From: tester-bot Date: Wed, 19 Aug 2026 23:15:22 +0000 Subject: [PATCH 1/2] WIP checkpoint: End-to-end headless smoke test + manual verify --- scripts/smoke_test.gd | 148 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 scripts/smoke_test.gd diff --git a/scripts/smoke_test.gd b/scripts/smoke_test.gd new file mode 100644 index 0000000..a4482fe --- /dev/null +++ b/scripts/smoke_test.gd @@ -0,0 +1,148 @@ +extends SceneTree +## End-to-end headless smoke test: register -> login -> fetch worlds list. +## +## Run with: +## godot --headless --path . --quit --script res://scripts/smoke_test.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 . --quit --script res://scripts/smoke_test.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) From fcfb767c494d02b84214a98ac9a25b9af91f8d48 Mon Sep 17 00:00:00 2001 From: tester-bot Date: Wed, 19 Aug 2026 23:30:52 +0000 Subject: [PATCH 2/2] End-to-end headless smoke test: register -> login -> fetch worlds Tester had already written a complete, correct test (4/4 steps pass against a real backend) but stalled investigating DB/migration setup after its own verification run silently exited with no result -- the task spec's suggested invocation included --quit, which kills a --script SceneTree test before its awaited HTTP calls finish (same Godot --script-mode gotcha documented elsewhere this session). Renamed smoke_test.gd -> smoke_test_e2e.gd: the task spec named it without the checkpoint runner's expected smoke_test_*.gd suffix, so scripts/run_checkpoint_tests.py's glob would have silently never picked it up. Also fixed the --quit flag in the file's own usage comments. Co-Authored-By: Claude Sonnet 5 --- scripts/{smoke_test.gd => smoke_test_e2e.gd} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename scripts/{smoke_test.gd => smoke_test_e2e.gd} (96%) diff --git a/scripts/smoke_test.gd b/scripts/smoke_test_e2e.gd similarity index 96% rename from scripts/smoke_test.gd rename to scripts/smoke_test_e2e.gd index a4482fe..dacd41a 100644 --- a/scripts/smoke_test.gd +++ b/scripts/smoke_test_e2e.gd @@ -2,7 +2,7 @@ extends SceneTree ## End-to-end headless smoke test: register -> login -> fetch worlds list. ## ## Run with: -## godot --headless --path . --quit --script res://scripts/smoke_test.gd +## 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 @@ -63,7 +63,7 @@ func _run() -> void: 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 . --quit --script res://scripts/smoke_test.gd") + printerr(" godot --headless --path . --script res://scripts/smoke_test_e2e.gd") _summarize(results) quit(1) return