NEO-99: update GdUnit tests for district visuals and prototype_npc_* targeting.
Add visual catalog transform tests and refresh target selection, marker, and combat client suites for PrototypeNpcRegistry ids from NEO-91.pull/134/head
parent
fa3d48a6aa
commit
ab7033e85a
|
|
@ -104,12 +104,12 @@ func test_request_cast_posts_target_id_string_when_set() -> void:
|
|||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"schemaVersion":1,"accepted":true}')
|
||||
var c := _make_client(transport)
|
||||
# Act
|
||||
var started: bool = bool(c.call("request_cast", 0, "prototype_pulse", "prototype_target_alpha"))
|
||||
var started: bool = bool(c.call("request_cast", 0, "prototype_pulse", "prototype_npc_melee"))
|
||||
# Assert
|
||||
assert_that(started).is_true()
|
||||
var parsed: Variant = JSON.parse_string(transport.last_body)
|
||||
var body: Dictionary = parsed
|
||||
assert_that(body.get("targetId")).is_equal("prototype_target_alpha")
|
||||
assert_that(body.get("targetId")).is_equal("prototype_npc_melee")
|
||||
|
||||
|
||||
func test_request_cast_returns_false_when_http_request_fails_to_start() -> void:
|
||||
|
|
@ -157,7 +157,7 @@ func test_cast_result_received_emits_false_with_reason_on_denied() -> void:
|
|||
got.append([accepted, reason, resolution])
|
||||
)
|
||||
# Act
|
||||
var started: bool = bool(c.call("request_cast", 0, "prototype_pulse", "prototype_target_alpha"))
|
||||
var started: bool = bool(c.call("request_cast", 0, "prototype_pulse", "prototype_npc_melee"))
|
||||
# Assert
|
||||
assert_that(started).is_true()
|
||||
assert_that(got.size()).is_equal(1)
|
||||
|
|
@ -195,7 +195,7 @@ func test_cast_result_received_emits_combat_resolution_on_accept() -> void:
|
|||
200,
|
||||
(
|
||||
'{"schemaVersion":1,"accepted":true,"combatResolution":'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_target_alpha",'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_npc_melee",'
|
||||
+ '"damageDealt":25,"targetRemainingHp":75,"targetDefeated":false}}'
|
||||
)
|
||||
)
|
||||
|
|
@ -207,7 +207,7 @@ func test_cast_result_received_emits_combat_resolution_on_accept() -> void:
|
|||
got.append([accepted, reason, resolution])
|
||||
)
|
||||
# Act
|
||||
var started: bool = bool(c.call("request_cast", 0, "prototype_pulse", "prototype_target_alpha"))
|
||||
var started: bool = bool(c.call("request_cast", 0, "prototype_pulse", "prototype_npc_melee"))
|
||||
# Assert
|
||||
assert_that(started).is_true()
|
||||
assert_that(got.size()).is_equal(1)
|
||||
|
|
@ -222,7 +222,7 @@ func test_parse_cast_response_json_maps_combat_resolution() -> void:
|
|||
# Arrange
|
||||
var body := (
|
||||
'{"schemaVersion":1,"accepted":true,"combatResolution":'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_target_alpha",'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_npc_melee",'
|
||||
+ '"damageDealt":25,"targetRemainingHp":75,"targetDefeated":false}}'
|
||||
)
|
||||
# Act
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class MockCastTransport:
|
|||
)
|
||||
var body_json: String = (
|
||||
'{"schemaVersion":1,"accepted":true,"combatResolution":'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_target_alpha",'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_npc_melee",'
|
||||
+ '"damageDealt":25,"targetRemainingHp":75,"targetDefeated":false}}'
|
||||
)
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ class CombatRefreshHarness:
|
|||
return ""
|
||||
|
||||
func request_cast() -> void:
|
||||
_cast.call("request_cast", 0, "prototype_pulse", "prototype_target_alpha")
|
||||
_cast.call("request_cast", 0, "prototype_pulse", "prototype_npc_melee")
|
||||
|
||||
|
||||
func test_successful_cast_triggers_combat_target_sync() -> void:
|
||||
|
|
@ -106,7 +106,7 @@ func test_target_lock_change_triggers_combat_target_sync() -> void:
|
|||
harness.setup(self, transport)
|
||||
# Act
|
||||
harness.on_target_state_changed(
|
||||
{"lockedTargetId": "prototype_target_alpha", "validity": "ok", "sequence": 1}
|
||||
{"lockedTargetId": "prototype_npc_melee", "validity": "ok", "sequence": 1}
|
||||
)
|
||||
# Assert
|
||||
assert_that(harness.combat_sync_calls).is_equal(1)
|
||||
|
|
@ -122,11 +122,11 @@ func test_target_validity_only_update_skips_combat_target_sync() -> void:
|
|||
add_child(harness)
|
||||
harness.setup(self, transport)
|
||||
harness.on_target_state_changed(
|
||||
{"lockedTargetId": "prototype_target_alpha", "validity": "ok", "sequence": 1}
|
||||
{"lockedTargetId": "prototype_npc_melee", "validity": "ok", "sequence": 1}
|
||||
)
|
||||
# Act
|
||||
harness.on_target_state_changed(
|
||||
{"lockedTargetId": "prototype_target_alpha", "validity": "out_of_range", "sequence": 2}
|
||||
{"lockedTargetId": "prototype_npc_melee", "validity": "out_of_range", "sequence": 2}
|
||||
)
|
||||
# Assert
|
||||
assert_that(harness.combat_sync_calls).is_equal(1)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ class MockHttpTransport:
|
|||
static func _two_target_snapshot_json() -> String:
|
||||
return (
|
||||
'{"schemaVersion":1,"targets":['
|
||||
+ '{"targetId":"prototype_target_alpha","maxHp":100,"currentHp":75,"defeated":false},'
|
||||
+ '{"targetId":"prototype_target_beta","maxHp":100,"currentHp":100,"defeated":false}'
|
||||
+ '{"targetId":"prototype_npc_melee","maxHp":100,"currentHp":75,"defeated":false},'
|
||||
+ '{"targetId":"prototype_npc_ranged","maxHp":80,"currentHp":80,"defeated":false},'
|
||||
+ '{"targetId":"prototype_npc_elite","maxHp":200,"currentHp":200,"defeated":false}'
|
||||
+ "]}"
|
||||
)
|
||||
|
||||
|
|
@ -86,7 +87,7 @@ func test_target_row_returns_alpha_hp_fields() -> void:
|
|||
c.call("request_sync_from_server")
|
||||
var snapshot: Dictionary = got[0]
|
||||
# Act
|
||||
var row: Dictionary = c.call("target_row", "prototype_target_alpha", snapshot) as Dictionary
|
||||
var row: Dictionary = c.call("target_row", "prototype_npc_melee", snapshot) as Dictionary
|
||||
# Assert
|
||||
assert_that(int(row.get("currentHp", 0))).is_equal(75)
|
||||
assert_that(int(row.get("maxHp", 0))).is_equal(100)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class GigRefreshHarness:
|
|||
_gig.request_sync_from_server()
|
||||
|
||||
func request_cast() -> void:
|
||||
_cast.call("request_cast", 0, "prototype_pulse", "prototype_target_alpha")
|
||||
_cast.call("request_cast", 0, "prototype_pulse", "prototype_npc_melee")
|
||||
|
||||
|
||||
func test_defeat_cast_triggers_gig_progression_sync() -> void:
|
||||
|
|
@ -65,7 +65,7 @@ func test_defeat_cast_triggers_gig_progression_sync() -> void:
|
|||
var transport := MockCastTransport.new()
|
||||
transport.body_json = (
|
||||
'{"schemaVersion":1,"accepted":true,"combatResolution":'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_target_alpha",'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_npc_melee",'
|
||||
+ '"damageDealt":25,"targetRemainingHp":0,"targetDefeated":true}}'
|
||||
)
|
||||
var harness := GigRefreshHarness.new()
|
||||
|
|
@ -87,7 +87,7 @@ func test_non_defeat_cast_skips_gig_progression_sync() -> void:
|
|||
var transport := MockCastTransport.new()
|
||||
transport.body_json = (
|
||||
'{"schemaVersion":1,"accepted":true,"combatResolution":'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_target_alpha",'
|
||||
+ '{"abilityId":"prototype_pulse","targetId":"prototype_npc_melee",'
|
||||
+ '"damageDealt":25,"targetRemainingHp":75,"targetDefeated":false}}'
|
||||
)
|
||||
var harness := GigRefreshHarness.new()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ func test_resolve_returns_ok_with_target_from_locked_target_id() -> void:
|
|||
# Arrange
|
||||
var slots := _eight_empty_slots()
|
||||
slots[0] = "prototype_pulse"
|
||||
var target_state := {"lockedTargetId": "prototype_target_alpha", "sequence": 3}
|
||||
var target_state := {"lockedTargetId": "prototype_npc_melee", "sequence": 3}
|
||||
|
||||
# Act
|
||||
var outcome: Dictionary = Resolver.resolve(0, slots, target_state)
|
||||
|
|
@ -24,7 +24,7 @@ func test_resolve_returns_ok_with_target_from_locked_target_id() -> void:
|
|||
assert_that(bool(outcome.get(Resolver.KEY_OK, false))).is_true()
|
||||
assert_that(int(outcome.get(Resolver.KEY_SLOT_INDEX, -1))).is_equal(0)
|
||||
assert_that(outcome.get(Resolver.KEY_ABILITY_ID)).is_equal("prototype_pulse")
|
||||
assert_that(outcome.get(Resolver.KEY_TARGET_ID)).is_equal("prototype_target_alpha")
|
||||
assert_that(outcome.get(Resolver.KEY_TARGET_ID)).is_equal("prototype_npc_melee")
|
||||
|
||||
|
||||
func test_resolve_returns_null_target_when_no_lock() -> void:
|
||||
|
|
@ -59,7 +59,7 @@ func test_resolve_denies_empty_slot_without_ability() -> void:
|
|||
# Arrange
|
||||
var slots := _eight_empty_slots()
|
||||
slots[3] = null
|
||||
var target_state := {"lockedTargetId": "prototype_target_beta"}
|
||||
var target_state := {"lockedTargetId": "prototype_npc_ranged"}
|
||||
|
||||
# Act
|
||||
var outcome: Dictionary = Resolver.resolve(3, slots, target_state)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
extends GdUnitTestSuite
|
||||
|
||||
const Catalog := preload("res://scripts/prototype_district_visual_catalog.gd")
|
||||
|
||||
|
||||
func test_instantiate_prop_clears_baked_child_transform() -> void:
|
||||
# Arrange
|
||||
var packed: PackedScene = Catalog.INTERACTABLE_PROPS["prototype_resource_node_alpha"]
|
||||
# Act
|
||||
var visual: Node3D = Catalog.instantiate_prop(packed, 0.5)
|
||||
# Assert
|
||||
var mesh: Node3D = visual.get_node("IX_SalvageAlpha_Mesh") as Node3D
|
||||
assert_that(mesh.position).is_equal(Vector3.ZERO)
|
||||
|
||||
|
||||
func test_normalize_prop_visual_grounds_mesh_feet_at_floor() -> void:
|
||||
# Arrange
|
||||
var packed: PackedScene = Catalog.INTERACTABLE_PROPS["prototype_resource_node_alpha"]
|
||||
var visual: Node3D = packed.instantiate() as Node3D
|
||||
var mesh: MeshInstance3D = visual.get_node("IX_SalvageAlpha_Mesh") as MeshInstance3D
|
||||
var min_y: float = mesh.get_aabb().position.y
|
||||
# Act
|
||||
Catalog.normalize_prop_visual(visual, 0.5)
|
||||
# Assert
|
||||
assert_float(visual.position.y).is_equal_approx(-0.5 - min_y, 0.0001)
|
||||
|
|
@ -0,0 +1 @@
|
|||
uid://c5m8p2n7k4w9x
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
extends GdUnitTestSuite
|
||||
|
||||
## NEO-85: prototype target display helpers for combat dummy markers and HUD copy.
|
||||
## NEO-85 / NEO-91: prototype NPC display helpers for combat markers and HUD copy.
|
||||
|
||||
const PrototypeTargetConstants := preload("res://scripts/prototype_target_constants.gd")
|
||||
|
||||
|
||||
func test_display_short_name_maps_alpha_and_beta() -> void:
|
||||
func test_display_short_name_maps_npc_archetypes() -> void:
|
||||
# Arrange
|
||||
# Act
|
||||
var alpha: String = PrototypeTargetConstants.display_short_name("prototype_target_alpha")
|
||||
var beta: String = PrototypeTargetConstants.display_short_name("prototype_target_beta")
|
||||
var elite: String = PrototypeTargetConstants.display_short_name("prototype_npc_elite")
|
||||
var melee: String = PrototypeTargetConstants.display_short_name("prototype_npc_melee")
|
||||
var ranged: String = PrototypeTargetConstants.display_short_name("prototype_npc_ranged")
|
||||
# Assert
|
||||
assert_that(alpha).is_equal("Dummy α")
|
||||
assert_that(beta).is_equal("Dummy β")
|
||||
assert_that(elite).is_equal("Elite")
|
||||
assert_that(melee).is_equal("Melee")
|
||||
assert_that(ranged).is_equal("Ranged")
|
||||
|
||||
|
||||
func test_display_short_name_falls_back_to_id() -> void:
|
||||
|
|
@ -21,3 +23,13 @@ func test_display_short_name_falls_back_to_id() -> void:
|
|||
var unknown: String = PrototypeTargetConstants.display_short_name("unknown_target")
|
||||
# Assert
|
||||
assert_that(unknown).is_equal("unknown_target")
|
||||
|
||||
|
||||
func test_ordered_ids_matches_server_tab_cycle() -> void:
|
||||
# Arrange
|
||||
# Act
|
||||
var ids: Array[String] = PrototypeTargetConstants.ordered_ids()
|
||||
# Assert
|
||||
assert_that(ids).is_equal(
|
||||
["prototype_npc_elite", "prototype_npc_melee", "prototype_npc_ranged"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,20 +4,32 @@ extends GdUnitTestSuite
|
|||
|
||||
const Markers := preload("res://scripts/prototype_target_markers.gd")
|
||||
|
||||
const ALPHA_ID := "prototype_target_alpha"
|
||||
const MELEE_ID := "prototype_npc_melee"
|
||||
|
||||
|
||||
func test_set_locked_target_brightens_body_albedo_without_emission() -> void:
|
||||
func test_set_locked_target_brightens_ring_without_emission() -> void:
|
||||
# Arrange
|
||||
var markers: Node3D = Markers.new()
|
||||
auto_free(markers)
|
||||
add_child(markers)
|
||||
await get_tree().process_frame
|
||||
var body: MeshInstance3D = markers.get_node("Marker_%s/DummyBody" % ALPHA_ID) as MeshInstance3D
|
||||
var body_mat: StandardMaterial3D = body.material_override as StandardMaterial3D
|
||||
var idle_color: Color = body_mat.albedo_color
|
||||
var ring: MeshInstance3D = markers.get_node("Marker_%s/RadiusRing" % MELEE_ID) as MeshInstance3D
|
||||
var ring_mat: StandardMaterial3D = ring.material_override as StandardMaterial3D
|
||||
var idle_color: Color = ring_mat.albedo_color
|
||||
# Act
|
||||
markers.set_locked_target(ALPHA_ID)
|
||||
markers.set_locked_target(MELEE_ID)
|
||||
# Assert
|
||||
assert_that(body_mat.albedo_color).is_not_equal(idle_color)
|
||||
assert_that(body_mat.emission_enabled).is_false()
|
||||
assert_that(ring_mat.albedo_color).is_not_equal(idle_color)
|
||||
assert_that(ring_mat.emission_enabled).is_false()
|
||||
|
||||
|
||||
func test_spawn_marker_uses_imported_prop_visual() -> void:
|
||||
# Arrange
|
||||
var markers: Node3D = Markers.new()
|
||||
auto_free(markers)
|
||||
add_child(markers)
|
||||
await get_tree().process_frame
|
||||
# Act
|
||||
var visual: Node = markers.get_node("Marker_%s/DummyBody/PropVisual" % MELEE_ID)
|
||||
# Assert
|
||||
assert_that(visual).is_not_null()
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ extends GdUnitTestSuite
|
|||
|
||||
const TargetSelectionTestDouble := preload("res://test/target_selection_test_double.gd")
|
||||
|
||||
const ALPHA_ID := "prototype_target_alpha"
|
||||
const BETA_ID := "prototype_target_beta"
|
||||
const ELITE_ID := "prototype_npc_elite"
|
||||
const MELEE_ID := "prototype_npc_melee"
|
||||
const RANGED_ID := "prototype_npc_ranged"
|
||||
|
||||
|
||||
class MockHttpTransport:
|
||||
|
|
@ -74,7 +75,7 @@ func test_tab_lock_change_emits_tab() -> void:
|
|||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
var events: Array = []
|
||||
|
|
@ -86,7 +87,7 @@ func test_tab_lock_change_emits_tab() -> void:
|
|||
var last: Dictionary = events[0] as Dictionary
|
||||
assert_that(last.get("cause", "")).is_equal("tab")
|
||||
assert_that(last.get("previous")).is_null()
|
||||
assert_that(last.get("next")).is_equal(ALPHA_ID)
|
||||
assert_that(last.get("next")).is_equal(MELEE_ID)
|
||||
assert_that(int(last.get("sequence", -1))).is_equal(1)
|
||||
|
||||
|
||||
|
|
@ -94,7 +95,7 @@ func test_clear_lock_change_emits_clear() -> void:
|
|||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, null, "none", 2))
|
||||
var c := _make_client(transport)
|
||||
|
|
@ -107,7 +108,7 @@ func test_clear_lock_change_emits_clear() -> void:
|
|||
assert_that(events.size()).is_equal(2)
|
||||
assert_that(int((events[0] as Dictionary).get("sequence", -1))).is_equal(1)
|
||||
assert_that((events[1] as Dictionary).get("cause", "")).is_equal("clear")
|
||||
assert_that((events[1] as Dictionary).get("previous")).is_equal(ALPHA_ID)
|
||||
assert_that((events[1] as Dictionary).get("previous")).is_equal(MELEE_ID)
|
||||
assert_that((events[1] as Dictionary).get("next")).is_null()
|
||||
assert_that(int((events[1] as Dictionary).get("sequence", -1))).is_equal(2)
|
||||
|
||||
|
|
@ -116,9 +117,9 @@ func test_get_lock_id_change_emits_server_correction() -> void:
|
|||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(BETA_ID, "ok", 2))
|
||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(RANGED_ID, "ok", 2))
|
||||
var c := _make_client(transport)
|
||||
var events: Array = []
|
||||
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
||||
|
|
@ -129,8 +130,8 @@ func test_get_lock_id_change_emits_server_correction() -> void:
|
|||
assert_that(events.size()).is_equal(2)
|
||||
var last: Dictionary = events[1] as Dictionary
|
||||
assert_that(last.get("cause", "")).is_equal("server_correction")
|
||||
assert_that(last.get("previous")).is_equal(ALPHA_ID)
|
||||
assert_that(last.get("next")).is_equal(BETA_ID)
|
||||
assert_that(last.get("previous")).is_equal(MELEE_ID)
|
||||
assert_that(last.get("next")).is_equal(RANGED_ID)
|
||||
assert_that(int(last.get("sequence", -1))).is_equal(2)
|
||||
|
||||
|
||||
|
|
@ -138,10 +139,10 @@ func test_get_validity_only_change_emits_nothing() -> void:
|
|||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(ALPHA_ID, "out_of_range", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(MELEE_ID, "out_of_range", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
var events: Array = []
|
||||
|
|
@ -167,6 +168,6 @@ func test_denial_without_lock_id_change_emits_nothing() -> void:
|
|||
var events: Array = []
|
||||
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
||||
# Act
|
||||
c.request_select_target_id(ALPHA_ID)
|
||||
c.request_select_target_id(MELEE_ID)
|
||||
# Assert
|
||||
assert_that(events.size()).is_equal(0)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ extends GdUnitTestSuite
|
|||
const PositionAuthorityTestDouble := preload("res://test/position_authority_test_double.gd")
|
||||
const TargetSelectionTestDouble := preload("res://test/target_selection_test_double.gd")
|
||||
|
||||
const ALPHA_ID := "prototype_target_alpha"
|
||||
const ALPHA_ID := "prototype_npc_melee"
|
||||
|
||||
|
||||
# Mirrors the transports from the per-client suites: a queue-backed `Node` with a
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ extends GdUnitTestSuite
|
|||
|
||||
const TargetSelectionTestDouble := preload("res://test/target_selection_test_double.gd")
|
||||
|
||||
const ALPHA_ID := "prototype_target_alpha"
|
||||
const BETA_ID := "prototype_target_beta"
|
||||
const ELITE_ID := "prototype_npc_elite"
|
||||
const MELEE_ID := "prototype_npc_melee"
|
||||
const RANGED_ID := "prototype_npc_ranged"
|
||||
|
||||
|
||||
# In-process transport: not an HTTPRequest subclass (Godot 4 will not call script `request()`
|
||||
|
|
@ -101,7 +102,7 @@ func test_tab_from_no_lock_selects_first_id() -> void:
|
|||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ELITE_ID, "ok", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
monitor_signals(c)
|
||||
|
|
@ -110,49 +111,53 @@ func test_tab_from_no_lock_selects_first_id() -> void:
|
|||
# Assert
|
||||
assert_that(transport.last_url).contains("/target/select")
|
||||
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ELITE_ID)
|
||||
await assert_signal(c).is_emitted("target_state_changed", any())
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ELITE_ID)
|
||||
|
||||
|
||||
func test_tab_from_alpha_requests_beta() -> void:
|
||||
func test_tab_from_elite_requests_melee() -> void:
|
||||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ELITE_ID, "ok", 1)
|
||||
)
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, BETA_ID, "ok", 2)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 2)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
# Act
|
||||
c.request_tab_next()
|
||||
c.request_tab_next()
|
||||
# Assert
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % MELEE_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(MELEE_ID)
|
||||
|
||||
|
||||
func test_tab_wraps_from_beta_to_alpha() -> void:
|
||||
func test_tab_wraps_from_ranged_back_to_elite() -> void:
|
||||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ELITE_ID, "ok", 1)
|
||||
)
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, BETA_ID, "ok", 2)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 2)
|
||||
)
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 3)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, RANGED_ID, "ok", 3)
|
||||
)
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ELITE_ID, "ok", 4)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
# Act
|
||||
c.request_tab_next()
|
||||
c.request_tab_next()
|
||||
c.request_tab_next()
|
||||
c.request_tab_next()
|
||||
# Assert
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ELITE_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ELITE_ID)
|
||||
|
||||
|
||||
func test_denial_reflects_authoritative_target_state() -> void:
|
||||
|
|
@ -166,7 +171,7 @@ func test_denial_reflects_authoritative_target_state() -> void:
|
|||
)
|
||||
var c := _make_client(transport)
|
||||
# Act
|
||||
c.request_select_target_id(ALPHA_ID)
|
||||
c.request_select_target_id(MELEE_ID)
|
||||
# Assert
|
||||
var state: Dictionary = c.cached_state()
|
||||
assert_that(bool(state.get("selectionApplied", true))).is_false()
|
||||
|
|
@ -192,21 +197,21 @@ func test_authoritative_ack_while_locked_fires_refresh_get() -> void:
|
|||
var transport := MockHttpTransport.new()
|
||||
# Step 1: lock alpha via POST.
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
# Step 2: movement-triggered GET shows out_of_range (soft lock).
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(ALPHA_ID, "out_of_range", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(MELEE_ID, "out_of_range", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
# Act
|
||||
c.request_select_target_id(ALPHA_ID)
|
||||
c.request_select_target_id(MELEE_ID)
|
||||
var count_before_ack: int = transport.request_count
|
||||
c.on_authoritative_ack(Vector3.ZERO)
|
||||
# Assert
|
||||
assert_that(transport.request_count).is_equal(count_before_ack + 1)
|
||||
assert_that(c.cached_state().get("validity")).is_equal("out_of_range")
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(MELEE_ID)
|
||||
|
||||
|
||||
func test_authoritative_ack_without_lock_fires_nothing() -> void:
|
||||
|
|
@ -223,14 +228,14 @@ func test_two_acks_within_cooldown_collapse_to_one_get() -> void:
|
|||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(ALPHA_ID, "out_of_range", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(MELEE_ID, "out_of_range", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
# Act
|
||||
c.request_select_target_id(ALPHA_ID)
|
||||
c.request_select_target_id(MELEE_ID)
|
||||
var count_before: int = transport.request_count
|
||||
c.on_authoritative_ack(Vector3.ZERO)
|
||||
c.on_authoritative_ack(Vector3.ZERO)
|
||||
|
|
@ -262,7 +267,7 @@ func test_select_post_kicks_freshness_stream_before_posting() -> void:
|
|||
# Arrange
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
var authority := FreshnessAuthorityStub.new()
|
||||
|
|
@ -271,7 +276,7 @@ func test_select_post_kicks_freshness_stream_before_posting() -> void:
|
|||
var player := _make_player(Vector3(1.5, 0.5, -2.25))
|
||||
c.set_freshness_kick(authority, player)
|
||||
# Act
|
||||
c.request_select_target_id(ALPHA_ID)
|
||||
c.request_select_target_id(MELEE_ID)
|
||||
# Assert
|
||||
# Exactly one freshness submit, matching the pre-POST capsule position.
|
||||
assert_that(authority.submit_count).is_equal(1)
|
||||
|
|
@ -287,7 +292,7 @@ func test_select_post_includes_position_hint_when_player_wired() -> void:
|
|||
# `positionHint` so the server's range check can bypass any stale stored snap.
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, BETA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, RANGED_ID, "ok", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
var authority := FreshnessAuthorityStub.new()
|
||||
|
|
@ -298,12 +303,12 @@ func test_select_post_includes_position_hint_when_player_wired() -> void:
|
|||
var player := _make_player(Vector3(3.0, 0.5, 5.0))
|
||||
c.set_freshness_kick(authority, player)
|
||||
# Act
|
||||
c.request_select_target_id(BETA_ID)
|
||||
c.request_select_target_id(RANGED_ID)
|
||||
# Assert
|
||||
var parsed: Variant = JSON.parse_string(transport.last_body)
|
||||
assert_that(parsed is Dictionary).is_true()
|
||||
var body: Dictionary = parsed
|
||||
assert_that(body.get("targetId")).is_equal(BETA_ID)
|
||||
assert_that(body.get("targetId")).is_equal(RANGED_ID)
|
||||
assert_that(body.has("positionHint")).is_true()
|
||||
var hint: Dictionary = body["positionHint"]
|
||||
assert_float(hint.get("x")).is_equal_approx(3.0, 0.0001)
|
||||
|
|
@ -317,11 +322,11 @@ func test_select_post_without_freshness_wiring_is_a_noop() -> void:
|
|||
# still POST normally, *without* a `positionHint` (hint is opt-in per wiring).
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
# Act
|
||||
c.request_select_target_id(ALPHA_ID)
|
||||
c.request_select_target_id(MELEE_ID)
|
||||
# Assert
|
||||
assert_that(transport.last_url).contains("/target/select")
|
||||
# No `positionHint` key in the body when no player is wired — re-parse so the assertion
|
||||
|
|
@ -329,29 +334,29 @@ func test_select_post_without_freshness_wiring_is_a_noop() -> void:
|
|||
var parsed: Variant = JSON.parse_string(transport.last_body)
|
||||
assert_that(parsed is Dictionary).is_true()
|
||||
assert_that((parsed as Dictionary).has("positionHint")).is_false()
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(MELEE_ID)
|
||||
|
||||
|
||||
func test_tab_from_no_lock_skips_out_of_range_and_picks_beta() -> void:
|
||||
func test_tab_from_no_lock_skips_out_of_range_and_picks_ranged() -> void:
|
||||
# Arrange
|
||||
# Player stands next to beta (well outside alpha's ring). Without the range-aware
|
||||
# tab cycle the server denies with `out_of_range` on alpha — see NEO-24 follow-up #3.
|
||||
# Player stands next to ranged only (elite/melee rings are out of reach). Without the
|
||||
# range-aware tab cycle the server denies on the first id in cycle order.
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, BETA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, RANGED_ID, "ok", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
var authority := FreshnessAuthorityStub.new()
|
||||
auto_free(authority)
|
||||
add_child(authority)
|
||||
# Near beta anchor (3, 3), ~1.4 m horizontal — inside 6 m. Far from alpha at (-3, -3).
|
||||
var player := _make_player(Vector3(4.0, 0.5, 4.0))
|
||||
# Near ranged anchor (3, 3), inside 6 m; elite at origin and melee at (-3, -3) are out.
|
||||
var player := _make_player(Vector3(7.0, 0.5, 7.0))
|
||||
c.set_freshness_kick(authority, player)
|
||||
# Act
|
||||
c.request_tab_next()
|
||||
# Assert
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % RANGED_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(RANGED_ID)
|
||||
|
||||
|
||||
func test_tab_from_no_lock_with_nothing_in_range_falls_back_to_first_id() -> void:
|
||||
|
|
@ -372,21 +377,21 @@ func test_tab_from_no_lock_with_nothing_in_range_falls_back_to_first_id() -> voi
|
|||
# Act
|
||||
c.request_tab_next()
|
||||
# Assert
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ELITE_ID)
|
||||
assert_that(c.cached_state().get("reasonCode", "")).is_equal("out_of_range")
|
||||
|
||||
|
||||
func test_tab_from_locked_alpha_at_origin_swaps_to_beta_when_both_in_range() -> void:
|
||||
func test_tab_from_locked_elite_at_origin_swaps_to_melee_when_both_in_range() -> void:
|
||||
# Arrange
|
||||
# With alpha (-3,-3) and beta (3,3) sharing r=6, origin is in both rings. Locked to
|
||||
# alpha, Tab should request beta (normal cycle behavior; range-aware skip-current
|
||||
# does not interfere when the next id is also in range).
|
||||
# With elite (0,0), melee (-3,-3), and ranged (3,3) sharing r=6, origin is in elite/melee
|
||||
# rings. Locked to elite, Tab should request melee (normal cycle behavior; range-aware
|
||||
# skip-current does not interfere when the next id is also in range).
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ELITE_ID, "ok", 1)
|
||||
)
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, BETA_ID, "ok", 2)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 2)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
var authority := FreshnessAuthorityStub.new()
|
||||
|
|
@ -398,8 +403,8 @@ func test_tab_from_locked_alpha_at_origin_swaps_to_beta_when_both_in_range() ->
|
|||
c.request_tab_next()
|
||||
c.request_tab_next()
|
||||
# Assert
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % MELEE_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(MELEE_ID)
|
||||
|
||||
|
||||
func test_tab_when_only_current_lock_is_in_range_falls_back_to_cycle_for_denial() -> void:
|
||||
|
|
@ -410,39 +415,39 @@ func test_tab_when_only_current_lock_is_in_range_falls_back_to_cycle_for_denial(
|
|||
# visible reaction to Tab.
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1)
|
||||
)
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS,
|
||||
200,
|
||||
_select_response_json(false, ALPHA_ID, "ok", 1, "out_of_range")
|
||||
_select_response_json(false, MELEE_ID, "ok", 1, "out_of_range")
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
var authority := FreshnessAuthorityStub.new()
|
||||
auto_free(authority)
|
||||
add_child(authority)
|
||||
# Near alpha (-3, -3), ~1.4 m in — beta (3, 3) is ~8.5 m out.
|
||||
var player := _make_player(Vector3(-2.0, 0.5, -2.0))
|
||||
# Near melee (-3, -3), inside its ring — elite at origin and ranged at (3, 3) are out.
|
||||
var player := _make_player(Vector3(-8.0, 0.5, -3.0))
|
||||
c.set_freshness_kick(authority, player)
|
||||
# Act
|
||||
c.request_select_target_id(ALPHA_ID)
|
||||
c.request_select_target_id(MELEE_ID)
|
||||
c.request_tab_next()
|
||||
# Assert
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % RANGED_ID)
|
||||
assert_that(c.cached_state().get("reasonCode", "")).is_equal("out_of_range")
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(MELEE_ID)
|
||||
|
||||
|
||||
func test_tab_without_player_ref_still_uses_plain_cycle_order() -> void:
|
||||
# Arrange
|
||||
# Regression: headless unit tests and any client that skips `set_freshness_kick(...)`
|
||||
# must keep the pre-existing cycle-order semantics. Alpha is requested from no-lock.
|
||||
# must keep the pre-existing cycle-order semantics. Elite is requested from no-lock.
|
||||
var transport := MockHttpTransport.new()
|
||||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ELITE_ID, "ok", 1)
|
||||
)
|
||||
var c := _make_client(transport)
|
||||
# Act
|
||||
c.request_tab_next()
|
||||
# Assert
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ELITE_ID)
|
||||
|
|
|
|||
Loading…
Reference in New Issue