extends GdUnitTestSuite ## NEO-74: `RecipeDefinitionsClient` GET parse + `recipes_ready` signal. ## `main.gd` repopulates craft panel on item `definitions_ready` even when inventory GET failed. const RecipeDefinitionsClient := preload("res://scripts/recipe_definitions_client.gd") var _recipes_capture: Array = [] func _capture_recipes(recipes: Array) -> void: _recipes_capture = recipes class MockHttpTransport: extends Node signal request_completed( result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray ) var last_url: String = "" var response_code: int = 200 var body_json: String = "" func request( url: String, _custom_headers: PackedStringArray = PackedStringArray(), _method: HTTPClient.Method = HTTPClient.METHOD_GET, _request_data: String = "" ) -> Error: last_url = url request_completed.emit( HTTPRequest.RESULT_SUCCESS, response_code, PackedStringArray(), body_json.to_utf8_buffer() ) return OK static func _two_recipes_json() -> String: return ( '{"schemaVersion":1,"recipes":[' + '{"id":"make_armor_quick","displayName":"Quick Armor","recipeKind":"make",' + '"requiredSkillId":"refine","inputs":[],"outputs":[]},' + '{"id":"refine_scrap_standard","displayName":"Refine Scrap (Standard)",' + '"recipeKind":"process","requiredSkillId":"refine",' + '"inputs":[{"itemId":"scrap_metal_bulk","quantity":5}],' + '"outputs":[{"itemId":"refined_plate_stock","quantity":1}]}' + "]}" ) func _make_client(transport: Node) -> Node: var c: Node = RecipeDefinitionsClient.new() c.set("injected_http", transport) auto_free(transport) auto_free(c) add_child(c) return c func test_parse_recipes_json_reads_array() -> void: # Arrange var json := _two_recipes_json() # Act var recipes: Variant = RecipeDefinitionsClient.parse_recipes_json(json) # Assert assert_that(recipes is Array).is_true() assert_that((recipes as Array).size()).is_equal(2) func test_request_sync_emits_recipes_ready_in_order() -> void: # Arrange _recipes_capture = [] var transport := MockHttpTransport.new() transport.body_json = _two_recipes_json() var c := _make_client(transport) c.connect("recipes_ready", Callable(self, "_capture_recipes")) monitor_signals(c) # Act c.call("request_sync_from_server") # Assert await assert_signal(c).is_emitted("recipes_ready", any()) assert_that(_recipes_capture.size()).is_equal(2) assert_that(str(_recipes_capture[0].get("id", ""))).is_equal("make_armor_quick") assert_that(transport.last_url).contains("/recipe-definitions") func test_schema_mismatch_emits_recipes_sync_failed() -> void: # Arrange var transport := MockHttpTransport.new() transport.body_json = '{"schemaVersion":99,"recipes":[]}' var c := _make_client(transport) monitor_signals(c) # Act c.call("request_sync_from_server") # Assert await assert_signal(c).is_emitted("recipes_sync_failed", any()) func test_http_error_emits_recipes_sync_failed_with_status() -> void: # Arrange var transport := MockHttpTransport.new() transport.response_code = 503 var c := _make_client(transport) var got: Array = [] c.connect("recipes_sync_failed", func(reason: String) -> void: got.append(reason)) # Act c.call("request_sync_from_server") # Assert assert_that(got.size()).is_equal(1) assert_that(str(got[0])).contains("503")