neon-sprawl/client/test/position_authority_client_t...

130 lines
4.0 KiB
GDScript

extends GdUnitTestSuite
const PositionAuthorityTestDouble := preload("res://test/position_authority_test_double.gd")
# In-process transport: not an HTTPRequest subclass (Godot 4 won't call script request() on those).
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
# Holds _busy without completing (never emits request_completed).
class HangingHttpTransport:
extends Node
signal request_completed(
result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray
)
var request_count: int = 0
func request(
_url: String,
_custom_headers: PackedStringArray = PackedStringArray(),
_method: HTTPClient.Method = HTTPClient.METHOD_GET,
_request_data: String = ""
) -> Error:
request_count += 1
return OK
func _make_client(mock: Node) -> Node:
var c: Node = PositionAuthorityTestDouble.new()
c.set("injected_http", mock)
auto_free(mock)
auto_free(c)
add_child(c)
return c
func test_sync_boot_get_200_emits_snap() -> void:
var mock := MockHttpTransport.new()
mock.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":1,"y":0.9,"z":-5}}')
var c := _make_client(mock)
monitor_signals(c)
c.sync_from_server()
await assert_signal(c).is_emitted(
"authoritative_position_received", Vector3(1.0, 0.9, -5.0), true
)
func test_sync_boot_get_200_malformed_position_does_not_emit() -> void:
var mock := MockHttpTransport.new()
mock.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":"invalid"}')
var c := _make_client(mock)
monitor_signals(c)
c.sync_from_server()
await assert_signal(c).wait_until(400).is_not_emitted("authoritative_position_received")
func test_move_post_400_emits_move_rejected() -> void:
var mock := MockHttpTransport.new()
mock.enqueue(HTTPRequest.RESULT_SUCCESS, 400, '{"reasonCode":"vertical_step_exceeded"}')
var c := _make_client(mock)
monitor_signals(c)
c.submit_move_target(Vector3(1.0, 2.0, 3.0))
await assert_signal(c).is_emitted("move_rejected", "vertical_step_exceeded")
func test_move_post_400_without_reason_emits_unknown() -> void:
var mock := MockHttpTransport.new()
mock.enqueue(HTTPRequest.RESULT_SUCCESS, 400, "{}")
var c := _make_client(mock)
monitor_signals(c)
c.submit_move_target(Vector3.ZERO)
await assert_signal(c).is_emitted("move_rejected", "unknown")
func test_move_post_200_then_verify_get_emits_nav_goal() -> void:
var mock := MockHttpTransport.new()
mock.enqueue(HTTPRequest.RESULT_SUCCESS, 200, "")
mock.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":2,"y":0.5,"z":-1}}')
var c := _make_client(mock)
monitor_signals(c)
c.submit_move_target(Vector3(9.0, 0.0, 9.0))
await assert_signal(c).is_emitted(
"authoritative_position_received", Vector3(2.0, 0.5, -1.0), false
)
func test_second_sync_while_busy_is_ignored() -> void:
var mock := HangingHttpTransport.new()
var c := _make_client(mock)
c.sync_from_server()
c.sync_from_server()
assert_that(mock.request_count).is_equal(1)
func test_move_stream_post_200_does_not_emit_position() -> void:
var mock := MockHttpTransport.new()
mock.enqueue(
HTTPRequest.RESULT_SUCCESS,
200,
(
'{"schemaVersion":1,"playerId":"dev-local-1","position":{"x":-4.9,"y":0.9,"z":-5},'
+ '"sequence":3}'
)
)
var c := _make_client(mock)
monitor_signals(c)
c.submit_stream_targets([Vector3(-4.9, 0.9, -5.0)])
await assert_signal(c).wait_until(400).is_not_emitted("authoritative_position_received")