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.pull/106/head
parent
15fcc09493
commit
e7a61a1b3f
|
|
@ -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.
|
**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.
|
**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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ static func parse_inventory_json(text: String) -> Variant:
|
||||||
|
|
||||||
|
|
||||||
func _on_request_completed(
|
func _on_request_completed(
|
||||||
result: int, response_code: int, _headers: PackedByteArray, body: PackedByteArray
|
result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray
|
||||||
) -> void:
|
) -> void:
|
||||||
_busy = false
|
_busy = false
|
||||||
if result != HTTPRequest.RESULT_SUCCESS:
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ func _base_root() -> String:
|
||||||
|
|
||||||
|
|
||||||
func _on_request_completed(
|
func _on_request_completed(
|
||||||
result: int, response_code: int, _headers: PackedByteArray, body: PackedByteArray
|
result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray
|
||||||
) -> void:
|
) -> void:
|
||||||
_busy = false
|
_busy = false
|
||||||
if result != HTTPRequest.RESULT_SUCCESS:
|
if result != HTTPRequest.RESULT_SUCCESS:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,12 @@ extends GdUnitTestSuite
|
||||||
|
|
||||||
const InventoryClient := preload("res://scripts/inventory_client.gd")
|
const InventoryClient := preload("res://scripts/inventory_client.gd")
|
||||||
|
|
||||||
|
var _inventory_capture: Dictionary = {}
|
||||||
|
|
||||||
|
|
||||||
|
func _capture_inventory(snapshot: Dictionary) -> void:
|
||||||
|
_inventory_capture = snapshot
|
||||||
|
|
||||||
|
|
||||||
class MockHttpTransport:
|
class MockHttpTransport:
|
||||||
extends Node
|
extends Node
|
||||||
|
|
@ -64,14 +70,19 @@ func test_parse_inventory_json_empty_grid() -> void:
|
||||||
|
|
||||||
func test_request_sync_emits_inventory_received() -> void:
|
func test_request_sync_emits_inventory_received() -> void:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
_inventory_capture = {}
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.body_json = _empty_inventory_json()
|
transport.body_json = _empty_inventory_json()
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
c.connect("inventory_received", Callable(self, "_capture_inventory"))
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
# Act
|
# Act
|
||||||
c.call("request_sync_from_server")
|
c.call("request_sync_from_server")
|
||||||
# Assert
|
# Assert
|
||||||
assert_signal(c).is_emitted("inventory_received")
|
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")
|
assert_that(transport.last_url).contains("/inventory")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@ extends GdUnitTestSuite
|
||||||
|
|
||||||
const ItemDefsClient := preload("res://scripts/item_definitions_client.gd")
|
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:
|
class MockHttpTransport:
|
||||||
extends Node
|
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)")
|
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:
|
func test_request_sync_hits_definitions_endpoint_and_emits() -> void:
|
||||||
# Arrange
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
var body_json := (
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _SAMPLE_BODY_JSON)
|
||||||
'{"schemaVersion":1,"items":[{"id":"scrap_metal_bulk",'
|
|
||||||
+ '"displayName":"Scrap Metal (Bulk)","inventorySlotKind":"bag"}]}'
|
|
||||||
)
|
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, body_json)
|
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
# Act
|
# Act
|
||||||
|
|
@ -115,6 +87,17 @@ func test_request_sync_hits_definitions_endpoint_and_emits() -> void:
|
||||||
assert_signal(c).is_emitted("definitions_ready")
|
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:
|
func test_http_error_does_not_emit_definitions_ready() -> void:
|
||||||
# Arrange
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
|
|
|
||||||
|
|
@ -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.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.
|
**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.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue