extends GdUnitTestSuite ## NEO-26: `TargetSelectionClient.selection_event` — cause strings and lock-id-only rule. const TargetSelectionTestDouble := preload("res://test/target_selection_test_double.gd") const ELITE_ID := "prototype_npc_elite" const MELEE_ID := "prototype_npc_melee" const RANGED_ID := "prototype_npc_ranged" class MockHttpTransport: extends Node signal request_completed( result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray ) var _queue: Array[Dictionary] = [] func enqueue(result: int, code: int, body: String) -> void: _queue.append({"result": result, "code": code, "body": body}) func request( _url: String, _custom_headers: PackedStringArray = PackedStringArray(), _method: HTTPClient.Method = HTTPClient.METHOD_GET, _request_data: String = "" ) -> Error: if _queue.is_empty(): return ERR_UNAVAILABLE var r: Dictionary = _queue.pop_front() var body_bytes: PackedByteArray = str(r["body"]).to_utf8_buffer() request_completed.emit(r["result"], r["code"], PackedStringArray(), body_bytes) return OK func _make_client(http_transport: Node) -> Node: var c: Node = TargetSelectionTestDouble.new() c.set("injected_http", http_transport) auto_free(http_transport) auto_free(c) add_child(c) return c func _target_state_json(locked_id_or_null: Variant, validity: String, sequence: int = 0) -> String: var locked := "null" if locked_id_or_null is String and not (locked_id_or_null as String).is_empty(): locked = '"%s"' % (locked_id_or_null as String) return ( '{"schemaVersion":1,"playerId":"dev-local-1","lockedTargetId":%s,' % locked + '"validity":"%s","sequence":%d}' % [validity, sequence] ) func _select_response_json( applied: bool, locked_id_or_null: Variant, validity: String, sequence: int = 0, reason: String = "" ) -> String: var target_state := _target_state_json(locked_id_or_null, validity, sequence) if applied: return '{"schemaVersion":1,"selectionApplied":true,"targetState":%s}' % target_state var reason_segment := "" if not reason.is_empty(): reason_segment = ',"reasonCode":"%s"' % reason return ( '{"schemaVersion":1,"selectionApplied":false%s,"targetState":%s}' % [reason_segment, target_state] ) func test_tab_lock_change_emits_tab() -> void: # Arrange var transport := MockHttpTransport.new() transport.enqueue( HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1) ) var c := _make_client(transport) var events: Array = [] c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate())) # Act c.request_tab_next() # Assert assert_that(events.size()).is_equal(1) 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(MELEE_ID) assert_that(int(last.get("sequence", -1))).is_equal(1) func test_clear_lock_change_emits_clear() -> void: # Arrange var transport := MockHttpTransport.new() transport.enqueue( 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) var events: Array = [] c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate())) # Act c.request_tab_next() c.request_clear_target() # Assert 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(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) 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, MELEE_ID, "ok", 1) ) 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())) # Act c.request_tab_next() c.request_sync_from_server() # Assert 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(MELEE_ID) assert_that(last.get("next")).is_equal(RANGED_ID) assert_that(int(last.get("sequence", -1))).is_equal(2) func test_get_validity_only_change_emits_nothing() -> void: # Arrange var transport := MockHttpTransport.new() transport.enqueue( HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, MELEE_ID, "ok", 1) ) transport.enqueue( HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(MELEE_ID, "out_of_range", 1) ) var c := _make_client(transport) var events: Array = [] c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate())) # Act c.request_tab_next() c.request_sync_from_server() # Assert assert_that(events.size()).is_equal(1) assert_that((events[0] as Dictionary).get("cause", "")).is_equal("tab") assert_that(int((events[0] as Dictionary).get("sequence", -1))).is_equal(1) func test_denial_without_lock_id_change_emits_nothing() -> void: # Arrange var transport := MockHttpTransport.new() transport.enqueue( HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(false, null, "none", 0, "out_of_range") ) var c := _make_client(transport) var events: Array = [] c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate())) # Act c.request_select_target_id(MELEE_ID) # Assert assert_that(events.size()).is_equal(0)