neon-sprawl/client/test/combat_feedback_refresh_tes...

133 lines
4.0 KiB
GDScript

extends GdUnitTestSuite
## NEO-85: cast accept + target lock change trigger combat-target GET refresh.
const CastClient := preload("res://scripts/ability_cast_client.gd")
const CombatTargetsClient := preload("res://scripts/combat_targets_client.gd")
class MockCastTransport:
extends Node
signal request_completed(
result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray
)
var body_json: String = (
'{"schemaVersion":1,"accepted":true,"combatResolution":'
+ '{"abilityId":"prototype_pulse","targetId":"prototype_target_alpha",'
+ '"damageDealt":25,"targetRemainingHp":75,"targetDefeated":false}}'
)
func request(
_url: String,
_custom_headers: PackedStringArray = PackedStringArray(),
_method: HTTPClient.Method = HTTPClient.METHOD_POST,
_request_data: String = ""
) -> Error:
request_completed.emit(
HTTPRequest.RESULT_SUCCESS, 200, PackedStringArray(), body_json.to_utf8_buffer()
)
return OK
class SpyCombatTargetsClient:
extends Node
var sync_calls: int = 0
func request_sync_from_server() -> void:
sync_calls += 1
class CombatRefreshHarness:
extends Node
var combat_sync_calls: int = 0
var last_target_state: Dictionary = {}
var _cast: Node
var _combat: SpyCombatTargetsClient
func setup(parent: Node, cast_transport: Node) -> void:
_combat = SpyCombatTargetsClient.new()
_cast = CastClient.new()
_cast.set("injected_http", cast_transport)
parent.add_child(_cast)
parent.add_child(_combat)
_cast.connect("cast_result_received", Callable(self, "_on_cast_result"))
last_target_state = {}
func _on_cast_result(accepted: bool, _reason_code: String, _resolution: Dictionary) -> void:
if not accepted:
return
_request_combat_targets_refresh()
func _request_combat_targets_refresh() -> void:
combat_sync_calls += 1
_combat.request_sync_from_server()
func on_target_state_changed(state: Dictionary) -> void:
var prev_lock := _locked_target_id_from_state(last_target_state)
last_target_state = state.duplicate()
var new_lock := _locked_target_id_from_state(last_target_state)
if new_lock != prev_lock:
_request_combat_targets_refresh()
func _locked_target_id_from_state(state: Dictionary) -> String:
var locked_variant: Variant = state.get("lockedTargetId", null)
if locked_variant is String:
return (locked_variant as String).strip_edges()
return ""
func request_cast() -> void:
_cast.call("request_cast", 0, "prototype_pulse", "prototype_target_alpha")
func test_successful_cast_triggers_combat_target_sync() -> void:
# Arrange
var transport := MockCastTransport.new()
var harness := CombatRefreshHarness.new()
auto_free(transport)
auto_free(harness)
add_child(harness)
harness.setup(self, transport)
monitor_signals(harness._cast)
# Act
harness.request_cast()
# Assert
await assert_signal(harness._cast).is_emitted("cast_result_received", true, "", any())
assert_that(harness.combat_sync_calls).is_equal(1)
assert_that(harness._combat.sync_calls).is_equal(1)
func test_target_lock_change_triggers_combat_target_sync() -> void:
# Arrange
var transport := MockCastTransport.new()
var harness := CombatRefreshHarness.new()
auto_free(transport)
auto_free(harness)
add_child(harness)
harness.setup(self, transport)
# Act
harness.on_target_state_changed(
{"lockedTargetId": "prototype_target_alpha", "validity": "ok", "sequence": 1}
)
# Assert
assert_that(harness.combat_sync_calls).is_equal(1)
assert_that(harness._combat.sync_calls).is_equal(1)
func test_target_validity_only_update_skips_combat_target_sync() -> void:
# Arrange
var transport := MockCastTransport.new()
var harness := CombatRefreshHarness.new()
auto_free(transport)
auto_free(harness)
add_child(harness)
harness.setup(self, transport)
harness.on_target_state_changed(
{"lockedTargetId": "prototype_target_alpha", "validity": "ok", "sequence": 1}
)
# Act
harness.on_target_state_changed(
{"lockedTargetId": "prototype_target_alpha", "validity": "out_of_range", "sequence": 2}
)
# Assert
assert_that(harness.combat_sync_calls).is_equal(1)