chore: Apply Arrange/Act/Assert across server and client tests.
Split HTTP verification reads into Assert in C# API and integration tests. Add # Arrange/# Act/# Assert to GdUnit suites and keep Act free of assertions where casts and busy guards are under test.pull/57/head
parent
7050bc12da
commit
e6ccc4453c
|
|
@ -126,11 +126,13 @@ func test_request_cast_while_busy_is_ignored() -> void:
|
||||||
var transport := HoldFirstThenAutoTransport.new()
|
var transport := HoldFirstThenAutoTransport.new()
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
# Act
|
# Act
|
||||||
assert_that(bool(c.call("request_cast", 0, "prototype_pulse", null))).is_true()
|
var first_started: bool = bool(c.call("request_cast", 0, "prototype_pulse", null))
|
||||||
assert_that(bool(c.call("request_cast", 1, "prototype_guard", null))).is_false()
|
var second_started: bool = bool(c.call("request_cast", 1, "prototype_guard", null))
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
# Assert
|
# Assert
|
||||||
|
assert_that(first_started).is_true()
|
||||||
|
assert_that(second_started).is_false()
|
||||||
assert_that(transport.request_count).is_equal(1)
|
assert_that(transport.request_count).is_equal(1)
|
||||||
var parsed: Variant = JSON.parse_string(transport.last_body)
|
var parsed: Variant = JSON.parse_string(transport.last_body)
|
||||||
var body: Dictionary = parsed
|
var body: Dictionary = parsed
|
||||||
|
|
@ -149,8 +151,9 @@ func test_cast_result_received_emits_false_with_reason_on_denied() -> void:
|
||||||
var got: Array = []
|
var got: Array = []
|
||||||
c.connect("cast_result_received", func(accepted: bool, reason: String): got.append([accepted, reason]))
|
c.connect("cast_result_received", func(accepted: bool, reason: String): got.append([accepted, reason]))
|
||||||
# Act
|
# Act
|
||||||
assert_that(bool(c.call("request_cast", 0, "prototype_pulse", "prototype_target_alpha"))).is_true()
|
var started: bool = bool(c.call("request_cast", 0, "prototype_pulse", "prototype_target_alpha"))
|
||||||
# Assert
|
# Assert
|
||||||
|
assert_that(started).is_true()
|
||||||
assert_that(got.size()).is_equal(1)
|
assert_that(got.size()).is_equal(1)
|
||||||
assert_that(bool(got[0][0])).is_false()
|
assert_that(bool(got[0][0])).is_false()
|
||||||
assert_that(str(got[0][1])).is_equal("invalid_target")
|
assert_that(str(got[0][1])).is_equal("invalid_target")
|
||||||
|
|
@ -164,8 +167,9 @@ func test_cast_result_received_emits_true_with_empty_reason_on_accepted() -> voi
|
||||||
var got: Array = []
|
var got: Array = []
|
||||||
c.connect("cast_result_received", func(accepted: bool, reason: String): got.append([accepted, reason]))
|
c.connect("cast_result_received", func(accepted: bool, reason: String): got.append([accepted, reason]))
|
||||||
# Act
|
# Act
|
||||||
assert_that(bool(c.call("request_cast", 0, "prototype_pulse", null))).is_true()
|
var started: bool = bool(c.call("request_cast", 0, "prototype_pulse", null))
|
||||||
# Assert
|
# Assert
|
||||||
|
assert_that(started).is_true()
|
||||||
assert_that(got.size()).is_equal(1)
|
assert_that(got.size()).is_equal(1)
|
||||||
assert_that(bool(got[0][0])).is_true()
|
assert_that(bool(got[0][0])).is_true()
|
||||||
assert_that(str(got[0][1])).is_equal("")
|
assert_that(str(got[0][1])).is_equal("")
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,24 @@ const CameraStateScript := preload("res://scripts/camera_state.gd")
|
||||||
|
|
||||||
|
|
||||||
func test_default_yaw_is_zero() -> void:
|
func test_default_yaw_is_zero() -> void:
|
||||||
|
# Arrange
|
||||||
var s = CameraStateScript.new()
|
var s = CameraStateScript.new()
|
||||||
assert_that(s.yaw).is_equal(0.0)
|
# Act
|
||||||
|
var yaw: float = s.yaw
|
||||||
|
# Assert
|
||||||
|
assert_that(yaw).is_equal(0.0)
|
||||||
|
|
||||||
|
|
||||||
func test_fields_round_trip() -> void:
|
func test_fields_round_trip() -> void:
|
||||||
|
# Arrange
|
||||||
var s = CameraStateScript.new()
|
var s = CameraStateScript.new()
|
||||||
|
# Act
|
||||||
s.follow_target_path = NodePath("../Player")
|
s.follow_target_path = NodePath("../Player")
|
||||||
s.distance = 18.5
|
s.distance = 18.5
|
||||||
s.zoom_band_index = 2
|
s.zoom_band_index = 2
|
||||||
s.focus_world = Vector3(1.0, 2.0, 3.0)
|
s.focus_world = Vector3(1.0, 2.0, 3.0)
|
||||||
s.yaw = 0.12
|
s.yaw = 0.12
|
||||||
|
# Assert
|
||||||
assert_that(s.follow_target_path).is_equal(NodePath("../Player"))
|
assert_that(s.follow_target_path).is_equal(NodePath("../Player"))
|
||||||
assert_that(s.distance).is_equal(18.5)
|
assert_that(s.distance).is_equal(18.5)
|
||||||
assert_that(s.zoom_band_index).is_equal(2)
|
assert_that(s.zoom_band_index).is_equal(2)
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ func _sample_slots_json() -> String:
|
||||||
|
|
||||||
|
|
||||||
func test_sync_get_applies_slots_to_hotbar_state() -> void:
|
func test_sync_get_applies_slots_to_hotbar_state() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _sample_slots_json())
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _sample_slots_json())
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
|
@ -102,7 +103,9 @@ func test_sync_get_applies_slots_to_hotbar_state() -> void:
|
||||||
auto_free(state)
|
auto_free(state)
|
||||||
add_child(state)
|
add_child(state)
|
||||||
c.call("set_hotbar_state", state)
|
c.call("set_hotbar_state", state)
|
||||||
|
# Act
|
||||||
c.call("request_sync_from_server")
|
c.call("request_sync_from_server")
|
||||||
|
# Assert
|
||||||
var slots: Array = state.call("slots_snapshot")
|
var slots: Array = state.call("slots_snapshot")
|
||||||
assert_that(slots.size()).is_equal(8)
|
assert_that(slots.size()).is_equal(8)
|
||||||
assert_that(slots[0]).is_equal("prototype_pulse")
|
assert_that(slots[0]).is_equal("prototype_pulse")
|
||||||
|
|
@ -111,6 +114,7 @@ func test_sync_get_applies_slots_to_hotbar_state() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_bind_slot_posts_expected_payload() -> void:
|
func test_bind_slot_posts_expected_payload() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS,
|
HTTPRequest.RESULT_SUCCESS,
|
||||||
|
|
@ -121,7 +125,9 @@ func test_bind_slot_posts_expected_payload() -> void:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.call("request_bind_slot", 3, "prototype_burst")
|
c.call("request_bind_slot", 3, "prototype_burst")
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_url).contains("/hotbar-loadout")
|
assert_that(transport.last_url).contains("/hotbar-loadout")
|
||||||
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
||||||
var parsed: Variant = JSON.parse_string(transport.last_body)
|
var parsed: Variant = JSON.parse_string(transport.last_body)
|
||||||
|
|
@ -135,6 +141,7 @@ func test_bind_slot_posts_expected_payload() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_denied_update_emits_reason_signal() -> void:
|
func test_denied_update_emits_reason_signal() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS,
|
HTTPRequest.RESULT_SUCCESS,
|
||||||
|
|
@ -146,17 +153,22 @@ func test_denied_update_emits_reason_signal() -> void:
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.call("request_bind_slot", 1, "not_real")
|
c.call("request_bind_slot", 1, "not_real")
|
||||||
|
# Assert
|
||||||
assert_signal(c).is_emitted("loadout_update_denied")
|
assert_signal(c).is_emitted("loadout_update_denied")
|
||||||
|
|
||||||
|
|
||||||
func test_bind_slot_while_busy_is_ignored() -> void:
|
func test_bind_slot_while_busy_is_ignored() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := HoldFirstThenAutoTransport.new()
|
var transport := HoldFirstThenAutoTransport.new()
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.call("request_bind_slot", 1, "prototype_pulse")
|
c.call("request_bind_slot", 1, "prototype_pulse")
|
||||||
c.call("request_bind_slot", 2, "prototype_guard")
|
c.call("request_bind_slot", 2, "prototype_guard")
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
|
# Assert
|
||||||
assert_that(transport.request_count).is_equal(1)
|
assert_that(transport.request_count).is_equal(1)
|
||||||
var parsed: Variant = JSON.parse_string(transport.last_body)
|
var parsed: Variant = JSON.parse_string(transport.last_body)
|
||||||
assert_that(parsed is Dictionary).is_true()
|
assert_that(parsed is Dictionary).is_true()
|
||||||
|
|
@ -165,18 +177,24 @@ func test_bind_slot_while_busy_is_ignored() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_http_failure_does_not_mutate_cached_loadout() -> void:
|
func test_http_failure_does_not_mutate_cached_loadout() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_CANT_CONNECT, 0, "")
|
transport.enqueue(HTTPRequest.RESULT_CANT_CONNECT, 0, "")
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.call("request_sync_from_server")
|
c.call("request_sync_from_server")
|
||||||
|
# Assert
|
||||||
var cached: Dictionary = c.call("cached_loadout")
|
var cached: Dictionary = c.call("cached_loadout")
|
||||||
assert_that(cached.is_empty()).is_true()
|
assert_that(cached.is_empty()).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_non_json_error_response_keeps_cached_loadout_empty() -> void:
|
func test_non_json_error_response_keeps_cached_loadout_empty() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 500, "oops")
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 500, "oops")
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.call("request_sync_from_server")
|
c.call("request_sync_from_server")
|
||||||
|
# Assert
|
||||||
var cached: Dictionary = c.call("cached_loadout")
|
var cached: Dictionary = c.call("cached_loadout")
|
||||||
assert_that(cached.is_empty()).is_true()
|
assert_that(cached.is_empty()).is_true()
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const _CATALOG_SCRIPT := preload("res://scripts/interactables_catalog_client.gd"
|
||||||
|
|
||||||
|
|
||||||
func test_parse_catalog_json_orders_and_fields() -> void:
|
func test_parse_catalog_json_orders_and_fields() -> void:
|
||||||
|
# Arrange
|
||||||
var json := (
|
var json := (
|
||||||
'{"schemaVersion":1,"interactables":['
|
'{"schemaVersion":1,"interactables":['
|
||||||
+ '{"interactableId":"prototype_resource_node_alpha","kind":"resource_node",'
|
+ '{"interactableId":"prototype_resource_node_alpha","kind":"resource_node",'
|
||||||
|
|
@ -14,7 +15,9 @@ func test_parse_catalog_json_orders_and_fields() -> void:
|
||||||
+ '"anchor":{"x":0,"y":0.5,"z":0},"interactionRadius":3}'
|
+ '"anchor":{"x":0,"y":0.5,"z":0},"interactionRadius":3}'
|
||||||
+ "]}"
|
+ "]}"
|
||||||
)
|
)
|
||||||
|
# Act
|
||||||
var rows: Variant = _CATALOG_SCRIPT.parse_catalog_json(json)
|
var rows: Variant = _CATALOG_SCRIPT.parse_catalog_json(json)
|
||||||
|
# Assert
|
||||||
assert_that(rows is Array).is_true()
|
assert_that(rows is Array).is_true()
|
||||||
var arr: Array = rows
|
var arr: Array = rows
|
||||||
assert_that(arr.size()).is_equal(2)
|
assert_that(arr.size()).is_equal(2)
|
||||||
|
|
@ -29,5 +32,9 @@ func test_parse_catalog_json_orders_and_fields() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_parse_catalog_json_returns_empty_on_garbage() -> void:
|
func test_parse_catalog_json_returns_empty_on_garbage() -> void:
|
||||||
var rows2: Variant = _CATALOG_SCRIPT.parse_catalog_json("[]")
|
# Arrange
|
||||||
|
var garbage := "[]"
|
||||||
|
# Act
|
||||||
|
var rows2: Variant = _CATALOG_SCRIPT.parse_catalog_json(garbage)
|
||||||
|
# Assert
|
||||||
assert_that((rows2 as Array).is_empty()).is_true()
|
assert_that((rows2 as Array).is_empty()).is_true()
|
||||||
|
|
|
||||||
|
|
@ -66,41 +66,56 @@ func _make_ix(transport: Node) -> Node:
|
||||||
|
|
||||||
|
|
||||||
func test_post_terminal_body_contains_prototype_terminal() -> void:
|
func test_post_terminal_body_contains_prototype_terminal() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
var ix := _make_ix(transport)
|
var ix := _make_ix(transport)
|
||||||
|
# Act
|
||||||
ix.call("_post_interact", "prototype_terminal")
|
ix.call("_post_interact", "prototype_terminal")
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_body).contains("prototype_terminal")
|
assert_that(transport.last_body).contains("prototype_terminal")
|
||||||
assert_that(transport.last_url).contains("/interact")
|
assert_that(transport.last_url).contains("/interact")
|
||||||
|
|
||||||
|
|
||||||
func test_post_resource_body_contains_resource_node_id() -> void:
|
func test_post_resource_body_contains_resource_node_id() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
var ix := _make_ix(transport)
|
var ix := _make_ix(transport)
|
||||||
|
# Act
|
||||||
ix.call("_post_interact", "prototype_resource_node_alpha")
|
ix.call("_post_interact", "prototype_resource_node_alpha")
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_body).contains("prototype_resource_node_alpha")
|
assert_that(transport.last_body).contains("prototype_resource_node_alpha")
|
||||||
|
|
||||||
|
|
||||||
func test_post_interact_terminal_public_entrypoint() -> void:
|
func test_post_interact_terminal_public_entrypoint() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
var ix := _make_ix(transport)
|
var ix := _make_ix(transport)
|
||||||
|
# Act
|
||||||
ix.call("post_interact_terminal")
|
ix.call("post_interact_terminal")
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_body).contains("prototype_terminal")
|
assert_that(transport.last_body).contains("prototype_terminal")
|
||||||
|
|
||||||
|
|
||||||
func test_post_interact_resource_public_entrypoint() -> void:
|
func test_post_interact_resource_public_entrypoint() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
var ix := _make_ix(transport)
|
var ix := _make_ix(transport)
|
||||||
|
# Act
|
||||||
ix.call("post_interact_resource")
|
ix.call("post_interact_resource")
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_body).contains("prototype_resource_node_alpha")
|
assert_that(transport.last_body).contains("prototype_resource_node_alpha")
|
||||||
|
|
||||||
|
|
||||||
func test_last_wins_second_press_before_first_completes() -> void:
|
func test_last_wins_second_press_before_first_completes() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := HoldFirstThenAutoTransport.new()
|
var transport := HoldFirstThenAutoTransport.new()
|
||||||
var ix := _make_ix(transport)
|
var ix := _make_ix(transport)
|
||||||
|
# Act
|
||||||
ix.call("_post_interact", "prototype_terminal")
|
ix.call("_post_interact", "prototype_terminal")
|
||||||
ix.call("_post_interact", "prototype_resource_node_alpha")
|
ix.call("_post_interact", "prototype_resource_node_alpha")
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
|
# Assert
|
||||||
assert_that(transport.bodies.size()).is_equal(2)
|
assert_that(transport.bodies.size()).is_equal(2)
|
||||||
assert_that(transport.bodies[0]).contains("prototype_terminal")
|
assert_that(transport.bodies[0]).contains("prototype_terminal")
|
||||||
assert_that(transport.bodies[1]).contains("prototype_resource_node_alpha")
|
assert_that(transport.bodies[1]).contains("prototype_resource_node_alpha")
|
||||||
|
|
|
||||||
|
|
@ -7,78 +7,134 @@ const OcclusionPolicyScript := preload("res://scripts/occlusion_policy.gd")
|
||||||
|
|
||||||
|
|
||||||
func test_desired_eye_matches_previous_static_camera_frame() -> void:
|
func test_desired_eye_matches_previous_static_camera_frame() -> void:
|
||||||
|
# Arrange
|
||||||
var focus := Vector3(-5.0, 0.9, -5.0)
|
var focus := Vector3(-5.0, 0.9, -5.0)
|
||||||
|
var want := Vector3(12.0, 10.0, 12.0)
|
||||||
|
# Act
|
||||||
var eye: Vector3 = IsoScript.desired_eye_world(
|
var eye: Vector3 = IsoScript.desired_eye_world(
|
||||||
focus, 25.709, deg_to_rad(20.693), deg_to_rad(45.0)
|
focus, 25.709, deg_to_rad(20.693), deg_to_rad(45.0)
|
||||||
)
|
)
|
||||||
var want := Vector3(12.0, 10.0, 12.0)
|
# Assert
|
||||||
assert_that(eye.distance_to(want)).is_less(0.06)
|
assert_that(eye.distance_to(want)).is_less(0.06)
|
||||||
|
|
||||||
|
|
||||||
func test_effective_follow_distance_fallback_when_config_null() -> void:
|
func test_effective_follow_distance_fallback_when_config_null() -> void:
|
||||||
assert_that(IsoScript.effective_follow_distance(null, 3, 12.5)).is_equal(12.5)
|
# Arrange
|
||||||
|
# Act
|
||||||
|
var d: float = IsoScript.effective_follow_distance(null, 3, 12.5)
|
||||||
|
# Assert
|
||||||
|
assert_that(d).is_equal(12.5)
|
||||||
|
|
||||||
|
|
||||||
func test_effective_follow_distance_uses_config_when_valid() -> void:
|
func test_effective_follow_distance_uses_config_when_valid() -> void:
|
||||||
|
# Arrange
|
||||||
var cfg = ZoomBandConfigScript.new()
|
var cfg = ZoomBandConfigScript.new()
|
||||||
cfg.band_distances = PackedFloat32Array([10.0, 20.0, 30.0])
|
cfg.band_distances = PackedFloat32Array([10.0, 20.0, 30.0])
|
||||||
assert_that(IsoScript.effective_follow_distance(cfg, 1, 99.0)).is_equal(20.0)
|
# Act
|
||||||
|
var d: float = IsoScript.effective_follow_distance(cfg, 1, 99.0)
|
||||||
|
# Assert
|
||||||
|
assert_that(d).is_equal(20.0)
|
||||||
|
|
||||||
|
|
||||||
func test_effective_follow_distance_clamps_band_index() -> void:
|
func test_effective_follow_distance_clamps_band_index() -> void:
|
||||||
|
# Arrange
|
||||||
var cfg = ZoomBandConfigScript.new()
|
var cfg = ZoomBandConfigScript.new()
|
||||||
cfg.band_distances = PackedFloat32Array([10.0, 20.0])
|
cfg.band_distances = PackedFloat32Array([10.0, 20.0])
|
||||||
assert_that(IsoScript.effective_follow_distance(cfg, 99, 1.0)).is_equal(20.0)
|
# Act
|
||||||
assert_that(IsoScript.effective_follow_distance(cfg, -3, 1.0)).is_equal(10.0)
|
var high: float = IsoScript.effective_follow_distance(cfg, 99, 1.0)
|
||||||
|
var low: float = IsoScript.effective_follow_distance(cfg, -3, 1.0)
|
||||||
|
# Assert
|
||||||
|
assert_that(high).is_equal(20.0)
|
||||||
|
assert_that(low).is_equal(10.0)
|
||||||
|
|
||||||
|
|
||||||
func test_effective_follow_distance_empty_config_bands_falls_back() -> void:
|
func test_effective_follow_distance_empty_config_bands_falls_back() -> void:
|
||||||
|
# Arrange
|
||||||
var cfg = ZoomBandConfigScript.new()
|
var cfg = ZoomBandConfigScript.new()
|
||||||
cfg.band_distances = PackedFloat32Array()
|
cfg.band_distances = PackedFloat32Array()
|
||||||
assert_that(IsoScript.effective_follow_distance(cfg, 0, 7.5)).is_equal(7.5)
|
# Act
|
||||||
|
var d: float = IsoScript.effective_follow_distance(cfg, 0, 7.5)
|
||||||
|
# Assert
|
||||||
|
assert_that(d).is_equal(7.5)
|
||||||
|
|
||||||
|
|
||||||
func test_effective_follow_distance_fallback_when_band_non_positive() -> void:
|
func test_effective_follow_distance_fallback_when_band_non_positive() -> void:
|
||||||
|
# Arrange
|
||||||
var cfg = ZoomBandConfigScript.new()
|
var cfg = ZoomBandConfigScript.new()
|
||||||
cfg.band_distances = PackedFloat32Array([10.0, 0.0, 30.0])
|
cfg.band_distances = PackedFloat32Array([10.0, 0.0, 30.0])
|
||||||
assert_that(IsoScript.effective_follow_distance(cfg, 2, 99.0)).is_equal(99.0)
|
# Act
|
||||||
|
var d: float = IsoScript.effective_follow_distance(cfg, 2, 99.0)
|
||||||
|
# Assert
|
||||||
|
assert_that(d).is_equal(99.0)
|
||||||
|
|
||||||
|
|
||||||
func test_occlusion_policy_is_valid_null() -> void:
|
func test_occlusion_policy_is_valid_null() -> void:
|
||||||
assert_that(IsoScript.occlusion_policy_is_valid(null)).is_false()
|
# Arrange
|
||||||
|
# Act
|
||||||
|
var ok: bool = IsoScript.occlusion_policy_is_valid(null)
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_occlusion_policy_is_valid_wrong_script() -> void:
|
func test_occlusion_policy_is_valid_wrong_script() -> void:
|
||||||
|
# Arrange
|
||||||
var wrong = ZoomBandConfigScript.new()
|
var wrong = ZoomBandConfigScript.new()
|
||||||
assert_that(IsoScript.occlusion_policy_is_valid(wrong)).is_false()
|
# Act
|
||||||
|
var ok: bool = IsoScript.occlusion_policy_is_valid(wrong)
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_occlusion_policy_is_valid_correct_script() -> void:
|
func test_occlusion_policy_is_valid_correct_script() -> void:
|
||||||
|
# Arrange
|
||||||
var p = OcclusionPolicyScript.new()
|
var p = OcclusionPolicyScript.new()
|
||||||
assert_that(IsoScript.occlusion_policy_is_valid(p)).is_true()
|
# Act
|
||||||
|
var ok: bool = IsoScript.occlusion_policy_is_valid(p)
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_occlusion_policy_is_valid_false_when_disabled() -> void:
|
func test_occlusion_policy_is_valid_false_when_disabled() -> void:
|
||||||
|
# Arrange
|
||||||
var p = OcclusionPolicyScript.new()
|
var p = OcclusionPolicyScript.new()
|
||||||
p.enabled = false
|
p.enabled = false
|
||||||
assert_that(IsoScript.occlusion_policy_is_valid(p)).is_false()
|
# Act
|
||||||
|
var ok: bool = IsoScript.occlusion_policy_is_valid(p)
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_occluder_override_key_is_valid_null() -> void:
|
func test_occluder_override_key_is_valid_null() -> void:
|
||||||
assert_that(IsoScript.occluder_override_key_is_valid(null)).is_false()
|
# Arrange
|
||||||
|
# Act
|
||||||
|
var ok: bool = IsoScript.occluder_override_key_is_valid(null)
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_occluder_override_key_is_valid_non_node() -> void:
|
func test_occluder_override_key_is_valid_non_node() -> void:
|
||||||
assert_that(IsoScript.occluder_override_key_is_valid(42)).is_false()
|
# Arrange
|
||||||
|
# Act
|
||||||
|
var ok: bool = IsoScript.occluder_override_key_is_valid(42)
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_occluder_override_key_is_valid_live_node3d() -> void:
|
func test_occluder_override_key_is_valid_live_node3d() -> void:
|
||||||
|
# Arrange
|
||||||
var n := Node3D.new()
|
var n := Node3D.new()
|
||||||
assert_that(IsoScript.occluder_override_key_is_valid(n)).is_true()
|
# Act
|
||||||
|
var ok: bool = IsoScript.occluder_override_key_is_valid(n)
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_true()
|
||||||
n.free()
|
n.free()
|
||||||
|
|
||||||
|
|
||||||
func test_occluder_override_key_is_valid_false_after_free() -> void:
|
func test_occluder_override_key_is_valid_false_after_free() -> void:
|
||||||
|
# Arrange
|
||||||
var n := Node3D.new()
|
var n := Node3D.new()
|
||||||
n.free()
|
n.free()
|
||||||
assert_that(IsoScript.occluder_override_key_is_valid(n)).is_false()
|
# Act
|
||||||
|
var ok: bool = IsoScript.occluder_override_key_is_valid(n)
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_false()
|
||||||
|
|
|
||||||
|
|
@ -5,39 +5,68 @@ const OcclusionPolicyScript := preload("res://scripts/occlusion_policy.gd")
|
||||||
|
|
||||||
|
|
||||||
func test_defaults_are_valid() -> void:
|
func test_defaults_are_valid() -> void:
|
||||||
|
# Arrange
|
||||||
var p = OcclusionPolicyScript.new()
|
var p = OcclusionPolicyScript.new()
|
||||||
assert_that(p.is_valid()).is_true()
|
# Act
|
||||||
|
var ok: bool = p.is_valid()
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_defaults_match_spec() -> void:
|
func test_defaults_match_spec() -> void:
|
||||||
|
# Arrange
|
||||||
var p = OcclusionPolicyScript.new()
|
var p = OcclusionPolicyScript.new()
|
||||||
assert_that(p.enabled).is_true()
|
# Act
|
||||||
assert_that(p.fade_alpha).is_equal(0.25)
|
var enabled: bool = p.enabled
|
||||||
assert_that(p.occluder_group).is_equal("occluder")
|
var fade: float = p.fade_alpha
|
||||||
assert_that(p.occluder_collision_mask).is_equal(1)
|
var group: String = p.occluder_group
|
||||||
assert_that(p.max_occluder_cast_depth).is_equal(4)
|
var mask: int = p.occluder_collision_mask
|
||||||
assert_that(p.occluder_count_log_threshold).is_equal(0)
|
var depth: int = p.max_occluder_cast_depth
|
||||||
|
var threshold: int = p.occluder_count_log_threshold
|
||||||
|
# Assert
|
||||||
|
assert_that(enabled).is_true()
|
||||||
|
assert_that(fade).is_equal(0.25)
|
||||||
|
assert_that(group).is_equal("occluder")
|
||||||
|
assert_that(mask).is_equal(1)
|
||||||
|
assert_that(depth).is_equal(4)
|
||||||
|
assert_that(threshold).is_equal(0)
|
||||||
|
|
||||||
|
|
||||||
func test_is_valid_false_when_disabled() -> void:
|
func test_is_valid_false_when_disabled() -> void:
|
||||||
|
# Arrange
|
||||||
var p = OcclusionPolicyScript.new()
|
var p = OcclusionPolicyScript.new()
|
||||||
p.enabled = false
|
p.enabled = false
|
||||||
assert_that(p.is_valid()).is_false()
|
# Act
|
||||||
|
var ok: bool = p.is_valid()
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_is_valid_true_when_fade_alpha_zero() -> void:
|
func test_is_valid_true_when_fade_alpha_zero() -> void:
|
||||||
|
# Arrange
|
||||||
var p = OcclusionPolicyScript.new()
|
var p = OcclusionPolicyScript.new()
|
||||||
p.fade_alpha = 0.0
|
p.fade_alpha = 0.0
|
||||||
assert_that(p.is_valid()).is_true()
|
# Act
|
||||||
|
var ok: bool = p.is_valid()
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_is_valid_false_when_fade_alpha_negative() -> void:
|
func test_is_valid_false_when_fade_alpha_negative() -> void:
|
||||||
|
# Arrange
|
||||||
var p = OcclusionPolicyScript.new()
|
var p = OcclusionPolicyScript.new()
|
||||||
p.fade_alpha = -0.1
|
p.fade_alpha = -0.1
|
||||||
assert_that(p.is_valid()).is_false()
|
# Act
|
||||||
|
var ok: bool = p.is_valid()
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_is_valid_true_at_full_opaque() -> void:
|
func test_is_valid_true_at_full_opaque() -> void:
|
||||||
|
# Arrange
|
||||||
var p = OcclusionPolicyScript.new()
|
var p = OcclusionPolicyScript.new()
|
||||||
p.fade_alpha = 1.0
|
p.fade_alpha = 1.0
|
||||||
assert_that(p.is_valid()).is_true()
|
# Act
|
||||||
|
var ok: bool = p.is_valid()
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_true()
|
||||||
|
|
|
||||||
|
|
@ -5,71 +5,98 @@ const PLAYER_SCRIPT: Script = preload("res://scripts/player.gd")
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_on_flat_floor_without_wall_contacts() -> void:
|
func test_idle_support_is_stable_on_flat_floor_without_wall_contacts() -> void:
|
||||||
|
# Arrange
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP]
|
var slide_normals: Array[Vector3] = [Vector3.UP]
|
||||||
|
# Act
|
||||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 0)
|
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 0)
|
||||||
|
# Assert
|
||||||
assert_that(stable).is_true()
|
assert_that(stable).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_false_when_floor_is_not_flat_enough() -> void:
|
func test_idle_support_is_stable_false_when_floor_is_not_flat_enough() -> void:
|
||||||
|
# Arrange
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP]
|
var slide_normals: Array[Vector3] = [Vector3.UP]
|
||||||
# Must be steeper than [member STABLE_IDLE_FLOOR_MIN_UP_DOT] (0.998) *and* below
|
# Must be steeper than [member STABLE_IDLE_FLOOR_MIN_UP_DOT] (0.998) *and* below
|
||||||
# [member IDLE_SLOPE_STABLE_MIN_UP_DOT] (0.88), or the ramp short-circuit treats support as stable.
|
# [member IDLE_SLOPE_STABLE_MIN_UP_DOT] (0.88), or the ramp short-circuit treats support as stable.
|
||||||
var tilted_floor: Vector3 = Vector3(0.5, 0.82, 0.0).normalized()
|
var tilted_floor: Vector3 = Vector3(0.5, 0.82, 0.0).normalized()
|
||||||
|
# Act
|
||||||
|
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, tilted_floor, slide_normals, 0)
|
||||||
|
# Assert
|
||||||
assert_that(tilted_floor.dot(Vector3.UP)).is_less(
|
assert_that(tilted_floor.dot(Vector3.UP)).is_less(
|
||||||
PLAYER_SCRIPT.get("IDLE_SLOPE_STABLE_MIN_UP_DOT")
|
PLAYER_SCRIPT.get("IDLE_SLOPE_STABLE_MIN_UP_DOT")
|
||||||
)
|
)
|
||||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, tilted_floor, slide_normals, 0)
|
|
||||||
assert_that(stable).is_false()
|
assert_that(stable).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_min_flat_up_dot_parameter() -> void:
|
func test_idle_support_is_stable_min_flat_up_dot_parameter() -> void:
|
||||||
|
# Arrange
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP]
|
var slide_normals: Array[Vector3] = [Vector3.UP]
|
||||||
var floor_n: Vector3 = Vector3(0.05, 0.99875, 0.0).normalized()
|
var floor_n: Vector3 = Vector3(0.05, 0.99875, 0.0).normalized()
|
||||||
|
# Act
|
||||||
var strict: bool = PLAYER_SCRIPT.idle_support_is_stable(true, floor_n, slide_normals, 0, 0.999)
|
var strict: bool = PLAYER_SCRIPT.idle_support_is_stable(true, floor_n, slide_normals, 0, 0.999)
|
||||||
var holdish: bool = PLAYER_SCRIPT.idle_support_is_stable(true, floor_n, slide_normals, 0, 0.992)
|
var holdish: bool = PLAYER_SCRIPT.idle_support_is_stable(true, floor_n, slide_normals, 0, 0.992)
|
||||||
|
# Assert
|
||||||
assert_that(strict).is_false()
|
assert_that(strict).is_false()
|
||||||
assert_that(holdish).is_true()
|
assert_that(holdish).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_true_when_flat_floor_has_extra_contacts() -> void:
|
func test_idle_support_is_stable_true_when_flat_floor_has_extra_contacts() -> void:
|
||||||
|
# Arrange
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP, Vector3(0.0, 0.6, 0.8).normalized()]
|
var slide_normals: Array[Vector3] = [Vector3.UP, Vector3(0.0, 0.6, 0.8).normalized()]
|
||||||
|
# Act
|
||||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 0)
|
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 0)
|
||||||
|
# Assert
|
||||||
assert_that(stable).is_true()
|
assert_that(stable).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_true_when_loose_ticks_and_ridged_but_floor_still_level() -> void:
|
func test_idle_support_is_stable_true_when_loose_ticks_and_ridged_but_floor_still_level() -> void:
|
||||||
|
# Arrange
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP, Vector3(1.0, 0.0, 0.0)]
|
var slide_normals: Array[Vector3] = [Vector3.UP, Vector3(1.0, 0.0, 0.0)]
|
||||||
|
# Act
|
||||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 12)
|
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 12)
|
||||||
|
# Assert
|
||||||
assert_that(stable).is_true()
|
assert_that(stable).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_false_when_loose_ticks_and_ridged_and_shallow_floor() -> void:
|
func test_idle_support_is_stable_false_when_loose_ticks_and_ridged_and_shallow_floor() -> void:
|
||||||
|
# Arrange
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP, Vector3(1.0, 0.0, 0.0)]
|
var slide_normals: Array[Vector3] = [Vector3.UP, Vector3(1.0, 0.0, 0.0)]
|
||||||
# Normal must be outside the ramp-stable band [IDLE_SLOPE_STABLE_MIN_UP_DOT, IDLE_RIM) or
|
# Normal must be outside the ramp-stable band [IDLE_SLOPE_STABLE_MIN_UP_DOT, IDLE_RIM) or
|
||||||
# `idle_ridged_stair_lip_only` is false and the ramp short-circuit returns stable. Use a tread
|
# `idle_ridged_stair_lip_only` is false and the ramp short-circuit returns stable. Use a tread
|
||||||
# tilt above the rim dot but still below the ridged hold threshold (0.992).
|
# tilt above the rim dot but still below the ridged hold threshold (0.992).
|
||||||
var floor_n: Vector3 = Vector3(0.14, 0.98, 0.0).normalized()
|
var floor_n: Vector3 = Vector3(0.14, 0.98, 0.0).normalized()
|
||||||
|
# Act
|
||||||
|
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, floor_n, slide_normals, 12)
|
||||||
|
# Assert
|
||||||
var up_dot: float = floor_n.dot(Vector3.UP)
|
var up_dot: float = floor_n.dot(Vector3.UP)
|
||||||
assert_that(up_dot).is_greater_equal(PLAYER_SCRIPT.get("IDLE_RIM_MIN_FLOOR_UP_DOT") as float)
|
assert_that(up_dot).is_greater_equal(PLAYER_SCRIPT.get("IDLE_RIM_MIN_FLOOR_UP_DOT") as float)
|
||||||
assert_that(up_dot).is_less(PLAYER_SCRIPT.get("STABLE_IDLE_FLOOR_MIN_UP_DOT") as float)
|
assert_that(up_dot).is_less(PLAYER_SCRIPT.get("STABLE_IDLE_FLOOR_MIN_UP_DOT") as float)
|
||||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, floor_n, slide_normals, 12)
|
|
||||||
assert_that(stable).is_false()
|
assert_that(stable).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_true_when_loose_ticks_but_flat_open_support() -> void:
|
func test_idle_support_is_stable_true_when_loose_ticks_but_flat_open_support() -> void:
|
||||||
|
# Arrange
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP]
|
var slide_normals: Array[Vector3] = [Vector3.UP]
|
||||||
|
# Act
|
||||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 12)
|
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 12)
|
||||||
|
# Assert
|
||||||
assert_that(stable).is_true()
|
assert_that(stable).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_true_when_loose_ticks_and_shallow_seam_not_ridged() -> void:
|
func test_idle_support_is_stable_true_when_loose_ticks_and_shallow_seam_not_ridged() -> void:
|
||||||
|
# Arrange
|
||||||
var n2: Vector3 = Vector3(0.0, 0.42, sqrt(1.0 - 0.42 * 0.42)).normalized()
|
var n2: Vector3 = Vector3(0.0, 0.42, sqrt(1.0 - 0.42 * 0.42)).normalized()
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP, n2]
|
var slide_normals: Array[Vector3] = [Vector3.UP, n2]
|
||||||
|
# Act
|
||||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 12)
|
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(true, Vector3.UP, slide_normals, 12)
|
||||||
|
# Assert
|
||||||
assert_that(stable).is_true()
|
assert_that(stable).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_idle_support_is_stable_false_when_not_on_floor() -> void:
|
func test_idle_support_is_stable_false_when_not_on_floor() -> void:
|
||||||
|
# Arrange
|
||||||
var slide_normals: Array[Vector3] = [Vector3.UP]
|
var slide_normals: Array[Vector3] = [Vector3.UP]
|
||||||
|
# Act
|
||||||
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(false, Vector3.UP, slide_normals, 0)
|
var stable: bool = PLAYER_SCRIPT.idle_support_is_stable(false, Vector3.UP, slide_normals, 0)
|
||||||
|
# Assert
|
||||||
assert_that(stable).is_false()
|
assert_that(stable).is_false()
|
||||||
|
|
|
||||||
|
|
@ -7,108 +7,150 @@ const FLOOR_RAY_FEET_INVALID: float = -1.0e9
|
||||||
|
|
||||||
|
|
||||||
func test_evaluate_floor_ray_hit_y_rejects_hit_too_far_below_feet() -> void:
|
func test_evaluate_floor_ray_hit_y_rejects_hit_too_far_below_feet() -> void:
|
||||||
|
# Arrange
|
||||||
var feet_y := 0.0
|
var feet_y := 0.0
|
||||||
var hit_y := feet_y - 0.2
|
var hit_y := feet_y - 0.2
|
||||||
var n := Vector3.UP
|
var n := Vector3.UP
|
||||||
|
# Act
|
||||||
var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
|
var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)
|
assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)
|
||||||
|
|
||||||
|
|
||||||
func test_evaluate_floor_ray_hit_y_rejects_shallow_floor_normal() -> void:
|
func test_evaluate_floor_ray_hit_y_rejects_shallow_floor_normal() -> void:
|
||||||
|
# Arrange
|
||||||
var feet_y := 0.0
|
var feet_y := 0.0
|
||||||
var hit_y := 0.0
|
var hit_y := 0.0
|
||||||
# Up component ~0.37 so dot(Vector3.UP, n) is below min_floor_up_dot (0.42).
|
# Up component ~0.37 so dot(Vector3.UP, n) is below min_floor_up_dot (0.42).
|
||||||
var n := Vector3(0.85, 0.35, 0.0).normalized()
|
var n := Vector3(0.85, 0.35, 0.0).normalized()
|
||||||
|
# Act
|
||||||
var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
|
var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)
|
assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)
|
||||||
|
|
||||||
|
|
||||||
func test_evaluate_floor_ray_hit_y_accepts_valid_hit() -> void:
|
func test_evaluate_floor_ray_hit_y_accepts_valid_hit() -> void:
|
||||||
|
# Arrange
|
||||||
var feet_y := 0.0
|
var feet_y := 0.0
|
||||||
var hit_y := -0.04
|
var hit_y := -0.04
|
||||||
var n := Vector3.UP
|
var n := Vector3.UP
|
||||||
|
# Act
|
||||||
var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
|
var out: float = LocomotionWasdScript.evaluate_floor_ray_hit_y(feet_y, hit_y, n, 0.108, 0.42)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(hit_y)
|
assert_that(out).is_equal(hit_y)
|
||||||
|
|
||||||
|
|
||||||
func test_median_feet_y_from_samples_empty_is_invalid() -> void:
|
func test_median_feet_y_from_samples_empty_is_invalid() -> void:
|
||||||
|
# Arrange
|
||||||
var empty: Array[float] = []
|
var empty: Array[float] = []
|
||||||
|
# Act
|
||||||
var out: float = LocomotionWasdScript.median_feet_y_from_samples(empty)
|
var out: float = LocomotionWasdScript.median_feet_y_from_samples(empty)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)
|
assert_that(out).is_equal(FLOOR_RAY_FEET_INVALID)
|
||||||
|
|
||||||
|
|
||||||
func test_median_feet_y_from_samples_single() -> void:
|
func test_median_feet_y_from_samples_single() -> void:
|
||||||
|
# Arrange
|
||||||
var samples: Array[float] = [0.12]
|
var samples: Array[float] = [0.12]
|
||||||
|
# Act
|
||||||
var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
|
var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(0.12)
|
assert_that(out).is_equal(0.12)
|
||||||
|
|
||||||
|
|
||||||
func test_median_feet_y_from_samples_three_sorted_middle() -> void:
|
func test_median_feet_y_from_samples_three_sorted_middle() -> void:
|
||||||
|
# Arrange
|
||||||
var samples: Array[float] = [0.3, 0.1, 0.2]
|
var samples: Array[float] = [0.3, 0.1, 0.2]
|
||||||
|
# Act
|
||||||
var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
|
var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(0.2)
|
assert_that(out).is_equal(0.2)
|
||||||
|
|
||||||
|
|
||||||
func test_median_feet_y_from_samples_two_uses_lower_index() -> void:
|
func test_median_feet_y_from_samples_two_uses_lower_index() -> void:
|
||||||
|
# Arrange
|
||||||
# Matches runtime: mid index (n-1)>>1 picks the smaller of two sorted values.
|
# Matches runtime: mid index (n-1)>>1 picks the smaller of two sorted values.
|
||||||
var samples: Array[float] = [0.5, 0.1]
|
var samples: Array[float] = [0.5, 0.1]
|
||||||
|
# Act
|
||||||
var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
|
var out: float = LocomotionWasdScript.median_feet_y_from_samples(samples)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(0.1)
|
assert_that(out).is_equal(0.1)
|
||||||
|
|
||||||
|
|
||||||
func test_xz_after_micro_slip_projection_zero_wish_unchanged() -> void:
|
func test_xz_after_micro_slip_projection_zero_wish_unchanged() -> void:
|
||||||
|
# Arrange
|
||||||
var anchor := Vector2.ZERO
|
var anchor := Vector2.ZERO
|
||||||
var player_xz := Vector2(0.02, 0.03)
|
var player_xz := Vector2(0.02, 0.03)
|
||||||
|
# Act
|
||||||
var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
|
var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
|
||||||
anchor, player_xz, Vector2.ZERO, 0.032
|
anchor, player_xz, Vector2.ZERO, 0.032
|
||||||
)
|
)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(player_xz)
|
assert_that(out).is_equal(player_xz)
|
||||||
|
|
||||||
|
|
||||||
func test_xz_after_micro_slip_projection_large_perp_unchanged() -> void:
|
func test_xz_after_micro_slip_projection_large_perp_unchanged() -> void:
|
||||||
|
# Arrange
|
||||||
var anchor := Vector2.ZERO
|
var anchor := Vector2.ZERO
|
||||||
var player_xz := Vector2(0.0, 0.2)
|
var player_xz := Vector2(0.0, 0.2)
|
||||||
var wish := Vector2(1.0, 0.0)
|
var wish := Vector2(1.0, 0.0)
|
||||||
|
# Act
|
||||||
var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
|
var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
|
||||||
anchor, player_xz, wish, 0.032
|
anchor, player_xz, wish, 0.032
|
||||||
)
|
)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(player_xz)
|
assert_that(out).is_equal(player_xz)
|
||||||
|
|
||||||
|
|
||||||
func test_xz_after_micro_slip_projection_nudges_small_perp() -> void:
|
func test_xz_after_micro_slip_projection_nudges_small_perp() -> void:
|
||||||
|
# Arrange
|
||||||
var anchor := Vector2.ZERO
|
var anchor := Vector2.ZERO
|
||||||
var player_xz := Vector2(0.0, 0.01)
|
var player_xz := Vector2(0.0, 0.01)
|
||||||
var wish := Vector2(1.0, 0.0)
|
var wish := Vector2(1.0, 0.0)
|
||||||
|
# Act
|
||||||
var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
|
var out: Vector2 = LocomotionWasdScript.xz_after_micro_slip_projection(
|
||||||
anchor, player_xz, wish, 0.032
|
anchor, player_xz, wish, 0.032
|
||||||
)
|
)
|
||||||
|
# Assert
|
||||||
assert_that(out.x).is_equal(0.0)
|
assert_that(out.x).is_equal(0.0)
|
||||||
assert_that(out.y).is_equal(0.0)
|
assert_that(out.y).is_equal(0.0)
|
||||||
|
|
||||||
|
|
||||||
func test_horizontal_velocity_aligned_to_wish_zero_wish_returns_velocity() -> void:
|
func test_horizontal_velocity_aligned_to_wish_zero_wish_returns_velocity() -> void:
|
||||||
|
# Arrange
|
||||||
|
# Act
|
||||||
var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
|
var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
|
||||||
Vector2.ZERO, Vector2(3.0, 4.0), 5.0
|
Vector2.ZERO, Vector2(3.0, 4.0), 5.0
|
||||||
)
|
)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(Vector2(3.0, 4.0))
|
assert_that(out).is_equal(Vector2(3.0, 4.0))
|
||||||
|
|
||||||
|
|
||||||
func test_horizontal_velocity_aligned_to_wish_clamps_negative_along_to_zero() -> void:
|
func test_horizontal_velocity_aligned_to_wish_clamps_negative_along_to_zero() -> void:
|
||||||
|
# Arrange
|
||||||
|
# Act
|
||||||
var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
|
var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
|
||||||
Vector2(1.0, 0.0), Vector2(-2.0, 0.0), 5.0
|
Vector2(1.0, 0.0), Vector2(-2.0, 0.0), 5.0
|
||||||
)
|
)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(Vector2.ZERO)
|
assert_that(out).is_equal(Vector2.ZERO)
|
||||||
|
|
||||||
|
|
||||||
func test_horizontal_velocity_aligned_to_wish_caps_speed() -> void:
|
func test_horizontal_velocity_aligned_to_wish_caps_speed() -> void:
|
||||||
|
# Arrange
|
||||||
|
# Act
|
||||||
var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
|
var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
|
||||||
Vector2(1.0, 0.0), Vector2(10.0, 0.0), 5.0
|
Vector2(1.0, 0.0), Vector2(10.0, 0.0), 5.0
|
||||||
)
|
)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(Vector2(5.0, 0.0))
|
assert_that(out).is_equal(Vector2(5.0, 0.0))
|
||||||
|
|
||||||
|
|
||||||
func test_horizontal_velocity_aligned_to_wish_preserves_wish_direction() -> void:
|
func test_horizontal_velocity_aligned_to_wish_preserves_wish_direction() -> void:
|
||||||
|
# Arrange
|
||||||
|
# Act
|
||||||
var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
|
var out: Vector2 = LocomotionWasdScript.horizontal_velocity_aligned_to_wish(
|
||||||
Vector2(0.0, 1.0), Vector2(0.0, 2.0), 5.0
|
Vector2(0.0, 1.0), Vector2(0.0, 2.0), 5.0
|
||||||
)
|
)
|
||||||
|
# Assert
|
||||||
assert_that(out).is_equal(Vector2(0.0, 2.0))
|
assert_that(out).is_equal(Vector2(0.0, 2.0))
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,25 @@ func _make_player() -> CharacterBody3D:
|
||||||
|
|
||||||
|
|
||||||
func test_snap_to_server_resets_goal_position_and_velocity() -> void:
|
func test_snap_to_server_resets_goal_position_and_velocity() -> void:
|
||||||
|
# Arrange
|
||||||
var p := _make_player()
|
var p := _make_player()
|
||||||
var target := Vector3(1.0, 0.9, -2.0)
|
var target := Vector3(1.0, 0.9, -2.0)
|
||||||
|
# Act
|
||||||
p.snap_to_server(target)
|
p.snap_to_server(target)
|
||||||
|
# Assert
|
||||||
assert_that(p.global_position).is_equal(target)
|
assert_that(p.global_position).is_equal(target)
|
||||||
assert_that(p.velocity).is_equal(Vector3.ZERO)
|
assert_that(p.velocity).is_equal(Vector3.ZERO)
|
||||||
assert_that(p.get("_has_walk_goal")).is_false()
|
assert_that(p.get("_has_walk_goal")).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_set_authoritative_nav_goal_sets_goal_and_target() -> void:
|
func test_set_authoritative_nav_goal_sets_goal_and_target() -> void:
|
||||||
|
# Arrange
|
||||||
var p := _make_player()
|
var p := _make_player()
|
||||||
p.set("_idle_anchor_active", true)
|
p.set("_idle_anchor_active", true)
|
||||||
var goal := Vector3(10.0, 0.0, 3.0)
|
var goal := Vector3(10.0, 0.0, 3.0)
|
||||||
|
# Act
|
||||||
p.set_authoritative_nav_goal(goal)
|
p.set_authoritative_nav_goal(goal)
|
||||||
|
# Assert
|
||||||
assert_that(p.get("_has_walk_goal")).is_true()
|
assert_that(p.get("_has_walk_goal")).is_true()
|
||||||
assert_that(p.get("_idle_anchor_active")).is_false()
|
assert_that(p.get("_idle_anchor_active")).is_false()
|
||||||
assert_that(p.get("_auth_walk_goal")).is_equal(goal)
|
assert_that(p.get("_auth_walk_goal")).is_equal(goal)
|
||||||
|
|
@ -37,24 +43,33 @@ func test_set_authoritative_nav_goal_sets_goal_and_target() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_set_locomotion_wish_world_xz_normalizes_horizontal() -> void:
|
func test_set_locomotion_wish_world_xz_normalizes_horizontal() -> void:
|
||||||
|
# Arrange
|
||||||
var p := _make_player()
|
var p := _make_player()
|
||||||
|
# Act
|
||||||
p.call("set_locomotion_wish_world_xz", Vector3(3.0, 9.0, 4.0))
|
p.call("set_locomotion_wish_world_xz", Vector3(3.0, 9.0, 4.0))
|
||||||
|
# Assert
|
||||||
var w: Vector3 = p.get("_locomotion_wish_world_xz") as Vector3
|
var w: Vector3 = p.get("_locomotion_wish_world_xz") as Vector3
|
||||||
assert_that(w.y).is_equal(0.0)
|
assert_that(w.y).is_equal(0.0)
|
||||||
assert_that(absf(w.length_squared() - 1.0)).is_less(0.0001)
|
assert_that(absf(w.length_squared() - 1.0)).is_less(0.0001)
|
||||||
|
|
||||||
|
|
||||||
func test_set_locomotion_wish_world_xz_zero_for_near_zero() -> void:
|
func test_set_locomotion_wish_world_xz_zero_for_near_zero() -> void:
|
||||||
|
# Arrange
|
||||||
var p := _make_player()
|
var p := _make_player()
|
||||||
|
# Act
|
||||||
p.call("set_locomotion_wish_world_xz", Vector3(0.0, 1.0, 0.0))
|
p.call("set_locomotion_wish_world_xz", Vector3(0.0, 1.0, 0.0))
|
||||||
|
# Assert
|
||||||
assert_that(p.get("_locomotion_wish_world_xz") as Vector3).is_equal(Vector3.ZERO)
|
assert_that(p.get("_locomotion_wish_world_xz") as Vector3).is_equal(Vector3.ZERO)
|
||||||
|
|
||||||
|
|
||||||
func test_clear_nav_goal_clears_velocity_and_nav_target() -> void:
|
func test_clear_nav_goal_clears_velocity_and_nav_target() -> void:
|
||||||
|
# Arrange
|
||||||
var p := _make_player()
|
var p := _make_player()
|
||||||
p.velocity = Vector3(1.0, 0.0, 0.0)
|
p.velocity = Vector3(1.0, 0.0, 0.0)
|
||||||
p.set_authoritative_nav_goal(Vector3(5.0, 0.0, 5.0))
|
p.set_authoritative_nav_goal(Vector3(5.0, 0.0, 5.0))
|
||||||
|
# Act
|
||||||
p.clear_nav_goal()
|
p.clear_nav_goal()
|
||||||
|
# Assert
|
||||||
assert_that(p.velocity).is_equal(Vector3.ZERO)
|
assert_that(p.velocity).is_equal(Vector3.ZERO)
|
||||||
assert_that(p.get("_has_walk_goal")).is_false()
|
assert_that(p.get("_has_walk_goal")).is_false()
|
||||||
assert_that(p.get("_idle_anchor_active")).is_false()
|
assert_that(p.get("_idle_anchor_active")).is_false()
|
||||||
|
|
@ -63,59 +78,83 @@ func test_clear_nav_goal_clears_velocity_and_nav_target() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_nav_goal_lifecycle_resets_vert_route_latch() -> void:
|
func test_nav_goal_lifecycle_resets_vert_route_latch() -> void:
|
||||||
|
# Arrange
|
||||||
var p := _make_player()
|
var p := _make_player()
|
||||||
|
# Act
|
||||||
p.set_authoritative_nav_goal(Vector3(1.0, 0.0, 2.0))
|
p.set_authoritative_nav_goal(Vector3(1.0, 0.0, 2.0))
|
||||||
p.set("_walk_vert_route_latched", true)
|
p.set("_walk_vert_route_latched", true)
|
||||||
p.clear_nav_goal()
|
p.clear_nav_goal()
|
||||||
|
# Assert
|
||||||
assert_that(p.get("_walk_vert_route_latched")).is_false()
|
assert_that(p.get("_walk_vert_route_latched")).is_false()
|
||||||
|
# Act
|
||||||
p.set("_walk_vert_route_latched", true)
|
p.set("_walk_vert_route_latched", true)
|
||||||
p.set_authoritative_nav_goal(Vector3(3.0, 0.0, 4.0))
|
p.set_authoritative_nav_goal(Vector3(3.0, 0.0, 4.0))
|
||||||
|
# Assert
|
||||||
assert_that(p.get("_walk_vert_route_latched")).is_false()
|
assert_that(p.get("_walk_vert_route_latched")).is_false()
|
||||||
|
# Act
|
||||||
p.set("_walk_vert_route_latched", true)
|
p.set("_walk_vert_route_latched", true)
|
||||||
p.snap_to_server(Vector3(0.0, 0.9, 0.0))
|
p.snap_to_server(Vector3(0.0, 0.9, 0.0))
|
||||||
|
# Assert
|
||||||
assert_that(p.get("_walk_vert_route_latched")).is_false()
|
assert_that(p.get("_walk_vert_route_latched")).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_hold_idle_anchor_sets_anchor_first_then_clamps_xz() -> void:
|
func test_hold_idle_anchor_sets_anchor_first_then_clamps_xz() -> void:
|
||||||
|
# Arrange
|
||||||
var p := _make_player()
|
var p := _make_player()
|
||||||
p.global_position = Vector3(1.0, 0.5, 2.0)
|
p.global_position = Vector3(1.0, 0.5, 2.0)
|
||||||
|
# Act
|
||||||
p.call("_hold_idle_anchor")
|
p.call("_hold_idle_anchor")
|
||||||
|
# Assert
|
||||||
assert_that(p.get("_idle_anchor_active")).is_true()
|
assert_that(p.get("_idle_anchor_active")).is_true()
|
||||||
assert_that(p.get("_idle_anchor_xz")).is_equal(Vector2(1.0, 2.0))
|
assert_that(p.get("_idle_anchor_xz")).is_equal(Vector2(1.0, 2.0))
|
||||||
|
# Act
|
||||||
p.global_position = Vector3(1.2, 0.7, 2.3)
|
p.global_position = Vector3(1.2, 0.7, 2.3)
|
||||||
p.call("_hold_idle_anchor")
|
p.call("_hold_idle_anchor")
|
||||||
|
# Assert
|
||||||
assert_that(p.global_position).is_equal(Vector3(1.0, 0.5, 2.0))
|
assert_that(p.global_position).is_equal(Vector3(1.0, 0.5, 2.0))
|
||||||
|
|
||||||
|
|
||||||
func test_snap_to_server_clears_idle_anchor() -> void:
|
func test_snap_to_server_clears_idle_anchor() -> void:
|
||||||
|
# Arrange
|
||||||
var p := _make_player()
|
var p := _make_player()
|
||||||
p.set("_idle_anchor_active", true)
|
p.set("_idle_anchor_active", true)
|
||||||
p.set("_idle_anchor_xz", Vector2(9.0, 9.0))
|
p.set("_idle_anchor_xz", Vector2(9.0, 9.0))
|
||||||
|
# Act
|
||||||
p.snap_to_server(Vector3(3.0, 0.9, 4.0))
|
p.snap_to_server(Vector3(3.0, 0.9, 4.0))
|
||||||
|
# Assert
|
||||||
assert_that(p.get("_idle_anchor_active")).is_false()
|
assert_that(p.get("_idle_anchor_active")).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_capsule_feet_y_uses_body_origin_minus_half_height() -> void:
|
func test_capsule_feet_y_uses_body_origin_minus_half_height() -> void:
|
||||||
|
# Arrange
|
||||||
|
# Act
|
||||||
var feet_y: float = PLAYER_SCRIPT.capsule_feet_y(0.545678, 0.5)
|
var feet_y: float = PLAYER_SCRIPT.capsule_feet_y(0.545678, 0.5)
|
||||||
|
# Assert
|
||||||
assert_that(absf(feet_y - 0.045678)).is_less(0.000001)
|
assert_that(absf(feet_y - 0.045678)).is_less(0.000001)
|
||||||
|
|
||||||
|
|
||||||
func test_vertical_arrival_error_uses_capsule_feet_height() -> void:
|
func test_vertical_arrival_error_uses_capsule_feet_height() -> void:
|
||||||
|
# Arrange
|
||||||
var goal_y := 0.045678
|
var goal_y := 0.045678
|
||||||
var body_origin_y := 0.545678
|
var body_origin_y := 0.545678
|
||||||
|
# Act
|
||||||
var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_origin_y, 0.5)
|
var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_origin_y, 0.5)
|
||||||
|
# Assert
|
||||||
assert_that(absf(err)).is_less(0.000001)
|
assert_that(absf(err)).is_less(0.000001)
|
||||||
|
|
||||||
|
|
||||||
func test_vertical_arrival_error_stays_large_for_origin_height_only_match() -> void:
|
func test_vertical_arrival_error_stays_large_for_origin_height_only_match() -> void:
|
||||||
|
# Arrange
|
||||||
var goal_y := 0.545678
|
var goal_y := 0.545678
|
||||||
var body_origin_y := 0.545678
|
var body_origin_y := 0.545678
|
||||||
|
# Act
|
||||||
var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_origin_y, 0.5)
|
var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_origin_y, 0.5)
|
||||||
|
# Assert
|
||||||
assert_that(absf(err - 0.5)).is_less(0.000001)
|
assert_that(absf(err - 0.5)).is_less(0.000001)
|
||||||
|
|
||||||
|
|
||||||
func test_arrival_vert_err_large_for_step_goal_from_floor() -> void:
|
func test_arrival_vert_err_large_for_step_goal_from_floor() -> void:
|
||||||
|
# Arrange
|
||||||
# Player at floor height (body Y=0.9). Goal at step surface (Y=0.3).
|
# Player at floor height (body Y=0.9). Goal at step surface (Y=0.3).
|
||||||
# Using actual capsule bottom (CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS) the error
|
# Using actual capsule bottom (CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS) the error
|
||||||
# must exceed VERT_ARRIVE_EPS so the arrival check does NOT fire prematurely.
|
# must exceed VERT_ARRIVE_EPS so the arrival check does NOT fire prematurely.
|
||||||
|
|
@ -128,11 +167,14 @@ func test_arrival_vert_err_large_for_step_goal_from_floor() -> void:
|
||||||
)
|
)
|
||||||
as float
|
as float
|
||||||
)
|
)
|
||||||
|
# Act
|
||||||
var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_y, full_half)
|
var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_y, full_half)
|
||||||
|
# Assert
|
||||||
assert_that(err).is_greater(PLAYER_SCRIPT.get("VERT_ARRIVE_EPS") as float)
|
assert_that(err).is_greater(PLAYER_SCRIPT.get("VERT_ARRIVE_EPS") as float)
|
||||||
|
|
||||||
|
|
||||||
func test_arrival_vert_err_zero_when_standing_on_step() -> void:
|
func test_arrival_vert_err_zero_when_standing_on_step() -> void:
|
||||||
|
# Arrange
|
||||||
# Player body centred at goal_y + total_half_height (i.e. actually standing on the step).
|
# Player body centred at goal_y + total_half_height (i.e. actually standing on the step).
|
||||||
# Error must be within VERT_ARRIVE_EPS so arrival fires correctly once the player is up.
|
# Error must be within VERT_ARRIVE_EPS so arrival fires correctly once the player is up.
|
||||||
var goal_y := 0.3
|
var goal_y := 0.3
|
||||||
|
|
@ -144,20 +186,29 @@ func test_arrival_vert_err_zero_when_standing_on_step() -> void:
|
||||||
as float
|
as float
|
||||||
)
|
)
|
||||||
var body_y: float = goal_y + full_half
|
var body_y: float = goal_y + full_half
|
||||||
|
# Act
|
||||||
var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_y, full_half)
|
var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_y, full_half)
|
||||||
|
# Assert
|
||||||
assert_that(err).is_less_equal(PLAYER_SCRIPT.get("VERT_ARRIVE_EPS") as float)
|
assert_that(err).is_less_equal(PLAYER_SCRIPT.get("VERT_ARRIVE_EPS") as float)
|
||||||
|
|
||||||
|
|
||||||
func test_flat_floor_goal_is_not_below_feet_height_margin() -> void:
|
func test_flat_floor_goal_is_not_below_feet_height_margin() -> void:
|
||||||
|
# Arrange
|
||||||
var goal_y := 0.0
|
var goal_y := 0.0
|
||||||
var body_origin_y := 0.4
|
var body_origin_y := 0.4
|
||||||
var descend_margin := PLAYER_SCRIPT.get("DESCEND_GOAL_Y_MARGIN") as float
|
var descend_margin := PLAYER_SCRIPT.get("DESCEND_GOAL_Y_MARGIN") as float
|
||||||
|
# Act
|
||||||
var below_feet_margin: bool = (
|
var below_feet_margin: bool = (
|
||||||
goal_y < PLAYER_SCRIPT.capsule_feet_y(body_origin_y, 0.5) - descend_margin
|
goal_y < PLAYER_SCRIPT.capsule_feet_y(body_origin_y, 0.5) - descend_margin
|
||||||
)
|
)
|
||||||
|
# Assert
|
||||||
assert_that(below_feet_margin).is_false()
|
assert_that(below_feet_margin).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_step_assist_max_surface_delta_matches_navigation_mesh_agent_max_climb() -> void:
|
func test_step_assist_max_surface_delta_matches_navigation_mesh_agent_max_climb() -> void:
|
||||||
|
# Arrange
|
||||||
# Keep in sync with NavigationMesh `agent_max_climb` in `scenes/main.tscn`.
|
# Keep in sync with NavigationMesh `agent_max_climb` in `scenes/main.tscn`.
|
||||||
assert_that(PLAYER_SCRIPT.get("WALK_STEP_ASSIST_MAX_SURFACE_DELTA") as float).is_equal(0.35)
|
# Act
|
||||||
|
var delta: float = PLAYER_SCRIPT.get("WALK_STEP_ASSIST_MAX_SURFACE_DELTA") as float
|
||||||
|
# Assert
|
||||||
|
assert_that(delta).is_equal(0.35)
|
||||||
|
|
|
||||||
|
|
@ -57,64 +57,83 @@ func _make_client(http_transport: Node) -> Node:
|
||||||
|
|
||||||
|
|
||||||
func test_sync_boot_get_200_emits_snap() -> void:
|
func test_sync_boot_get_200_emits_snap() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":1,"y":0.9,"z":-5}}')
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":1,"y":0.9,"z":-5}}')
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.sync_from_server()
|
c.sync_from_server()
|
||||||
|
# Assert
|
||||||
assert_signal(c).is_emitted("authoritative_position_received", Vector3(1.0, 0.9, -5.0), true)
|
assert_signal(c).is_emitted("authoritative_position_received", Vector3(1.0, 0.9, -5.0), true)
|
||||||
|
|
||||||
|
|
||||||
# NEO-24: boot sync also emits the cooldown-throttled heartbeat.
|
# NEO-24: boot sync also emits the cooldown-throttled heartbeat.
|
||||||
func test_sync_boot_get_200_emits_authoritative_ack() -> void:
|
func test_sync_boot_get_200_emits_authoritative_ack() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":1,"y":0.9,"z":-5}}')
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":1,"y":0.9,"z":-5}}')
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.sync_from_server()
|
c.sync_from_server()
|
||||||
|
# Assert
|
||||||
assert_signal(c).is_emitted("authoritative_ack", Vector3(1.0, 0.9, -5.0))
|
assert_signal(c).is_emitted("authoritative_ack", Vector3(1.0, 0.9, -5.0))
|
||||||
|
|
||||||
|
|
||||||
func test_sync_boot_get_200_malformed_position_does_not_emit() -> void:
|
func test_sync_boot_get_200_malformed_position_does_not_emit() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":"invalid"}')
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":"invalid"}')
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.sync_from_server()
|
c.sync_from_server()
|
||||||
|
# Assert
|
||||||
assert_signal(c).wait_until(400).is_not_emitted("authoritative_position_received")
|
assert_signal(c).wait_until(400).is_not_emitted("authoritative_position_received")
|
||||||
|
|
||||||
|
|
||||||
func test_move_stream_post_400_emits_move_rejected_then_boot_get() -> void:
|
func test_move_stream_post_400_emits_move_rejected_then_boot_get() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 400, '{"reasonCode":"vertical_step_exceeded"}')
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 400, '{"reasonCode":"vertical_step_exceeded"}')
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":0,"y":0.9,"z":0}}')
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":0,"y":0.9,"z":0}}')
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.submit_stream_targets([Vector3(1.0, 2.0, 3.0)])
|
c.submit_stream_targets([Vector3(1.0, 2.0, 3.0)])
|
||||||
|
# Assert
|
||||||
assert_signal(c).is_emitted("move_rejected", "vertical_step_exceeded")
|
assert_signal(c).is_emitted("move_rejected", "vertical_step_exceeded")
|
||||||
assert_signal(c).is_emitted("authoritative_position_received", Vector3(0.0, 0.9, 0.0), true)
|
assert_signal(c).is_emitted("authoritative_position_received", Vector3(0.0, 0.9, 0.0), true)
|
||||||
|
|
||||||
|
|
||||||
func test_move_stream_post_400_without_reason_emits_unknown() -> void:
|
func test_move_stream_post_400_without_reason_emits_unknown() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 400, "{}")
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 400, "{}")
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":1,"y":0.9,"z":1}}')
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, '{"position":{"x":1,"y":0.9,"z":1}}')
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.submit_stream_targets([Vector3.ZERO])
|
c.submit_stream_targets([Vector3.ZERO])
|
||||||
|
# Assert
|
||||||
assert_signal(c).is_emitted("move_rejected", "unknown")
|
assert_signal(c).is_emitted("move_rejected", "unknown")
|
||||||
assert_signal(c).is_emitted("authoritative_position_received", Vector3(1.0, 0.9, 1.0), true)
|
assert_signal(c).is_emitted("authoritative_position_received", Vector3(1.0, 0.9, 1.0), true)
|
||||||
|
|
||||||
|
|
||||||
func test_second_sync_while_busy_is_ignored() -> void:
|
func test_second_sync_while_busy_is_ignored() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := HangingHttpTransport.new()
|
var transport := HangingHttpTransport.new()
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.sync_from_server()
|
c.sync_from_server()
|
||||||
c.sync_from_server()
|
c.sync_from_server()
|
||||||
|
# Assert
|
||||||
assert_that(transport.request_count).is_equal(1)
|
assert_that(transport.request_count).is_equal(1)
|
||||||
|
|
||||||
|
|
||||||
func test_move_stream_post_200_does_not_emit_position() -> void:
|
func test_move_stream_post_200_does_not_emit_position() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS,
|
HTTPRequest.RESULT_SUCCESS,
|
||||||
|
|
@ -126,13 +145,16 @@ func test_move_stream_post_200_does_not_emit_position() -> void:
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.submit_stream_targets([Vector3(-4.9, 0.9, -5.0)])
|
c.submit_stream_targets([Vector3(-4.9, 0.9, -5.0)])
|
||||||
|
# Assert
|
||||||
assert_signal(c).wait_until(400).is_not_emitted("authoritative_position_received")
|
assert_signal(c).wait_until(400).is_not_emitted("authoritative_position_received")
|
||||||
|
|
||||||
|
|
||||||
# NEO-24: move-stream 200 must emit `authoritative_ack` so a held target lock can re-validate
|
# NEO-24: move-stream 200 must emit `authoritative_ack` so a held target lock can re-validate
|
||||||
# during normal locomotion (without re-introducing the snap rubber-band).
|
# during normal locomotion (without re-introducing the snap rubber-band).
|
||||||
func test_move_stream_post_200_emits_authoritative_ack() -> void:
|
func test_move_stream_post_200_emits_authoritative_ack() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS,
|
HTTPRequest.RESULT_SUCCESS,
|
||||||
|
|
@ -144,5 +166,7 @@ func test_move_stream_post_200_emits_authoritative_ack() -> void:
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.submit_stream_targets([Vector3(-4.9, 0.9, -5.0)])
|
c.submit_stream_targets([Vector3(-4.9, 0.9, -5.0)])
|
||||||
|
# Assert
|
||||||
assert_signal(c).is_emitted("authoritative_ack", Vector3(-4.9, 0.9, -5.0))
|
assert_signal(c).is_emitted("authoritative_ack", Vector3(-4.9, 0.9, -5.0))
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ func _select_response_json(
|
||||||
|
|
||||||
|
|
||||||
func test_tab_lock_change_emits_tab() -> void:
|
func test_tab_lock_change_emits_tab() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
|
|
@ -78,7 +79,9 @@ func test_tab_lock_change_emits_tab() -> void:
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
var events: Array = []
|
var events: Array = []
|
||||||
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
|
# Assert
|
||||||
assert_that(events.size()).is_equal(1)
|
assert_that(events.size()).is_equal(1)
|
||||||
var last: Dictionary = events[0] as Dictionary
|
var last: Dictionary = events[0] as Dictionary
|
||||||
assert_that(last.get("cause", "")).is_equal("tab")
|
assert_that(last.get("cause", "")).is_equal("tab")
|
||||||
|
|
@ -88,6 +91,7 @@ func test_tab_lock_change_emits_tab() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_clear_lock_change_emits_clear() -> void:
|
func test_clear_lock_change_emits_clear() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
|
|
@ -96,8 +100,10 @@ func test_clear_lock_change_emits_clear() -> void:
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
var events: Array = []
|
var events: Array = []
|
||||||
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
c.request_clear_target()
|
c.request_clear_target()
|
||||||
|
# Assert
|
||||||
assert_that(events.size()).is_equal(2)
|
assert_that(events.size()).is_equal(2)
|
||||||
assert_that(int((events[0] as Dictionary).get("sequence", -1))).is_equal(1)
|
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("cause", "")).is_equal("clear")
|
||||||
|
|
@ -107,6 +113,7 @@ func test_clear_lock_change_emits_clear() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_get_lock_id_change_emits_server_correction() -> void:
|
func test_get_lock_id_change_emits_server_correction() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
|
|
@ -115,8 +122,10 @@ func test_get_lock_id_change_emits_server_correction() -> void:
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
var events: Array = []
|
var events: Array = []
|
||||||
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
c.request_sync_from_server()
|
c.request_sync_from_server()
|
||||||
|
# Assert
|
||||||
assert_that(events.size()).is_equal(2)
|
assert_that(events.size()).is_equal(2)
|
||||||
var last: Dictionary = events[1] as Dictionary
|
var last: Dictionary = events[1] as Dictionary
|
||||||
assert_that(last.get("cause", "")).is_equal("server_correction")
|
assert_that(last.get("cause", "")).is_equal("server_correction")
|
||||||
|
|
@ -126,6 +135,7 @@ func test_get_lock_id_change_emits_server_correction() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_get_validity_only_change_emits_nothing() -> void:
|
func test_get_validity_only_change_emits_nothing() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
|
|
@ -136,14 +146,17 @@ func test_get_validity_only_change_emits_nothing() -> void:
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
var events: Array = []
|
var events: Array = []
|
||||||
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
c.request_sync_from_server()
|
c.request_sync_from_server()
|
||||||
|
# Assert
|
||||||
assert_that(events.size()).is_equal(1)
|
assert_that(events.size()).is_equal(1)
|
||||||
assert_that((events[0] as Dictionary).get("cause", "")).is_equal("tab")
|
assert_that((events[0] as Dictionary).get("cause", "")).is_equal("tab")
|
||||||
assert_that(int((events[0] as Dictionary).get("sequence", -1))).is_equal(1)
|
assert_that(int((events[0] as Dictionary).get("sequence", -1))).is_equal(1)
|
||||||
|
|
||||||
|
|
||||||
func test_denial_without_lock_id_change_emits_nothing() -> void:
|
func test_denial_without_lock_id_change_emits_nothing() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS,
|
HTTPRequest.RESULT_SUCCESS,
|
||||||
|
|
@ -153,5 +166,7 @@ func test_denial_without_lock_id_change_emits_nothing() -> void:
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
var events: Array = []
|
var events: Array = []
|
||||||
c.selection_event.connect(func(ev: Dictionary) -> void: events.append(ev.duplicate()))
|
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(ALPHA_ID)
|
||||||
|
# Assert
|
||||||
assert_that(events.size()).is_equal(0)
|
assert_that(events.size()).is_equal(0)
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ func _target_state_json(locked_id: String, validity: String, sequence: int) -> S
|
||||||
|
|
||||||
|
|
||||||
func test_move_stream_200_triggers_target_refresh_while_locked() -> void:
|
func test_move_stream_200_triggers_target_refresh_while_locked() -> void:
|
||||||
|
# Arrange
|
||||||
var auth_transport := MockHttpTransport.new()
|
var auth_transport := MockHttpTransport.new()
|
||||||
var target_transport := MockHttpTransport.new()
|
var target_transport := MockHttpTransport.new()
|
||||||
|
|
||||||
|
|
@ -111,15 +112,16 @@ func test_move_stream_200_triggers_target_refresh_while_locked() -> void:
|
||||||
# or handler without updating both ends.
|
# or handler without updating both ends.
|
||||||
authority.connect("authoritative_ack", Callable(target_client, "on_authoritative_ack"))
|
authority.connect("authoritative_ack", Callable(target_client, "on_authoritative_ack"))
|
||||||
|
|
||||||
# Establish the lock first.
|
# Act
|
||||||
target_client.request_select_target_id(ALPHA_ID)
|
target_client.request_select_target_id(ALPHA_ID)
|
||||||
|
# Assert
|
||||||
assert_that(target_client.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
assert_that(target_client.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
||||||
var get_count_baseline: int = target_transport.request_count
|
var get_count_baseline: int = target_transport.request_count
|
||||||
|
|
||||||
# Simulate WASD locomotion: authority POSTs move-stream, server echoes 200.
|
# Act
|
||||||
authority.submit_stream_targets([Vector3(-20.0, 0.9, -20.0)])
|
authority.submit_stream_targets([Vector3(-20.0, 0.9, -20.0)])
|
||||||
|
|
||||||
# The target client must have issued exactly one refresh GET in response to the ack.
|
# Assert
|
||||||
var new_requests: int = target_transport.request_count - get_count_baseline
|
var new_requests: int = target_transport.request_count - get_count_baseline
|
||||||
assert_that(new_requests).is_equal(1)
|
assert_that(new_requests).is_equal(1)
|
||||||
assert_that(target_transport.last_url).contains("/game/players/dev-local-1/target")
|
assert_that(target_transport.last_url).contains("/game/players/dev-local-1/target")
|
||||||
|
|
@ -130,6 +132,7 @@ func test_move_stream_200_triggers_target_refresh_while_locked() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_move_stream_200_does_not_refresh_without_lock() -> void:
|
func test_move_stream_200_does_not_refresh_without_lock() -> void:
|
||||||
|
# Arrange
|
||||||
var auth_transport := MockHttpTransport.new()
|
var auth_transport := MockHttpTransport.new()
|
||||||
var target_transport := MockHttpTransport.new()
|
var target_transport := MockHttpTransport.new()
|
||||||
|
|
||||||
|
|
@ -143,6 +146,8 @@ func test_move_stream_200_does_not_refresh_without_lock() -> void:
|
||||||
var target_client := _make_target_client(target_transport)
|
var target_client := _make_target_client(target_transport)
|
||||||
authority.connect("authoritative_ack", Callable(target_client, "on_authoritative_ack"))
|
authority.connect("authoritative_ack", Callable(target_client, "on_authoritative_ack"))
|
||||||
|
|
||||||
|
# Act
|
||||||
authority.submit_stream_targets([Vector3.ZERO])
|
authority.submit_stream_targets([Vector3.ZERO])
|
||||||
|
|
||||||
|
# Assert
|
||||||
assert_that(target_transport.request_count).is_equal(0)
|
assert_that(target_transport.request_count).is_equal(0)
|
||||||
|
|
|
||||||
|
|
@ -83,11 +83,14 @@ func _select_response_json(
|
||||||
|
|
||||||
|
|
||||||
func test_sync_get_200_emits_state() -> void:
|
func test_sync_get_200_emits_state() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(null, "none"))
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(null, "none"))
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.request_sync_from_server()
|
c.request_sync_from_server()
|
||||||
|
# Assert
|
||||||
assert_signal(c).is_emitted("target_state_changed")
|
assert_signal(c).is_emitted("target_state_changed")
|
||||||
var state: Dictionary = c.cached_state()
|
var state: Dictionary = c.cached_state()
|
||||||
assert_that(state.get("validity", "")).is_equal("none")
|
assert_that(state.get("validity", "")).is_equal("none")
|
||||||
|
|
@ -95,13 +98,16 @@ func test_sync_get_200_emits_state() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_tab_from_no_lock_selects_first_id() -> void:
|
func test_tab_from_no_lock_selects_first_id() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
monitor_signals(c)
|
monitor_signals(c)
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_url).contains("/target/select")
|
assert_that(transport.last_url).contains("/target/select")
|
||||||
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
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"' % ALPHA_ID)
|
||||||
|
|
@ -110,6 +116,7 @@ func test_tab_from_no_lock_selects_first_id() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_tab_from_alpha_requests_beta() -> void:
|
func test_tab_from_alpha_requests_beta() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
|
|
@ -118,13 +125,16 @@ func test_tab_from_alpha_requests_beta() -> void:
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, BETA_ID, "ok", 2)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, BETA_ID, "ok", 2)
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
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"' % BETA_ID)
|
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
||||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
||||||
|
|
||||||
|
|
||||||
func test_tab_wraps_from_beta_to_alpha() -> void:
|
func test_tab_wraps_from_beta_to_alpha() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
|
|
@ -136,14 +146,17 @@ func test_tab_wraps_from_beta_to_alpha() -> void:
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 3)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 3)
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
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(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
||||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
assert_that(c.cached_state().get("lockedTargetId")).is_equal(ALPHA_ID)
|
||||||
|
|
||||||
|
|
||||||
func test_denial_reflects_authoritative_target_state() -> void:
|
func test_denial_reflects_authoritative_target_state() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
# Server denies: selectionApplied=false, lockedTargetId=null, reasonCode=out_of_range.
|
# Server denies: selectionApplied=false, lockedTargetId=null, reasonCode=out_of_range.
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
|
|
@ -152,7 +165,9 @@ func test_denial_reflects_authoritative_target_state() -> void:
|
||||||
_select_response_json(false, null, "none", 0, "out_of_range")
|
_select_response_json(false, null, "none", 0, "out_of_range")
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.request_select_target_id(ALPHA_ID)
|
c.request_select_target_id(ALPHA_ID)
|
||||||
|
# Assert
|
||||||
var state: Dictionary = c.cached_state()
|
var state: Dictionary = c.cached_state()
|
||||||
assert_that(bool(state.get("selectionApplied", true))).is_false()
|
assert_that(bool(state.get("selectionApplied", true))).is_false()
|
||||||
assert_that(state.get("reasonCode", "")).is_equal("out_of_range")
|
assert_that(state.get("reasonCode", "")).is_equal("out_of_range")
|
||||||
|
|
@ -160,16 +175,20 @@ func test_denial_reflects_authoritative_target_state() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_clear_issues_post_without_target_id() -> void:
|
func test_clear_issues_post_without_target_id() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, null, "none", 4))
|
transport.enqueue(HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, null, "none", 4))
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.request_clear_target()
|
c.request_clear_target()
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_url).contains("/target/select")
|
assert_that(transport.last_url).contains("/target/select")
|
||||||
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
assert_that(transport.last_method).is_equal(HTTPClient.METHOD_POST)
|
||||||
assert_that(transport.last_body).not_contains("targetId")
|
assert_that(transport.last_body).not_contains("targetId")
|
||||||
|
|
||||||
|
|
||||||
func test_authoritative_ack_while_locked_fires_refresh_get() -> void:
|
func test_authoritative_ack_while_locked_fires_refresh_get() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
# Step 1: lock alpha via POST.
|
# Step 1: lock alpha via POST.
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
|
|
@ -180,22 +199,28 @@ func test_authoritative_ack_while_locked_fires_refresh_get() -> void:
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(ALPHA_ID, "out_of_range", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(ALPHA_ID, "out_of_range", 1)
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.request_select_target_id(ALPHA_ID)
|
c.request_select_target_id(ALPHA_ID)
|
||||||
var count_before_ack: int = transport.request_count
|
var count_before_ack: int = transport.request_count
|
||||||
c.on_authoritative_ack(Vector3.ZERO)
|
c.on_authoritative_ack(Vector3.ZERO)
|
||||||
|
# Assert
|
||||||
assert_that(transport.request_count).is_equal(count_before_ack + 1)
|
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("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(ALPHA_ID)
|
||||||
|
|
||||||
|
|
||||||
func test_authoritative_ack_without_lock_fires_nothing() -> void:
|
func test_authoritative_ack_without_lock_fires_nothing() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.on_authoritative_ack(Vector3.ZERO)
|
c.on_authoritative_ack(Vector3.ZERO)
|
||||||
|
# Assert
|
||||||
assert_that(transport.request_count).is_equal(0)
|
assert_that(transport.request_count).is_equal(0)
|
||||||
|
|
||||||
|
|
||||||
func test_two_acks_within_cooldown_collapse_to_one_get() -> void:
|
func test_two_acks_within_cooldown_collapse_to_one_get() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
|
|
@ -204,10 +229,12 @@ func test_two_acks_within_cooldown_collapse_to_one_get() -> void:
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(ALPHA_ID, "out_of_range", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _target_state_json(ALPHA_ID, "out_of_range", 1)
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.request_select_target_id(ALPHA_ID)
|
c.request_select_target_id(ALPHA_ID)
|
||||||
var count_before: int = transport.request_count
|
var count_before: int = transport.request_count
|
||||||
c.on_authoritative_ack(Vector3.ZERO)
|
c.on_authoritative_ack(Vector3.ZERO)
|
||||||
c.on_authoritative_ack(Vector3.ZERO)
|
c.on_authoritative_ack(Vector3.ZERO)
|
||||||
|
# Assert
|
||||||
assert_that(transport.request_count).is_equal(count_before + 1)
|
assert_that(transport.request_count).is_equal(count_before + 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -232,6 +259,7 @@ func _make_player(pos: Vector3) -> Node3D:
|
||||||
|
|
||||||
|
|
||||||
func test_select_post_kicks_freshness_stream_before_posting() -> void:
|
func test_select_post_kicks_freshness_stream_before_posting() -> void:
|
||||||
|
# Arrange
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
|
|
@ -242,7 +270,9 @@ func test_select_post_kicks_freshness_stream_before_posting() -> void:
|
||||||
add_child(authority)
|
add_child(authority)
|
||||||
var player := _make_player(Vector3(1.5, 0.5, -2.25))
|
var player := _make_player(Vector3(1.5, 0.5, -2.25))
|
||||||
c.set_freshness_kick(authority, player)
|
c.set_freshness_kick(authority, player)
|
||||||
|
# Act
|
||||||
c.request_select_target_id(ALPHA_ID)
|
c.request_select_target_id(ALPHA_ID)
|
||||||
|
# Assert
|
||||||
# Exactly one freshness submit, matching the pre-POST capsule position.
|
# Exactly one freshness submit, matching the pre-POST capsule position.
|
||||||
assert_that(authority.submit_count).is_equal(1)
|
assert_that(authority.submit_count).is_equal(1)
|
||||||
assert_that(authority.last_batch.size()).is_equal(1)
|
assert_that(authority.last_batch.size()).is_equal(1)
|
||||||
|
|
@ -252,6 +282,7 @@ func test_select_post_kicks_freshness_stream_before_posting() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_select_post_includes_position_hint_when_player_wired() -> void:
|
func test_select_post_includes_position_hint_when_player_wired() -> void:
|
||||||
|
# Arrange
|
||||||
# NEO-24 follow-up #5: the select POST body must carry the live capsule position as
|
# NEO-24 follow-up #5: the select POST body must carry the live capsule position as
|
||||||
# `positionHint` so the server's range check can bypass any stale stored snap.
|
# `positionHint` so the server's range check can bypass any stale stored snap.
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
|
|
@ -266,7 +297,9 @@ func test_select_post_includes_position_hint_when_player_wired() -> void:
|
||||||
# drift): 3.0 → "3", 0.5 → "0.5", 5.0 → "5". Re-parse the JSON for precise assertions.
|
# drift): 3.0 → "3", 0.5 → "0.5", 5.0 → "5". Re-parse the JSON for precise assertions.
|
||||||
var player := _make_player(Vector3(3.0, 0.5, 5.0))
|
var player := _make_player(Vector3(3.0, 0.5, 5.0))
|
||||||
c.set_freshness_kick(authority, player)
|
c.set_freshness_kick(authority, player)
|
||||||
|
# Act
|
||||||
c.request_select_target_id(BETA_ID)
|
c.request_select_target_id(BETA_ID)
|
||||||
|
# Assert
|
||||||
var parsed: Variant = JSON.parse_string(transport.last_body)
|
var parsed: Variant = JSON.parse_string(transport.last_body)
|
||||||
assert_that(parsed is Dictionary).is_true()
|
assert_that(parsed is Dictionary).is_true()
|
||||||
var body: Dictionary = parsed
|
var body: Dictionary = parsed
|
||||||
|
|
@ -279,6 +312,7 @@ func test_select_post_includes_position_hint_when_player_wired() -> void:
|
||||||
|
|
||||||
|
|
||||||
func test_select_post_without_freshness_wiring_is_a_noop() -> void:
|
func test_select_post_without_freshness_wiring_is_a_noop() -> void:
|
||||||
|
# Arrange
|
||||||
# Regression: tests that construct the client without `set_freshness_kick(...)` must
|
# Regression: tests that construct the client without `set_freshness_kick(...)` must
|
||||||
# still POST normally, *without* a `positionHint` (hint is opt-in per wiring).
|
# still POST normally, *without* a `positionHint` (hint is opt-in per wiring).
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
|
|
@ -286,7 +320,9 @@ func test_select_post_without_freshness_wiring_is_a_noop() -> void:
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.request_select_target_id(ALPHA_ID)
|
c.request_select_target_id(ALPHA_ID)
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_url).contains("/target/select")
|
assert_that(transport.last_url).contains("/target/select")
|
||||||
# No `positionHint` key in the body when no player is wired — re-parse so the assertion
|
# No `positionHint` key in the body when no player is wired — re-parse so the assertion
|
||||||
# is robust against JSON whitespace / key ordering.
|
# is robust against JSON whitespace / key ordering.
|
||||||
|
|
@ -297,6 +333,7 @@ func test_select_post_without_freshness_wiring_is_a_noop() -> void:
|
||||||
|
|
||||||
|
|
||||||
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_beta() -> void:
|
||||||
|
# Arrange
|
||||||
# Player stands next to beta (well outside alpha's ring). Without the range-aware
|
# 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.
|
# tab cycle the server denies with `out_of_range` on alpha — see NEO-24 follow-up #3.
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
|
|
@ -310,12 +347,15 @@ func test_tab_from_no_lock_skips_out_of_range_and_picks_beta() -> void:
|
||||||
# Near beta anchor (3, 3), ~1.4 m horizontal — inside 6 m. Far from alpha at (-3, -3).
|
# 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))
|
var player := _make_player(Vector3(4.0, 0.5, 4.0))
|
||||||
c.set_freshness_kick(authority, player)
|
c.set_freshness_kick(authority, player)
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
||||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
||||||
|
|
||||||
|
|
||||||
func test_tab_from_no_lock_with_nothing_in_range_falls_back_to_first_id() -> void:
|
func test_tab_from_no_lock_with_nothing_in_range_falls_back_to_first_id() -> void:
|
||||||
|
# Arrange
|
||||||
# No anchors are in range → cycle-order fallback. Server owns the denial reason.
|
# No anchors are in range → cycle-order fallback. Server owns the denial reason.
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
transport.enqueue(
|
transport.enqueue(
|
||||||
|
|
@ -329,12 +369,15 @@ func test_tab_from_no_lock_with_nothing_in_range_falls_back_to_first_id() -> voi
|
||||||
add_child(authority)
|
add_child(authority)
|
||||||
var player := _make_player(Vector3(20.0, 0.5, 20.0))
|
var player := _make_player(Vector3(20.0, 0.5, 20.0))
|
||||||
c.set_freshness_kick(authority, player)
|
c.set_freshness_kick(authority, player)
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
assert_that(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
||||||
assert_that(c.cached_state().get("reasonCode", "")).is_equal("out_of_range")
|
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_alpha_at_origin_swaps_to_beta_when_both_in_range() -> void:
|
||||||
|
# Arrange
|
||||||
# With alpha (-3,-3) and beta (3,3) sharing r=6, origin is in both rings. Locked to
|
# 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
|
# alpha, Tab should request beta (normal cycle behavior; range-aware skip-current
|
||||||
# does not interfere when the next id is also in range).
|
# does not interfere when the next id is also in range).
|
||||||
|
|
@ -351,13 +394,16 @@ func test_tab_from_locked_alpha_at_origin_swaps_to_beta_when_both_in_range() ->
|
||||||
add_child(authority)
|
add_child(authority)
|
||||||
var player := _make_player(Vector3(0.0, 0.5, 0.0))
|
var player := _make_player(Vector3(0.0, 0.5, 0.0))
|
||||||
c.set_freshness_kick(authority, player)
|
c.set_freshness_kick(authority, player)
|
||||||
|
# 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"' % BETA_ID)
|
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
||||||
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
assert_that(c.cached_state().get("lockedTargetId")).is_equal(BETA_ID)
|
||||||
|
|
||||||
|
|
||||||
func test_tab_when_only_current_lock_is_in_range_falls_back_to_cycle_for_denial() -> void:
|
func test_tab_when_only_current_lock_is_in_range_falls_back_to_cycle_for_denial() -> void:
|
||||||
|
# Arrange
|
||||||
# Locked to alpha, only alpha is in range — Tab should express a *swap* intent and
|
# Locked to alpha, only alpha is in range — Tab should express a *swap* intent and
|
||||||
# fall through to the plain cycle so the server denies with `out_of_range` (NEO-23
|
# fall through to the plain cycle so the server denies with `out_of_range` (NEO-23
|
||||||
# soft-lock rule). If the picker returned alpha instead, the user would see no
|
# soft-lock rule). If the picker returned alpha instead, the user would see no
|
||||||
|
|
@ -378,14 +424,17 @@ func test_tab_when_only_current_lock_is_in_range_falls_back_to_cycle_for_denial(
|
||||||
# Near alpha (-3, -3), ~1.4 m in — beta (3, 3) is ~8.5 m out.
|
# 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))
|
var player := _make_player(Vector3(-2.0, 0.5, -2.0))
|
||||||
c.set_freshness_kick(authority, player)
|
c.set_freshness_kick(authority, player)
|
||||||
|
# Act
|
||||||
c.request_select_target_id(ALPHA_ID)
|
c.request_select_target_id(ALPHA_ID)
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
assert_that(transport.last_body).contains('"targetId":"%s"' % BETA_ID)
|
||||||
assert_that(c.cached_state().get("reasonCode", "")).is_equal("out_of_range")
|
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(ALPHA_ID)
|
||||||
|
|
||||||
|
|
||||||
func test_tab_without_player_ref_still_uses_plain_cycle_order() -> void:
|
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(...)`
|
# 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. Alpha is requested from no-lock.
|
||||||
var transport := MockHttpTransport.new()
|
var transport := MockHttpTransport.new()
|
||||||
|
|
@ -393,5 +442,7 @@ func test_tab_without_player_ref_still_uses_plain_cycle_order() -> void:
|
||||||
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
HTTPRequest.RESULT_SUCCESS, 200, _select_response_json(true, ALPHA_ID, "ok", 1)
|
||||||
)
|
)
|
||||||
var c := _make_client(transport)
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
c.request_tab_next()
|
c.request_tab_next()
|
||||||
|
# Assert
|
||||||
assert_that(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
assert_that(transport.last_body).contains('"targetId":"%s"' % ALPHA_ID)
|
||||||
|
|
|
||||||
|
|
@ -5,65 +5,109 @@ const ZoomBandConfigScript := preload("res://scripts/zoom_band_config.gd")
|
||||||
|
|
||||||
|
|
||||||
func test_empty_bands_band_count_zero() -> void:
|
func test_empty_bands_band_count_zero() -> void:
|
||||||
|
# Arrange
|
||||||
var c = ZoomBandConfigScript.new()
|
var c = ZoomBandConfigScript.new()
|
||||||
c.band_distances = PackedFloat32Array()
|
c.band_distances = PackedFloat32Array()
|
||||||
assert_that(c.band_count()).is_equal(0)
|
# Act
|
||||||
|
var n: int = c.band_count()
|
||||||
|
# Assert
|
||||||
|
assert_that(n).is_equal(0)
|
||||||
|
|
||||||
|
|
||||||
func test_empty_bands_clamp_index_zero() -> void:
|
func test_empty_bands_clamp_index_zero() -> void:
|
||||||
|
# Arrange
|
||||||
var c = ZoomBandConfigScript.new()
|
var c = ZoomBandConfigScript.new()
|
||||||
c.band_distances = PackedFloat32Array()
|
c.band_distances = PackedFloat32Array()
|
||||||
assert_that(c.clamp_index(0)).is_equal(0)
|
# Act
|
||||||
assert_that(c.clamp_index(99)).is_equal(0)
|
var a: int = c.clamp_index(0)
|
||||||
|
var b: int = c.clamp_index(99)
|
||||||
|
# Assert
|
||||||
|
assert_that(a).is_equal(0)
|
||||||
|
assert_that(b).is_equal(0)
|
||||||
|
|
||||||
|
|
||||||
func test_empty_bands_distance_at_zero() -> void:
|
func test_empty_bands_distance_at_zero() -> void:
|
||||||
|
# Arrange
|
||||||
var c = ZoomBandConfigScript.new()
|
var c = ZoomBandConfigScript.new()
|
||||||
c.band_distances = PackedFloat32Array()
|
c.band_distances = PackedFloat32Array()
|
||||||
assert_that(c.distance_at(0)).is_equal(0.0)
|
# Act
|
||||||
|
var d: float = c.distance_at(0)
|
||||||
|
# Assert
|
||||||
|
assert_that(d).is_equal(0.0)
|
||||||
|
|
||||||
|
|
||||||
func test_single_band_clamp_always_zero() -> void:
|
func test_single_band_clamp_always_zero() -> void:
|
||||||
|
# Arrange
|
||||||
var c = ZoomBandConfigScript.new()
|
var c = ZoomBandConfigScript.new()
|
||||||
c.band_distances = PackedFloat32Array([42.0])
|
c.band_distances = PackedFloat32Array([42.0])
|
||||||
assert_that(c.clamp_index(-1)).is_equal(0)
|
# Act
|
||||||
assert_that(c.clamp_index(0)).is_equal(0)
|
var a: int = c.clamp_index(-1)
|
||||||
assert_that(c.clamp_index(9)).is_equal(0)
|
var b: int = c.clamp_index(0)
|
||||||
|
var cc: int = c.clamp_index(9)
|
||||||
|
# Assert
|
||||||
|
assert_that(a).is_equal(0)
|
||||||
|
assert_that(b).is_equal(0)
|
||||||
|
assert_that(cc).is_equal(0)
|
||||||
|
|
||||||
|
|
||||||
func test_multi_band_clamp_and_distance_at() -> void:
|
func test_multi_band_clamp_and_distance_at() -> void:
|
||||||
|
# Arrange
|
||||||
var c = ZoomBandConfigScript.new()
|
var c = ZoomBandConfigScript.new()
|
||||||
c.band_distances = PackedFloat32Array([10.0, 20.0, 30.0])
|
c.band_distances = PackedFloat32Array([10.0, 20.0, 30.0])
|
||||||
assert_that(c.clamp_index(-5)).is_equal(0)
|
# Act
|
||||||
assert_that(c.clamp_index(1)).is_equal(1)
|
var i0: int = c.clamp_index(-5)
|
||||||
assert_that(c.clamp_index(99)).is_equal(2)
|
var i1: int = c.clamp_index(1)
|
||||||
assert_that(c.distance_at(1)).is_equal(20.0)
|
var i2: int = c.clamp_index(99)
|
||||||
assert_that(c.distance_at(100)).is_equal(30.0)
|
var d1: float = c.distance_at(1)
|
||||||
|
var d2: float = c.distance_at(100)
|
||||||
|
# Assert
|
||||||
|
assert_that(i0).is_equal(0)
|
||||||
|
assert_that(i1).is_equal(1)
|
||||||
|
assert_that(i2).is_equal(2)
|
||||||
|
assert_that(d1).is_equal(20.0)
|
||||||
|
assert_that(d2).is_equal(30.0)
|
||||||
|
|
||||||
|
|
||||||
func test_default_index_clamped_via_clamp_index() -> void:
|
func test_default_index_clamped_via_clamp_index() -> void:
|
||||||
|
# Arrange
|
||||||
var c = ZoomBandConfigScript.new()
|
var c = ZoomBandConfigScript.new()
|
||||||
c.band_distances = PackedFloat32Array([1.0, 2.0, 3.0])
|
c.band_distances = PackedFloat32Array([1.0, 2.0, 3.0])
|
||||||
c.default_band_index = 99
|
c.default_band_index = 99
|
||||||
assert_that(c.clamp_index(c.default_band_index)).is_equal(2)
|
# Act
|
||||||
|
var idx: int = c.clamp_index(c.default_band_index)
|
||||||
|
# Assert
|
||||||
|
assert_that(idx).is_equal(2)
|
||||||
|
|
||||||
|
|
||||||
func test_all_band_distances_positive_vacuous_when_empty() -> void:
|
func test_all_band_distances_positive_vacuous_when_empty() -> void:
|
||||||
|
# Arrange
|
||||||
var c = ZoomBandConfigScript.new()
|
var c = ZoomBandConfigScript.new()
|
||||||
c.band_distances = PackedFloat32Array()
|
c.band_distances = PackedFloat32Array()
|
||||||
assert_that(c.all_band_distances_positive()).is_true()
|
# Act
|
||||||
|
var ok: bool = c.all_band_distances_positive()
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_all_band_distances_positive_true_when_all_positive() -> void:
|
func test_all_band_distances_positive_true_when_all_positive() -> void:
|
||||||
|
# Arrange
|
||||||
var c = ZoomBandConfigScript.new()
|
var c = ZoomBandConfigScript.new()
|
||||||
c.band_distances = PackedFloat32Array([1.0, 2.5, 10.0])
|
c.band_distances = PackedFloat32Array([1.0, 2.5, 10.0])
|
||||||
assert_that(c.all_band_distances_positive()).is_true()
|
# Act
|
||||||
|
var ok: bool = c.all_band_distances_positive()
|
||||||
|
# Assert
|
||||||
|
assert_that(ok).is_true()
|
||||||
|
|
||||||
|
|
||||||
func test_all_band_distances_positive_false_when_zero_or_negative() -> void:
|
func test_all_band_distances_positive_false_when_zero_or_negative() -> void:
|
||||||
|
# Arrange
|
||||||
var c0 = ZoomBandConfigScript.new()
|
var c0 = ZoomBandConfigScript.new()
|
||||||
c0.band_distances = PackedFloat32Array([10.0, 0.0, 20.0])
|
c0.band_distances = PackedFloat32Array([10.0, 0.0, 20.0])
|
||||||
assert_that(c0.all_band_distances_positive()).is_false()
|
|
||||||
var cn = ZoomBandConfigScript.new()
|
var cn = ZoomBandConfigScript.new()
|
||||||
cn.band_distances = PackedFloat32Array([5.0, -1.0])
|
cn.band_distances = PackedFloat32Array([5.0, -1.0])
|
||||||
assert_that(cn.all_band_distances_positive()).is_false()
|
# Act
|
||||||
|
var ok0: bool = c0.all_band_distances_positive()
|
||||||
|
var okn: bool = cn.all_band_distances_positive()
|
||||||
|
# Assert
|
||||||
|
assert_that(ok0).is_false()
|
||||||
|
assert_that(okn).is_false()
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ public sealed class HotbarLoadoutApiTests
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await client.GetAsync("/game/players/dev-local-1/hotbar-loadout");
|
var response = await client.GetAsync("/game/players/dev-local-1/hotbar-loadout");
|
||||||
var body = await response.Content.ReadFromJsonAsync<HotbarLoadoutResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await response.Content.ReadFromJsonAsync<HotbarLoadoutResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
Assert.Equal(HotbarLoadoutResponse.CurrentSchemaVersion, body!.SchemaVersion);
|
Assert.Equal(HotbarLoadoutResponse.CurrentSchemaVersion, body!.SchemaVersion);
|
||||||
|
|
@ -47,10 +47,10 @@ public sealed class HotbarLoadoutApiTests
|
||||||
new HotbarSlotBindingJson { SlotIndex = 3, AbilityId = PrototypeAbilityRegistry.PrototypeBurst },
|
new HotbarSlotBindingJson { SlotIndex = 3, AbilityId = PrototypeAbilityRegistry.PrototypeBurst },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
var postBody = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
|
||||||
var get = await client.GetFromJsonAsync<HotbarLoadoutResponse>("/game/players/dev-local-1/hotbar-loadout");
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var postBody = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
||||||
|
var get = await client.GetFromJsonAsync<HotbarLoadoutResponse>("/game/players/dev-local-1/hotbar-loadout");
|
||||||
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
||||||
Assert.NotNull(postBody);
|
Assert.NotNull(postBody);
|
||||||
Assert.True(postBody!.Updated);
|
Assert.True(postBody!.Updated);
|
||||||
|
|
@ -77,9 +77,9 @@ public sealed class HotbarLoadoutApiTests
|
||||||
SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
|
SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
|
||||||
Slots = [new HotbarSlotBindingJson { SlotIndex = 8, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
|
Slots = [new HotbarSlotBindingJson { SlotIndex = 8, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
|
||||||
});
|
});
|
||||||
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
Assert.False(body!.Updated);
|
Assert.False(body!.Updated);
|
||||||
|
|
@ -101,9 +101,9 @@ public sealed class HotbarLoadoutApiTests
|
||||||
SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
|
SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
|
||||||
Slots = [new HotbarSlotBindingJson { SlotIndex = -1, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
|
Slots = [new HotbarSlotBindingJson { SlotIndex = -1, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
|
||||||
});
|
});
|
||||||
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
Assert.False(body!.Updated);
|
Assert.False(body!.Updated);
|
||||||
|
|
@ -125,9 +125,9 @@ public sealed class HotbarLoadoutApiTests
|
||||||
SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
|
SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
|
||||||
Slots = [new HotbarSlotBindingJson { SlotIndex = 2, AbilityId = "missing_ability" }],
|
Slots = [new HotbarSlotBindingJson { SlotIndex = 2, AbilityId = "missing_ability" }],
|
||||||
});
|
});
|
||||||
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
Assert.False(body!.Updated);
|
Assert.False(body!.Updated);
|
||||||
|
|
@ -153,9 +153,9 @@ public sealed class HotbarLoadoutApiTests
|
||||||
new HotbarSlotBindingJson { SlotIndex = 1, AbilityId = PrototypeAbilityRegistry.PrototypeGuard },
|
new HotbarSlotBindingJson { SlotIndex = 1, AbilityId = PrototypeAbilityRegistry.PrototypeGuard },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
Assert.False(body!.Updated);
|
Assert.False(body!.Updated);
|
||||||
|
|
@ -177,9 +177,9 @@ public sealed class HotbarLoadoutApiTests
|
||||||
SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
|
SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
|
||||||
Slots = [new HotbarSlotBindingJson { SlotIndex = 5, AbilityId = " PROTOTYPE_DASH " }],
|
Slots = [new HotbarSlotBindingJson { SlotIndex = 5, AbilityId = " PROTOTYPE_DASH " }],
|
||||||
});
|
});
|
||||||
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
Assert.True(body!.Updated);
|
Assert.True(body!.Updated);
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,11 @@ public sealed class HotbarLoadoutPersistenceIntegrationTests(PostgresIntegration
|
||||||
postStatus = post.StatusCode;
|
postStatus = post.StatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
await using var secondFactory = new PostgresWebApplicationFactory();
|
await using var secondFactory = new PostgresWebApplicationFactory();
|
||||||
using var secondClient = secondFactory.CreateClient();
|
using var secondClient = secondFactory.CreateClient();
|
||||||
var loadout = await secondClient.GetFromJsonAsync<HotbarLoadoutResponse>(
|
var loadout = await secondClient.GetFromJsonAsync<HotbarLoadoutResponse>(
|
||||||
"/game/players/dev-local-1/hotbar-loadout");
|
"/game/players/dev-local-1/hotbar-loadout");
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(HttpStatusCode.OK, postStatus);
|
Assert.Equal(HttpStatusCode.OK, postStatus);
|
||||||
Assert.NotNull(loadout);
|
Assert.NotNull(loadout);
|
||||||
Assert.Equal(PrototypeAbilityRegistry.PrototypePulse, loadout!.Slots[1].AbilityId);
|
Assert.Equal(PrototypeAbilityRegistry.PrototypePulse, loadout!.Slots[1].AbilityId);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ public class InteractablesWorldApiTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetInteractables_ShouldReturnOrderedDescriptors_WithSchemaV1()
|
public async Task GetInteractables_ShouldReturnOrderedDescriptors_WithSchemaV1()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,9 @@ public class InteractionApiTests
|
||||||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||||
Target = new PositionVector { X = 3, Y = 0, Z = 0 },
|
Target = new PositionVector { X = 3, Y = 0, Z = 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
|
||||||
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
||||||
|
|
||||||
|
// Act
|
||||||
var response = await client.PostAsJsonAsync(
|
var response = await client.PostAsJsonAsync(
|
||||||
"/game/players/dev-local-1/interact",
|
"/game/players/dev-local-1/interact",
|
||||||
new InteractionRequest
|
new InteractionRequest
|
||||||
|
|
@ -32,9 +31,9 @@ public class InteractionApiTests
|
||||||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||||
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
||||||
});
|
});
|
||||||
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
|
|
@ -43,7 +42,8 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldAllow_WhenPlayerInHorizontalRange()
|
public async Task PostInteract_ShouldAllow_WhenPlayerInHorizontalRange()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -52,10 +52,7 @@ public class InteractionApiTests
|
||||||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||||
Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
|
Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
|
||||||
};
|
};
|
||||||
// Act
|
|
||||||
var moveResp = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
var moveResp = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
||||||
// Assert
|
|
||||||
Assert.Equal(HttpStatusCode.OK, moveResp.StatusCode);
|
|
||||||
|
|
||||||
var req = new InteractionRequest
|
var req = new InteractionRequest
|
||||||
{
|
{
|
||||||
|
|
@ -63,8 +60,11 @@ public class InteractionApiTests
|
||||||
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
|
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, moveResp.StatusCode);
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
|
|
@ -77,7 +77,8 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldDenyOutOfRange_WhenPlayerSeededFarFromTerminal()
|
public async Task PostInteract_ShouldDenyOutOfRange_WhenPlayerSeededFarFromTerminal()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -100,7 +101,8 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldDenyUnknownInteractable_WhenIdNotInRegistry()
|
public async Task PostInteract_ShouldDenyUnknownInteractable_WhenIdNotInRegistry()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -132,8 +134,6 @@ public class InteractionApiTests
|
||||||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||||
Target = new PositionVector { X = 0, Y = 0, Z = 0 },
|
Target = new PositionVector { X = 0, Y = 0, Z = 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
|
||||||
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
||||||
|
|
||||||
var req = new InteractionRequest
|
var req = new InteractionRequest
|
||||||
|
|
@ -142,10 +142,11 @@ public class InteractionApiTests
|
||||||
InteractableId = " PROTOTYPE_TERMINAL ",
|
InteractableId = " PROTOTYPE_TERMINAL ",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
|
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
|
||||||
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
|
|
@ -154,7 +155,8 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldReturnBadRequest_WhenSchemaVersionWrong()
|
public async Task PostInteract_ShouldReturnBadRequest_WhenSchemaVersionWrong()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -173,7 +175,8 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdMissing()
|
public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdMissing()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -191,7 +194,8 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdWhitespaceOnly()
|
public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdWhitespaceOnly()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -210,7 +214,8 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldReturnNotFound_WhenPlayerUnknown()
|
public async Task PostInteract_ShouldReturnNotFound_WhenPlayerUnknown()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -229,12 +234,12 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldReflectNewPosition_AfterMove()
|
public async Task PostInteract_ShouldReflectNewPosition_AfterMove()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
// Act
|
|
||||||
|
|
||||||
|
// Act
|
||||||
var far = await client.PostAsJsonAsync(
|
var far = await client.PostAsJsonAsync(
|
||||||
"/game/players/dev-local-1/interact",
|
"/game/players/dev-local-1/interact",
|
||||||
new InteractionRequest
|
new InteractionRequest
|
||||||
|
|
@ -242,18 +247,25 @@ public class InteractionApiTests
|
||||||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||||
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
||||||
});
|
});
|
||||||
var farBody = await far.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
||||||
// Assert
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, far.StatusCode);
|
||||||
|
var farBody = await far.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||||
Assert.False(farBody!.Allowed);
|
Assert.False(farBody!.Allowed);
|
||||||
Assert.Equal(InteractionApi.ReasonOutOfRange, farBody.ReasonCode);
|
Assert.Equal(InteractionApi.ReasonOutOfRange, farBody.ReasonCode);
|
||||||
|
|
||||||
|
// Act
|
||||||
var move = new MoveCommandRequest
|
var move = new MoveCommandRequest
|
||||||
{
|
{
|
||||||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||||
Target = new PositionVector { X = 0, Y = 0, Z = 0 },
|
Target = new PositionVector { X = 0, Y = 0, Z = 0 },
|
||||||
};
|
};
|
||||||
Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync("/game/players/dev-local-1/move", move)).StatusCode);
|
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
||||||
|
|
||||||
|
// Act
|
||||||
var near = await client.PostAsJsonAsync(
|
var near = await client.PostAsJsonAsync(
|
||||||
"/game/players/dev-local-1/interact",
|
"/game/players/dev-local-1/interact",
|
||||||
new InteractionRequest
|
new InteractionRequest
|
||||||
|
|
@ -261,6 +273,9 @@ public class InteractionApiTests
|
||||||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||||
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, near.StatusCode);
|
||||||
var nearBody = await near.Content.ReadFromJsonAsync<InteractionResponse>();
|
var nearBody = await near.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||||
Assert.True(nearBody!.Allowed);
|
Assert.True(nearBody!.Allowed);
|
||||||
}
|
}
|
||||||
|
|
@ -276,10 +291,9 @@ public class InteractionApiTests
|
||||||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||||
Target = new PositionVector { X = 12, Y = 0.9, Z = -6 },
|
Target = new PositionVector { X = 12, Y = 0.9, Z = -6 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
|
||||||
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
||||||
|
|
||||||
|
// Act
|
||||||
var response = await client.PostAsJsonAsync(
|
var response = await client.PostAsJsonAsync(
|
||||||
"/game/players/dev-local-1/interact",
|
"/game/players/dev-local-1/interact",
|
||||||
new InteractionRequest
|
new InteractionRequest
|
||||||
|
|
@ -287,9 +301,9 @@ public class InteractionApiTests
|
||||||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||||
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
|
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
|
||||||
});
|
});
|
||||||
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
|
|
@ -299,7 +313,8 @@ public class InteractionApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostInteract_ShouldDenyOutOfRange_ForResourceNode_WhenPlayerFar()
|
public async Task PostInteract_ShouldDenyOutOfRange_ForResourceNode_WhenPlayerFar()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
|
||||||
|
|
@ -25,18 +25,18 @@ public class MoveCommandApiTests
|
||||||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||||
Target = new PositionVector { X = 1.5, Y = 0.0, Z = 2.25 },
|
Target = new PositionVector { X = 1.5, Y = 0.0, Z = 2.25 },
|
||||||
};
|
};
|
||||||
|
var beforeMove = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var beforeMove = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
|
||||||
var postResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
var postResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
||||||
var postBody = await postResponse.Content.ReadFromJsonAsync<PositionStateResponse>();
|
|
||||||
var afterMove = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(beforeMove);
|
Assert.NotNull(beforeMove);
|
||||||
var seq0 = beforeMove!.Sequence;
|
var seq0 = beforeMove!.Sequence;
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.OK, postResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, postResponse.StatusCode);
|
||||||
|
var postBody = await postResponse.Content.ReadFromJsonAsync<PositionStateResponse>();
|
||||||
|
var afterMove = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||||
Assert.NotNull(postBody);
|
Assert.NotNull(postBody);
|
||||||
Assert.Equal(PositionStateResponse.CurrentSchemaVersion, postBody!.SchemaVersion);
|
Assert.Equal(PositionStateResponse.CurrentSchemaVersion, postBody!.SchemaVersion);
|
||||||
Assert.Equal("dev-local-1", postBody.PlayerId);
|
Assert.Equal("dev-local-1", postBody.PlayerId);
|
||||||
|
|
@ -110,7 +110,8 @@ public class MoveCommandApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostMove_ShouldReturn400WithReason_WhenHorizontalStepTooLarge()
|
public async Task PostMove_ShouldReturn400WithReason_WhenHorizontalStepTooLarge()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
||||||
{
|
{
|
||||||
|
|
@ -130,10 +131,9 @@ public class MoveCommandApiTests
|
||||||
Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
|
Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
|
||||||
};
|
};
|
||||||
// Act
|
// Act
|
||||||
|
|
||||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
||||||
// Assert
|
|
||||||
|
|
||||||
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
|
|
@ -143,7 +143,8 @@ public class MoveCommandApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostMove_ShouldReturn400WithReason_WhenVerticalStepTooLarge()
|
public async Task PostMove_ShouldReturn400WithReason_WhenVerticalStepTooLarge()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
||||||
{
|
{
|
||||||
|
|
@ -159,10 +160,9 @@ public class MoveCommandApiTests
|
||||||
Target = new PositionVector { X = -5, Y = 2.0, Z = -5 },
|
Target = new PositionVector { X = -5, Y = 2.0, Z = -5 },
|
||||||
};
|
};
|
||||||
// Act
|
// Act
|
||||||
|
|
||||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
||||||
// Assert
|
|
||||||
|
|
||||||
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
|
|
@ -171,7 +171,8 @@ public class MoveCommandApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostMove_ShouldReturn400WithoutReasonBody_WhenSchemaInvalid()
|
public async Task PostMove_ShouldReturn400WithoutReasonBody_WhenSchemaInvalid()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ public sealed class MoveStreamApiTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostMoveStream_ShouldApplyChainAndIncrementSequence_WhenTwoSmallSteps()
|
public async Task PostMoveStream_ShouldApplyChainAndIncrementSequence_WhenTwoSmallSteps()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
||||||
{
|
{
|
||||||
|
|
@ -26,12 +27,7 @@ public sealed class MoveStreamApiTests
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
// Act
|
|
||||||
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||||
// Assert
|
|
||||||
Assert.NotNull(before);
|
|
||||||
var seq0 = before!.Sequence;
|
|
||||||
|
|
||||||
var body = new MoveStreamRequest
|
var body = new MoveStreamRequest
|
||||||
{
|
{
|
||||||
SchemaVersion = MoveStreamRequest.CurrentSchemaVersion,
|
SchemaVersion = MoveStreamRequest.CurrentSchemaVersion,
|
||||||
|
|
@ -42,9 +38,13 @@ public sealed class MoveStreamApiTests
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
var postResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move-stream", body);
|
var postResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move-stream", body);
|
||||||
var postBody = await postResponse.Content.ReadFromJsonAsync<PositionStateResponse>();
|
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(before);
|
||||||
|
var seq0 = before!.Sequence;
|
||||||
|
var postBody = await postResponse.Content.ReadFromJsonAsync<PositionStateResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, postResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, postResponse.StatusCode);
|
||||||
Assert.NotNull(postBody);
|
Assert.NotNull(postBody);
|
||||||
Assert.Equal(seq0 + 2, postBody!.Sequence);
|
Assert.Equal(seq0 + 2, postBody!.Sequence);
|
||||||
|
|
@ -55,7 +55,8 @@ public sealed class MoveStreamApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostMoveStream_ShouldReturnNotFound_WhenPlayerIsUnknown()
|
public async Task PostMoveStream_ShouldReturnNotFound_WhenPlayerIsUnknown()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -74,7 +75,8 @@ public sealed class MoveStreamApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostMoveStream_ShouldReturn400_WhenTargetsEmpty()
|
public async Task PostMoveStream_ShouldReturn400_WhenTargetsEmpty()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -92,7 +94,8 @@ public sealed class MoveStreamApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostMoveStream_ShouldReturn400WithReason_WhenSecondStepExceedsHorizontalLimit()
|
public async Task PostMoveStream_ShouldReturn400WithReason_WhenSecondStepExceedsHorizontalLimit()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
||||||
{
|
{
|
||||||
|
|
@ -116,10 +119,9 @@ public sealed class MoveStreamApiTests
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
// Act
|
// Act
|
||||||
|
|
||||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move-stream", body);
|
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move-stream", body);
|
||||||
// Assert
|
|
||||||
|
|
||||||
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
var rej = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
var rej = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
||||||
Assert.NotNull(rej);
|
Assert.NotNull(rej);
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,16 @@ public sealed class PostgresPositionStateIntegrationTests(PostgresIntegrationHar
|
||||||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||||
Target = new PositionVector { X = 2, Y = 0.5, Z = 3.5 },
|
Target = new PositionVector { X = 2, Y = 0.5, Z = 3.5 },
|
||||||
};
|
};
|
||||||
|
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
|
||||||
var post = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
var post = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
||||||
var postBody = await post.Content.ReadFromJsonAsync<PositionStateResponse>();
|
|
||||||
var after = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(before);
|
Assert.NotNull(before);
|
||||||
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, post.StatusCode);
|
||||||
|
var postBody = await post.Content.ReadFromJsonAsync<PositionStateResponse>();
|
||||||
|
var after = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||||
Assert.NotNull(postBody);
|
Assert.NotNull(postBody);
|
||||||
Assert.NotNull(after);
|
Assert.NotNull(after);
|
||||||
Assert.Equal(PositionStateResponse.CurrentSchemaVersion, postBody!.SchemaVersion);
|
Assert.Equal(PositionStateResponse.CurrentSchemaVersion, postBody!.SchemaVersion);
|
||||||
|
|
@ -69,11 +69,10 @@ public sealed class PostgresPositionStateIntegrationTests(PostgresIntegrationHar
|
||||||
persisted = await post.Content.ReadFromJsonAsync<PositionStateResponse>();
|
persisted = await post.Content.ReadFromJsonAsync<PositionStateResponse>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assert
|
||||||
await using var secondFactory = new PostgresWebApplicationFactory();
|
await using var secondFactory = new PostgresWebApplicationFactory();
|
||||||
using var secondClient = secondFactory.CreateClient();
|
using var secondClient = secondFactory.CreateClient();
|
||||||
var after = await secondClient.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
var after = await secondClient.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(HttpStatusCode.OK, postStatus);
|
Assert.Equal(HttpStatusCode.OK, postStatus);
|
||||||
Assert.NotNull(persisted);
|
Assert.NotNull(persisted);
|
||||||
Assert.NotNull(after);
|
Assert.NotNull(after);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ public class TargetingApiTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetTarget_ShouldReturnNone_WhenNoLock()
|
public async Task GetTarget_ShouldReturnNone_WhenNoLock()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -32,7 +33,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldApplyLock_WhenInRange()
|
public async Task PostSelect_ShouldApplyLock_WhenInRange()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -59,7 +61,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldDenyUnknownTarget_WithReasonCode()
|
public async Task PostSelect_ShouldDenyUnknownTarget_WithReasonCode()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -85,7 +88,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldDenyOutOfRange_WhenBetaFarFromSpawn()
|
public async Task PostSelect_ShouldDenyOutOfRange_WhenBetaFarFromSpawn()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -172,23 +176,21 @@ public class TargetingApiTests
|
||||||
// Arrange
|
// Arrange
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
// Act
|
|
||||||
|
|
||||||
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||||
// Assert
|
|
||||||
Assert.NotNull(before);
|
|
||||||
|
|
||||||
Assert.Equal(
|
// Act
|
||||||
HttpStatusCode.OK,
|
var selectResponse = await client.PostAsJsonAsync(
|
||||||
(await client.PostAsJsonAsync(
|
|
||||||
"/game/players/dev-local-1/target/select",
|
"/game/players/dev-local-1/target/select",
|
||||||
new TargetSelectRequest
|
new TargetSelectRequest
|
||||||
{
|
{
|
||||||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||||
TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId,
|
TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId,
|
||||||
PositionHint = new PositionVector { X = 3.0, Y = 0.5, Z = 5.0 },
|
PositionHint = new PositionVector { X = 3.0, Y = 0.5, Z = 5.0 },
|
||||||
})).StatusCode);
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(before);
|
||||||
|
Assert.Equal(HttpStatusCode.OK, selectResponse.StatusCode);
|
||||||
var after = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
var after = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||||
Assert.NotNull(after);
|
Assert.NotNull(after);
|
||||||
Assert.Equal(before!.Position.X, after!.Position.X);
|
Assert.Equal(before!.Position.X, after!.Position.X);
|
||||||
|
|
@ -220,9 +222,9 @@ public class TargetingApiTests
|
||||||
$"{{\"schemaVersion\":{TargetSelectRequest.CurrentSchemaVersion}}}",
|
$"{{\"schemaVersion\":{TargetSelectRequest.CurrentSchemaVersion}}}",
|
||||||
Encoding.UTF8,
|
Encoding.UTF8,
|
||||||
"application/json"));
|
"application/json"));
|
||||||
var body = await clear.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await clear.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, setResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, setResponse.StatusCode);
|
||||||
Assert.Equal(HttpStatusCode.OK, clear.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, clear.StatusCode);
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
|
|
@ -256,9 +258,9 @@ public class TargetingApiTests
|
||||||
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
||||||
|
|
||||||
var get = await client.GetAsync("/game/players/dev-local-1/target");
|
var get = await client.GetAsync("/game/players/dev-local-1/target");
|
||||||
var body = await get.Content.ReadFromJsonAsync<PlayerTargetStateResponse>();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
var body = await get.Content.ReadFromJsonAsync<PlayerTargetStateResponse>();
|
||||||
Assert.Equal(HttpStatusCode.OK, selectResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, selectResponse.StatusCode);
|
||||||
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
|
||||||
Assert.Equal(HttpStatusCode.OK, get.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, get.StatusCode);
|
||||||
|
|
@ -269,7 +271,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetTarget_ShouldReturnNotFound_WhenPlayerUnknown()
|
public async Task GetTarget_ShouldReturnNotFound_WhenPlayerUnknown()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -282,7 +285,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldReturnNotFound_WhenPlayerUnknown()
|
public async Task PostSelect_ShouldReturnNotFound_WhenPlayerUnknown()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -302,7 +306,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldReturnBadRequest_WhenSchemaVersionWrong()
|
public async Task PostSelect_ShouldReturnBadRequest_WhenSchemaVersionWrong()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -322,7 +327,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldReturnBadRequest_WhenTargetIdWhitespaceOnly()
|
public async Task PostSelect_ShouldReturnBadRequest_WhenTargetIdWhitespaceOnly()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -342,7 +348,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldBeCaseInsensitive_ForTargetId()
|
public async Task PostSelect_ShouldBeCaseInsensitive_ForTargetId()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -366,7 +373,8 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldStayIdempotent_WhenSameTargetReselect()
|
public async Task PostSelect_ShouldStayIdempotent_WhenSameTargetReselect()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
|
|
@ -389,11 +397,10 @@ public class TargetingApiTests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PostSelect_ShouldDenyOutOfRange_WhenReselectingSameLockWhileOutOfRange()
|
public async Task PostSelect_ShouldDenyOutOfRange_WhenReselectingSameLockWhileOutOfRange()
|
||||||
{ // Arrange
|
{
|
||||||
|
// Arrange
|
||||||
await using var factory = new InMemoryWebApplicationFactory();
|
await using var factory = new InMemoryWebApplicationFactory();
|
||||||
var client = factory.CreateClient();
|
var client = factory.CreateClient();
|
||||||
// Act
|
|
||||||
|
|
||||||
await client.PostAsJsonAsync(
|
await client.PostAsJsonAsync(
|
||||||
"/game/players/dev-local-1/target/select",
|
"/game/players/dev-local-1/target/select",
|
||||||
|
|
@ -410,6 +417,7 @@ public class TargetingApiTests
|
||||||
};
|
};
|
||||||
await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
||||||
|
|
||||||
|
// Act
|
||||||
var response = await client.PostAsJsonAsync(
|
var response = await client.PostAsJsonAsync(
|
||||||
"/game/players/dev-local-1/target/select",
|
"/game/players/dev-local-1/target/select",
|
||||||
new TargetSelectRequest
|
new TargetSelectRequest
|
||||||
|
|
@ -417,8 +425,8 @@ public class TargetingApiTests
|
||||||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||||
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
|
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
|
||||||
});
|
});
|
||||||
// Assert
|
|
||||||
|
|
||||||
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
||||||
Assert.NotNull(body);
|
Assert.NotNull(body);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue