extends Node ## Headless E2E — gerçek backend'e karşı istemci akışlarını doğrular. ## Çalıştırma (RP5): tests/run_e2e.sh (token üretir + tea_mint seed eder + bunu çağırır) ## godot --headless --path . res://tests/e2e_test.tscn ## Env: SS_TOKEN_A (ana karakter), SS_TOKEN_B (wstestchar), SS_API_BASE (ops. override) var _fails := 0 var _ws_connected := false var _got_online_list := false var _got_chat_history := false var _got_local_history := false var _moved_payload: Dictionary = {} var _chat_payload: Dictionary = {} func _ready() -> void: await _run() print("== SONUÇ: %s (%d hata) ==" % ["BAŞARILI" if _fails == 0 else "BAŞARISIZ", _fails]) get_tree().quit(0 if _fails == 0 else 1) func _run() -> void: print("== Stepstead headless E2E ==") var base := OS.get_environment("SS_API_BASE") if base != "": Config.override_base = base print(" API base: ", base) var token_a := OS.get_environment("SS_TOKEN_A") var token_b := OS.get_environment("SS_TOKEN_B") if token_a == "" or token_b == "": _check(false, "SS_TOKEN_A / SS_TOKEN_B env değişkenleri gerekli") return Auth.token = token_a # 1) Karakter yükle var res: Dictionary = await GameState.load_character() _check(res.ok and str(GameState.character.get("name", "")) != "", "load_character") print(" karakter: %s, adım: %d" % [str(GameState.character.get("name", "")), int(GameState.character.get("step_balance", 0))]) # 2) Envanter yükle res = await GameState.load_inventory() _check(res.ok, "load_inventory (%d item)" % GameState.inventory.size()) # 3) Consumable kullan (wrapper tea_mint seed etti) var bal_before: int = int(GameState.character.get("step_balance", 0)) res = await GameState.use_item("tea_mint", 1) _check(res.ok, "use_item HTTP") var bal_after: int = int(GameState.character.get("step_balance", 0)) _check(bal_after == bal_before + 50, "use_item +50 adım (önce %d, sonra %d)" % [bal_before, bal_after]) # 4) WebSocket bağlan WsClient.connection_changed.connect(func(c: bool) -> void: _ws_connected = c) WsClient.online_list.connect(func(_players: Array) -> void: _got_online_list = true) WsClient.chat_history.connect(func(_messages: Array) -> void: _got_chat_history = true) WsClient.player_moved.connect(func(p: Dictionary) -> void: _moved_payload = p) WsClient.chat_received.connect(func(m: Dictionary) -> void: _chat_payload = m) WsClient.start() _check(await _wait_for(func() -> bool: return _ws_connected, 10.0), "WS bağlantısı açıldı") _check(await _wait_for(func() -> bool: return _got_online_list, 5.0), "online_list alındı") _check(await _wait_for(func() -> bool: return _got_chat_history, 5.0), "chat_history alındı") # 4b) Chat: gönder → echo geri gelmeli var chat_msg := "e2e test %d" % (Time.get_ticks_msec()) WsClient.send_chat(chat_msg) _check(await _wait_for(func() -> bool: return str(_chat_payload.get("text", "")) == chat_msg, 10.0), "chat gönderildi ve echo alındı") # 4c) Yerel chat: kanal local echo + yerel geçmiş await get_tree().create_timer(1.2).timeout # flood koruması var local_msg := "e2e yerel %d" % (Time.get_ticks_msec()) WsClient.send_chat(local_msg, "local") _check(await _wait_for(func() -> bool: return str(_chat_payload.get("text", "")) == local_msg \ and str(_chat_payload.get("channel", "")) == "local", 10.0), "yerel chat echo alındı (channel=local)") WsClient.local_chat_history.connect(func(_m: Array) -> void: _got_local_history = true) WsClient.request_local_history() _check(await _wait_for(func() -> bool: return _got_local_history, 5.0), "yerel chat geçmişi alındı") # 4d) Günlük hedef + market vitrini res = await GameState.daily_status() _check(res.ok and res.body is Dictionary and res.body.has("goal"), "daily_status (hedef %d, bugün %d)" % [int(res.body.get("goal", 0)), int(res.body.get("today_steps", 0))] if res.ok else "daily_status") var market_rows: Array = await GameState.market_listings() _check(market_rows is Array, "market_listings (%d ilan)" % market_rows.size()) var vendor_rows: Array = await GameState.vendor_prices() _check(not vendor_rows.is_empty(), "vendor_prices (%d çeşit item)" % vendor_rows.size()) var stock_rows: Array = await GameState.vendor_stock() _check(not stock_rows.is_empty(), "vendor_stock (%d mal)" % stock_rows.size()) res = await GameState.leaderboard("steps") _check(res.ok and res.body is Dictionary and not (res.body.get("rows", []) as Array).is_empty(), "leaderboard (sıram #%d)" % int(res.body.get("my_rank", 0)) if res.ok else "leaderboard") # 5) Harita + seyahat (lokasyon-graf dünya) res = await GameState.load_map() var locs: Array = GameState.map_data.get("locations", []) var my_loc := str(GameState.map_data.get("my_location", "")) _check(res.ok and locs.size() >= 10 and my_loc != "", "load_map (%d lokasyon, buradayım: %s)" % [locs.size(), my_loc]) # Bulunduğum yerden giden ilk yolu seç, oraya git var dest := "" for r in GameState.map_data.get("roads", []): if str(r["a"]) == my_loc: dest = str(r["b"]) break if str(r["b"]) == my_loc: dest = str(r["a"]) break _check(dest != "", "gidilecek komşu lokasyon bulundu (%s)" % dest) res = await GameState.travel(dest) _check(res.ok and str(GameState.character.get("location_code", "")) == dest, "travel → %s" % dest) # Yolda olay çıktıysa çöz (0. seçim) if res.ok and res.body is Dictionary and res.body.get("event") is Dictionary: var ev_res: Dictionary = await GameState.resolve_event(0) _check(ev_res.ok, "yol olayı çözüldü (%s)" % str(res.body["event"].get("name", ""))) # 6) Lokasyon detayı res = await GameState.load_location() _check(res.ok and res.body is Dictionary and res.body.has("resources"), "load_location (%d kaynak)" % (res.body.get("resources", []) as Array).size() if res.ok else "load_location") # 7) B karakteri REST ile seyahat etsin → bize location_code'lu player_moved düşmeli Auth.token = token_b var mv: Dictionary = await Api.request("POST", "/world/travel", {"to_code": "fisilti_ormani"}, true) if not mv.ok: mv = await Api.request("POST", "/world/travel", {"to_code": "koy"}, true) Auth.token = token_a _check(mv.ok, "B /world/travel") _check(await _wait_for(func() -> bool: return str(_moved_payload.get("name", "")) == "wstestchar", 10.0), "player_moved eventi WS'ten alındı (lokasyon: %s)" % str(_moved_payload.get("location_code", ""))) WsClient.stop() func _wait_for(cond: Callable, timeout: float) -> bool: var elapsed := 0.0 while elapsed < timeout: if cond.call(): return true await get_tree().process_frame elapsed += get_process_delta_time() return bool(cond.call()) func _check(ok: bool, what: String) -> void: if ok: print(" ✅ ", what) else: _fails += 1 printerr(" ❌ ", what)