extends GdUnitTestSuite const HotbarClient := preload("res://scripts/hotbar_loadout_client.gd") const HotbarState := preload("res://scripts/hotbar_state.gd") class MockHttpTransport: extends Node signal request_completed( result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray ) var last_url: String = "" var last_body: String = "" var last_method: HTTPClient.Method = HTTPClient.METHOD_GET var _queue: Array[Dictionary] = [] func enqueue(result: int, code: int, body: String) -> void: _queue.append({"result": result, "code": code, "body": body}) func request( url: String, _custom_headers: PackedStringArray = PackedStringArray(), method: HTTPClient.Method = HTTPClient.METHOD_GET, request_data: String = "" ) -> Error: last_url = url last_method = method last_body = request_data if _queue.is_empty(): return ERR_UNAVAILABLE var r: Dictionary = _queue.pop_front() request_completed.emit( r["result"], r["code"], PackedStringArray(), str(r["body"]).to_utf8_buffer() ) return OK func _make_client(transport: Node) -> Node: var c: Node = HotbarClient.new() c.set("injected_http", transport) auto_free(transport) auto_free(c) add_child(c) return c func _sample_slots_json() -> String: return ( '{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[' + '{"slotIndex":0,"abilityId":"prototype_pulse"},' + '{"slotIndex":1,"abilityId":null},' + '{"slotIndex":2,"abilityId":"prototype_guard"},' + '{"slotIndex":3,"abilityId":null},' + '{"slotIndex":4,"abilityId":null},' + '{"slotIndex":5,"abilityId":null},' + '{"slotIndex":6,"abilityId":null},' + '{"slotIndex":7,"abilityId":null}' + "]}" ) func test_sync_get_applies_slots_to_hotbar_state() -> void: var transport := MockHttpTransport.new() transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _sample_slots_json()) var c := _make_client(transport) var state := HotbarState.new() auto_free(state) add_child(state) c.call("set_hotbar_state", state) c.call("request_sync_from_server") var slots: Array = state.call("slots_snapshot") assert_that(slots.size()).is_equal(8) assert_that(slots[0]).is_equal("prototype_pulse") assert_that(slots[2]).is_equal("prototype_guard") assert_that(slots[1]).is_null() func test_bind_slot_posts_expected_payload() -> void: var transport := MockHttpTransport.new() transport.enqueue( HTTPRequest.RESULT_SUCCESS, 200, '{"schemaVersion":1,"updated":true,"loadout":{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[]}}' ) var c := _make_client(transport) c.call("request_bind_slot", 3, "prototype_burst") assert_that(transport.last_url).contains("/hotbar-loadout") assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST) var parsed: Variant = JSON.parse_string(transport.last_body) assert_that(parsed is Dictionary).is_true() var body: Dictionary = parsed assert_that(int(body.get("schemaVersion", 0))).is_equal(1) var slots: Array = body.get("slots", []) assert_that(slots.size()).is_equal(1) assert_that(int((slots[0] as Dictionary).get("slotIndex", -1))).is_equal(3) assert_that((slots[0] as Dictionary).get("abilityId")).is_equal("prototype_burst") func test_denied_update_emits_reason_signal() -> void: var transport := MockHttpTransport.new() transport.enqueue( HTTPRequest.RESULT_SUCCESS, 200, '{"schemaVersion":1,"updated":false,"reasonCode":"unknown_ability","loadout":{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[]}}' ) var c := _make_client(transport) monitor_signals(c) c.call("request_bind_slot", 1, "not_real") assert_signal(c).is_emitted("loadout_update_denied")