From e7a61a1b3ff0b6e9c3b6da53228cde02b5a201a8 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 24 May 2026 18:43:39 -0400 Subject: [PATCH] NEO-72: fix HTTP client header types and strengthen tests. Use PackedStringArray for request_completed headers so handlers run; assert inventory payload shape and display_name_for after sync per review. --- client/README.md | 2 +- client/scripts/inventory_client.gd | 2 +- client/scripts/item_definitions_client.gd | 2 +- client/test/inventory_client_test.gd | 11 ++++ client/test/item_definitions_client_test.gd | 51 +++++++------------ .../modules/module_dependency_register.md | 2 +- 6 files changed, 32 insertions(+), 38 deletions(-) diff --git a/client/README.md b/client/README.md index 68106c9..94353dc 100644 --- a/client/README.md +++ b/client/README.md @@ -203,7 +203,7 @@ On **Linux** (including GitHub Actions), the path must be **`gdUnit4`** with a c **CI:** The **GDScript** workflow fails the job if Godot prints **`SCRIPT ERROR:`** or **`ERROR: Failed to load script`** while running GdUnit. GdUnit alone can still exit **0** when a test file fails to parse (that suite is skipped); the workflow guards against that. -**Scope:** Unit tests cover **`player.gd`**, **`player_locomotion_wasd.gd`** (pure WASD seam helpers), **`position_authority_client.gd`**, **`isometric_follow_camera.gd`** (eye math + effective zoom distance), **`camera_state.gd`**, and **`zoom_band_config.gd`**. **`main.gd`** and scene wiring are **not** automated here—use manual checks above. +**Scope:** Unit tests cover **`player.gd`**, **`player_locomotion_wasd.gd`** (pure WASD seam helpers), **`position_authority_client.gd`**, **`inventory_client.gd`**, **`item_definitions_client.gd`** (NEO-72), **`isometric_follow_camera.gd`** (eye math + effective zoom distance), **`camera_state.gd`**, and **`zoom_band_config.gd`**. **`main.gd`** and scene wiring are **not** automated here—use manual checks above. **Camera zoom:** Input actions **`camera_zoom_in`** / **`camera_zoom_out`** (mouse wheel, **`=`** / **`-`**, keypad **+** / **−**) are defined in **`project.godot`**. On layouts where **`+`** requires **Shift**, remap or add a binding under **Project → Project Settings → Input Map** if needed. diff --git a/client/scripts/inventory_client.gd b/client/scripts/inventory_client.gd index 3ba2f2d..6dd5984 100644 --- a/client/scripts/inventory_client.gd +++ b/client/scripts/inventory_client.gd @@ -59,7 +59,7 @@ static func parse_inventory_json(text: String) -> Variant: func _on_request_completed( - result: int, response_code: int, _headers: PackedByteArray, body: PackedByteArray + result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray ) -> void: _busy = false if result != HTTPRequest.RESULT_SUCCESS: diff --git a/client/scripts/item_definitions_client.gd b/client/scripts/item_definitions_client.gd index 1dda1ab..9decc87 100644 --- a/client/scripts/item_definitions_client.gd +++ b/client/scripts/item_definitions_client.gd @@ -75,7 +75,7 @@ func _base_root() -> String: func _on_request_completed( - result: int, response_code: int, _headers: PackedByteArray, body: PackedByteArray + result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray ) -> void: _busy = false if result != HTTPRequest.RESULT_SUCCESS: diff --git a/client/test/inventory_client_test.gd b/client/test/inventory_client_test.gd index ddc8f3d..21247e5 100644 --- a/client/test/inventory_client_test.gd +++ b/client/test/inventory_client_test.gd @@ -4,6 +4,12 @@ extends GdUnitTestSuite const InventoryClient := preload("res://scripts/inventory_client.gd") +var _inventory_capture: Dictionary = {} + + +func _capture_inventory(snapshot: Dictionary) -> void: + _inventory_capture = snapshot + class MockHttpTransport: extends Node @@ -64,14 +70,19 @@ func test_parse_inventory_json_empty_grid() -> void: func test_request_sync_emits_inventory_received() -> void: # Arrange + _inventory_capture = {} var transport := MockHttpTransport.new() transport.body_json = _empty_inventory_json() var c := _make_client(transport) + c.connect("inventory_received", Callable(self, "_capture_inventory")) monitor_signals(c) # Act c.call("request_sync_from_server") # Assert assert_signal(c).is_emitted("inventory_received") + assert_that(_inventory_capture.has("bagSlots")).is_true() + assert_that((_inventory_capture["bagSlots"] as Array).size()).is_equal(24) + assert_that((_inventory_capture["equipmentSlots"] as Array).size()).is_equal(1) assert_that(transport.last_url).contains("/inventory") diff --git a/client/test/item_definitions_client_test.gd b/client/test/item_definitions_client_test.gd index a075124..cea7b47 100644 --- a/client/test/item_definitions_client_test.gd +++ b/client/test/item_definitions_client_test.gd @@ -4,6 +4,11 @@ extends GdUnitTestSuite const ItemDefsClient := preload("res://scripts/item_definitions_client.gd") +const _SAMPLE_BODY_JSON := ( + '{"schemaVersion":1,"items":[{"id":"scrap_metal_bulk",' + + '"displayName":"Scrap Metal (Bulk)","inventorySlotKind":"bag"}]}' +) + class MockHttpTransport: extends Node @@ -69,43 +74,10 @@ func test_build_definitions_map_extracts_display_name() -> void: assert_that(built["scrap_metal_bulk"]["displayName"]).is_equal("Scrap Metal (Bulk)") -func test_parse_mock_transport_json_body() -> void: - # Arrange - var json := ( - '{"schemaVersion":1,"items":[{"id":"scrap_metal_bulk",' - + '"displayName":"Scrap Metal (Bulk)","inventorySlotKind":"bag"}]}' - ) - # Act - var parsed: Variant = JSON.parse_string(json) - var built: Dictionary = ItemDefsClient.build_definitions_map(parsed) - # Assert - assert_that(built.has("scrap_metal_bulk")).is_true() - - -func test_handler_body_bytes_parse_builds_map() -> void: - # Arrange - var body_json := ( - '{"schemaVersion":1,"items":[{"id":"scrap_metal_bulk",' - + '"displayName":"Scrap Metal (Bulk)","inventorySlotKind":"bag"}]}' - ) - var body_bytes: PackedByteArray = body_json.to_utf8_buffer() - # Act - var text: String = body_bytes.get_string_from_utf8() - var parsed: Variant = JSON.parse_string(text) - var built: Dictionary = ItemDefsClient.build_definitions_map(parsed) - # Assert - assert_that(int((parsed as Dictionary).get("schemaVersion", -1))).is_equal(1) - assert_that(built.has("scrap_metal_bulk")).is_true() - - func test_request_sync_hits_definitions_endpoint_and_emits() -> void: # Arrange var transport := MockHttpTransport.new() - var body_json := ( - '{"schemaVersion":1,"items":[{"id":"scrap_metal_bulk",' - + '"displayName":"Scrap Metal (Bulk)","inventorySlotKind":"bag"}]}' - ) - transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, body_json) + transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _SAMPLE_BODY_JSON) var c := _make_client(transport) monitor_signals(c) # Act @@ -115,6 +87,17 @@ func test_request_sync_hits_definitions_endpoint_and_emits() -> void: assert_signal(c).is_emitted("definitions_ready") +func test_display_name_for_after_request_sync() -> void: + # Arrange + var transport := MockHttpTransport.new() + transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _SAMPLE_BODY_JSON) + var c := _make_client(transport) + # Act + c.call("request_sync_from_server") + # Assert + assert_that(c.call("display_name_for", "scrap_metal_bulk")).is_equal("Scrap Metal (Bulk)") + + func test_http_error_does_not_emit_definitions_ready() -> void: # Arrange var transport := MockHttpTransport.new() diff --git a/docs/decomposition/modules/module_dependency_register.md b/docs/decomposition/modules/module_dependency_register.md index 10ac879..3c80324 100644 --- a/docs/decomposition/modules/module_dependency_register.md +++ b/docs/decomposition/modules/module_dependency_register.md @@ -50,7 +50,7 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen **E3.M1 note:** Epic 3 **Slice 2** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-57](https://linear.app/neon-sprawl/issue/NEO-57) → [NEO-64](https://linear.app/neon-sprawl/issue/NEO-64); label **`E3.M1`**. See [E3M1-prototype-backlog.md](../../plans/E3M1-prototype-backlog.md), [E3_M1_ResourceNodeAndGatherLoop.md](E3_M1_ResourceNodeAndGatherLoop.md). **NEO-57** (content + CI), **NEO-58** (server fail-fast load), **NEO-59** (`IResourceNodeDefinitionRegistry` + DI), **NEO-60** (`GET /game/world/resource-node-definitions`), **NEO-61** (`IResourceNodeInstanceStore` + depletion operations, `V006`), **NEO-62** (`GatherOperations` + `GatherResult`), **NEO-63** (interact → gather engine + four registry anchors + Bruno), **NEO-64** (comment-only **`resource_gathered`** / **`gather_node_depleted`** telemetry hook sites in `GatherOperations.TryGather`) — E3M1-01–**E3M1-08** complete; register row **Ready**. -**E3.M3 note:** Epic 3 **Slice 1** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-50](https://linear.app/neon-sprawl/issue/NEO-50) → [NEO-56](https://linear.app/neon-sprawl/issue/NEO-56); label **`E3.M3`**. See [E3M3-prototype-backlog.md](../../plans/E3M3-prototype-backlog.md), [E3_M3_ItemizationAndInventorySchema.md](E3_M3_ItemizationAndInventorySchema.md). **NEO-50** (content + CI), **NEO-51** (server fail-fast load), **NEO-52** (`IItemDefinitionRegistry` + DI), **NEO-53** (`GET /game/world/item-definitions`), **NEO-54** (inventory store + stack/slot rules engine), **NEO-55** (`GET`/`POST /game/players/{id}/inventory`), and **NEO-56** (comment-only `item_created` / `inventory_transfer_denied` telemetry hook sites in `PlayerInventoryOperations`) — Epic 3 Slice 1 backlog **E3M3-01**–**E3M3-07** complete; register row **In Progress** until Slice 2+; later slices update the alignment table as they land. +**E3.M3 note:** Epic 3 **Slice 1** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-50](https://linear.app/neon-sprawl/issue/NEO-50) → [NEO-56](https://linear.app/neon-sprawl/issue/NEO-56); label **`E3.M3`**. See [E3M3-prototype-backlog.md](../../plans/E3M3-prototype-backlog.md), [E3_M3_ItemizationAndInventorySchema.md](E3_M3_ItemizationAndInventorySchema.md). **NEO-50** (content + CI), **NEO-51** (server fail-fast load), **NEO-52** (`IItemDefinitionRegistry` + DI), **NEO-53** (`GET /game/world/item-definitions`), **NEO-54** (inventory store + stack/slot rules engine), **NEO-55** (`GET`/`POST /game/players/{id}/inventory`), and **NEO-56** (comment-only `item_created` / `inventory_transfer_denied` telemetry hook sites in `PlayerInventoryOperations`) — Epic 3 Slice 1 backlog **E3M3-01**–**E3M3-07** complete; **NEO-72** (client inventory HUD — `inventory_client.gd`, `item_definitions_client.gd`, `InventoryLabel`; [`NEO-72` manual QA](../../manual-qa/NEO-72.md)) — Epic 3 Slice 5 **E3S5-01** client landed; register row **In Progress** until Slice 2+; later slices update the alignment table as they land. **E3.M2 note:** Epic 3 **Slice 3** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-65](https://linear.app/neon-sprawl/issue/NEO-65) → [NEO-71](https://linear.app/neon-sprawl/issue/NEO-71); label **`E3.M2`**. See [E3M2-prototype-backlog.md](../../plans/E3M2-prototype-backlog.md), [E3_M2_RefinementAndRecipeExecution.md](E3_M2_RefinementAndRecipeExecution.md). **NEO-42** (refine XP prep helper) is **Done**; **NEO-65**–**NEO-71** complete — **NEO-71** (comment-only **`item_crafted`** / **`craft_failed`** telemetry hook sites in `CraftOperations.TryCraft`) — E3M2-01–**E3M2-07** complete; register row **Ready**. Upstream: E3.M1 **Ready**, E3.M3 inventory landed.