fcfb767c49
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 <noreply@anthropic.com>
149 lines
5.2 KiB
GDScript
149 lines
5.2 KiB
GDScript
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)
|