Files
CosmicClash/Game/tests/cases/test_matchmaking_state.gd
T
2026-08-31 22:33:33 +01:00

67 lines
4.0 KiB
GDScript

extends "res://tests/test_case.gd"
const MatchmakingState = preload("res://scripts/matchmaking_state.gd")
func test_ticket_projection_accepts_ordered_updates_and_exposes_cancel() -> void:
var state := MatchmakingState.new()
assert_true(state.begin_queue("ticket-1", "ranked"), "valid queue starts in QUEUED")
assert_true(state.apply_ticket_update({"ticket_id": "ticket-1", "revision": 1, "state": "PROPOSED", "playlist": "ranked"}), "next revision applies")
assert_eq(state.phase, MatchmakingState.PROPOSED, "proposal is visible")
assert_true(state.can_cancel(), "authoritative cancel remains available before allocation")
func test_ticket_projection_rejects_gap_and_wrong_ticket_without_mutation() -> void:
var state := MatchmakingState.new()
state.begin_queue("ticket-1", "casual")
var resync_id := ""
state.resync_required.connect(func(id: String) -> void: resync_id = id)
assert_true(not state.apply_ticket_update({"ticket_id": "ticket-2", "revision": 1, "state": "PROPOSED"}), "another player's ticket is rejected")
assert_eq(resync_id, "ticket-1", "wrong resource requests recovery for current ticket")
assert_eq(state.phase, MatchmakingState.QUEUED, "invalid update cannot mutate phase")
assert_true(state.needs_resync, "invalid identity is visible to recovery")
state.needs_resync = false
assert_true(not state.apply_ticket_update({"ticket_id": "ticket-1", "revision": 3, "state": "ALLOCATING"}), "revision gap is rejected")
assert_eq(state.phase, MatchmakingState.QUEUED, "gap cannot skip authoritative state")
func test_duplicate_conflict_and_stale_updates_are_safe() -> void:
var state := MatchmakingState.new()
state.begin_queue("ticket-1", "casual")
var update := {"ticket_id": "ticket-1", "revision": 1, "state": "PROPOSED", "playlist": "casual", "expires_at_unix": 100}
assert_true(state.apply_ticket_update(update), "first update applies")
assert_true(state.apply_ticket_update(update), "identical duplicate is idempotent")
assert_true(not state.apply_ticket_update({"ticket_id": "ticket-1", "revision": 1, "state": "QUEUED", "playlist": "casual", "expires_at_unix": 100}), "same-revision conflict requests recovery")
assert_eq(state.phase, MatchmakingState.PROPOSED, "conflicting replay cannot rewind state")
assert_true(not state.apply_ticket_update({"ticket_id": "ticket-1", "revision": 0, "state": "QUEUED"}), "stale update is ignored")
assert_eq(state.phase, MatchmakingState.PROPOSED, "stale update cannot mutate state")
func test_proposal_terminal_states_are_visible_and_not_cancellable() -> void:
var state := MatchmakingState.new()
state.begin_queue("ticket-1", "casual")
assert_true(state.apply_proposal_update({"proposal_id": "proposal-1", "revision": 1, "state": "OPEN"}), "open proposal applies")
assert_eq(state.phase, MatchmakingState.PROPOSED, "open proposal is visible")
assert_true(state.apply_proposal_update({"proposal_id": "proposal-1", "revision": 2, "state": "DECLINED"}), "declined proposal applies")
assert_eq(state.phase, MatchmakingState.FAILED, "decline is terminal and visible")
assert_true(not state.can_cancel(), "terminal proposal cannot issue queue cancel")
var expired := MatchmakingState.new()
expired.begin_queue("ticket-2", "casual")
assert_true(expired.apply_proposal_update({"proposal_id": "proposal-2", "revision": 1, "state": "OPEN"}), "second proposal opens")
assert_true(expired.apply_proposal_update({"proposal_id": "proposal-2", "revision": 2, "state": "EXPIRED"}), "expired proposal applies")
assert_eq(expired.phase, MatchmakingState.EXPIRED, "expiry is visible")
assert_true(not expired.can_cancel(), "expired proposal cannot be cancelled")
func test_assignment_lifecycle_has_explicit_connecting_and_live_states() -> void:
var state := MatchmakingState.new()
state.begin_queue("ticket-1", "ranked")
state.mark_assignment_ready()
assert_eq(state.phase, MatchmakingState.ASSIGNMENT_READY, "assignment readiness is visible")
state.mark_connecting()
assert_eq(state.phase, MatchmakingState.CONNECTING, "transport connection is visible")
state.mark_live()
assert_eq(state.phase, MatchmakingState.LIVE, "live match is visible")