test(engine): ✅ Update lifecycle unit tests for courier behavior and coverage
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
0832067691
commit
5a3733aac4
1 changed files with 241 additions and 0 deletions
241
src/game/engine/tests/unit/test_courier_lifecycle.gd
Normal file
241
src/game/engine/tests/unit/test_courier_lifecycle.gd
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
extends GutTest
|
||||
## p3-01 bullet 10 — GUT headless tests for courier lifecycle.
|
||||
##
|
||||
## Six scenarios: route resolution, intercept, payment-vs-delivery,
|
||||
## tier upgrade, infrastructure severance, agreement expiry.
|
||||
##
|
||||
## All tests run headless (no display server). GdTrade, GdTradeLedger,
|
||||
## GdCourierRoute, GdOpenBordersAgreement, GdSharedMapAgreement, and
|
||||
## GdCourierMapView are GDExtension types instantiated via ClassDB.
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _courier_bindings_available() -> bool:
|
||||
## GdTradeLedger is the sentinel: it only exists once the courier
|
||||
## diplomacy bindings are compiled into the GDExtension.
|
||||
return ClassDB.class_exists("GdTradeLedger")
|
||||
|
||||
|
||||
func _make_trade() -> RefCounted:
|
||||
return ClassDB.instantiate("GdTrade") as RefCounted
|
||||
|
||||
|
||||
func _make_ledger() -> RefCounted:
|
||||
return ClassDB.instantiate("GdTradeLedger") as RefCounted
|
||||
|
||||
|
||||
func _make_map_view() -> RefCounted:
|
||||
## Headless map view: constructed from a default GdGameState handle.
|
||||
## step_shared_map_agreements calls route_intact / adamantine_echo_active
|
||||
## on the view; for agreement-lifecycle tests those are driven by
|
||||
## agreement state, not the real grid.
|
||||
var gs: RefCounted = ClassDB.instantiate("GdGameState") as RefCounted
|
||||
return ClassDB.instantiate("GdCourierMapView").call("from_game_state", gs)
|
||||
|
||||
|
||||
func _collect_events_of_type(events: Array, event_type: String) -> Array:
|
||||
var result: Array = []
|
||||
for ev: Dictionary in events:
|
||||
if ev.get("type", "") == event_type:
|
||||
result.append(ev)
|
||||
return result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Scenario 1 — Route resolution: higher courier tier produces shorter ETA
|
||||
## (era_2 tier 2 vs era_8 tier 8 on the same capital pair)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_higher_tier_produces_shorter_eta() -> void:
|
||||
if not _courier_bindings_available():
|
||||
pending("GdTradeLedger not found — courier GDExtension bindings not compiled in")
|
||||
return
|
||||
|
||||
var trade: RefCounted = _make_trade()
|
||||
|
||||
var route_slow: RefCounted = trade.call("courier_route_new", 0, 1)
|
||||
route_slow.call("set_courier_era_tier", 2)
|
||||
|
||||
var route_fast: RefCounted = trade.call("courier_route_new", 0, 1)
|
||||
route_fast.call("set_courier_era_tier", 8)
|
||||
|
||||
var eta_slow: int = route_slow.call("get_eta_turn")
|
||||
var eta_fast: int = route_fast.call("get_eta_turn")
|
||||
|
||||
assert_true(
|
||||
eta_fast <= eta_slow,
|
||||
"era_8 tier should produce equal-or-shorter ETA than era_2 tier"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Scenario 2 — Intercept: no map delivered; payment retained in agreement
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_courier_intercept_no_delivery_payment_retained() -> void:
|
||||
if not _courier_bindings_available():
|
||||
pending("GdTradeLedger not found — courier GDExtension bindings not compiled in")
|
||||
return
|
||||
|
||||
var trade: RefCounted = _make_trade()
|
||||
var ledger: RefCounted = _make_ledger()
|
||||
var map_view: RefCounted = _make_map_view()
|
||||
|
||||
var agreement: RefCounted = trade.call("shared_map_agreement_new", 0, 1, 50, 10)
|
||||
var route: RefCounted = trade.call("courier_route_new", 0, 1)
|
||||
route.call("force_intercepted")
|
||||
agreement.call("set_courier_route", route)
|
||||
ledger.call("add_shared_map", agreement)
|
||||
|
||||
var events: Array = trade.call("step_shared_map_agreements", ledger, map_view, 1)
|
||||
|
||||
var intercept_events: Array = _collect_events_of_type(events, "courier_intercepted")
|
||||
assert_eq(intercept_events.size(), 1, "exactly one courier_intercepted event expected")
|
||||
assert_eq(
|
||||
intercept_events[0].get("agreement_id"),
|
||||
agreement.call("get_agreement_id"),
|
||||
"intercepted event must carry the correct agreement_id"
|
||||
)
|
||||
|
||||
var deliver_events: Array = _collect_events_of_type(events, "shared_map_delivered")
|
||||
assert_eq(deliver_events.size(), 0, "no delivery event when courier intercepted")
|
||||
|
||||
assert_eq(agreement.call("get_payment_gold"), 50,
|
||||
"payment_gold must be retained in agreement after intercept")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Scenario 3 — Payment-vs-delivery: payment recorded at agreement creation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_payment_recorded_at_agreement_creation() -> void:
|
||||
if not _courier_bindings_available():
|
||||
pending("GdTradeLedger not found — courier GDExtension bindings not compiled in")
|
||||
return
|
||||
|
||||
var trade: RefCounted = _make_trade()
|
||||
|
||||
var ob: RefCounted = trade.call("open_borders_agreement_new", 0, 1, 80, 5)
|
||||
assert_eq(ob.call("get_payment_gold"), 80,
|
||||
"open_borders payment_gold must equal the value passed at creation")
|
||||
assert_eq(ob.call("get_player_a"), 0)
|
||||
assert_eq(ob.call("get_player_b"), 1)
|
||||
assert_eq(ob.call("get_turns_remaining"), 5)
|
||||
|
||||
var sm: RefCounted = trade.call("shared_map_agreement_new", 2, 3, 120, 8)
|
||||
assert_eq(sm.call("get_payment_gold"), 120,
|
||||
"shared_map payment_gold must equal the value passed at creation")
|
||||
assert_false(sm.call("is_delivered"),
|
||||
"shared_map must not be delivered at creation time")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Scenario 4 — Tier upgrade: era_8 ETA <= era_2 ETA after step
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_tier_upgrade_shrinks_eta() -> void:
|
||||
if not _courier_bindings_available():
|
||||
pending("GdTradeLedger not found — courier GDExtension bindings not compiled in")
|
||||
return
|
||||
|
||||
var trade: RefCounted = _make_trade()
|
||||
var map_view: RefCounted = _make_map_view()
|
||||
|
||||
var ledger_slow: RefCounted = _make_ledger()
|
||||
var sm_slow: RefCounted = trade.call("shared_map_agreement_new", 0, 1, 50, 20)
|
||||
var route_slow: RefCounted = trade.call("courier_route_new", 0, 1)
|
||||
route_slow.call("set_courier_era_tier", 2)
|
||||
sm_slow.call("set_courier_route", route_slow)
|
||||
ledger_slow.call("add_shared_map", sm_slow)
|
||||
|
||||
var ledger_fast: RefCounted = _make_ledger()
|
||||
var sm_fast: RefCounted = trade.call("shared_map_agreement_new", 0, 1, 50, 20)
|
||||
var route_fast: RefCounted = trade.call("courier_route_new", 0, 1)
|
||||
route_fast.call("set_courier_era_tier", 8)
|
||||
sm_fast.call("set_courier_route", route_fast)
|
||||
ledger_fast.call("add_shared_map", sm_fast)
|
||||
|
||||
trade.call("step_shared_map_agreements", ledger_slow, map_view, 1)
|
||||
trade.call("step_shared_map_agreements", ledger_fast, map_view, 1)
|
||||
|
||||
var eta_slow: int = route_slow.call("get_eta_turn")
|
||||
var eta_fast: int = route_fast.call("get_eta_turn")
|
||||
|
||||
assert_true(eta_fast <= eta_slow,
|
||||
"era_8 route ETA must be <= era_2 route ETA after first step")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Scenario 5 — Infrastructure severance: severed route → intercept on next step
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_infrastructure_severance_triggers_intercept() -> void:
|
||||
if not _courier_bindings_available():
|
||||
pending("GdTradeLedger not found — courier GDExtension bindings not compiled in")
|
||||
return
|
||||
|
||||
var trade: RefCounted = _make_trade()
|
||||
var ledger: RefCounted = _make_ledger()
|
||||
var map_view: RefCounted = _make_map_view()
|
||||
|
||||
var sm: RefCounted = trade.call("shared_map_agreement_new", 0, 1, 60, 15)
|
||||
var route: RefCounted = trade.call("courier_route_new", 0, 1)
|
||||
route.call("set_courier_era_tier", 7)
|
||||
route.call("mark_infrastructure_severed")
|
||||
sm.call("set_courier_route", route)
|
||||
ledger.call("add_shared_map", sm)
|
||||
|
||||
var events: Array = trade.call("step_shared_map_agreements", ledger, map_view, 3)
|
||||
|
||||
var intercept_events: Array = _collect_events_of_type(events, "courier_intercepted")
|
||||
assert_eq(intercept_events.size(), 1,
|
||||
"severed infrastructure must emit courier_intercepted on next step")
|
||||
assert_eq(
|
||||
intercept_events[0].get("agreement_id"),
|
||||
sm.call("get_agreement_id"),
|
||||
"intercepted event must carry the correct agreement_id"
|
||||
)
|
||||
assert_false(sm.call("is_delivered"),
|
||||
"agreement must not be delivered after infrastructure severance")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Scenario 6 — Agreement expiry: OpenBorders decrements and fires expiry event
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_open_borders_decrements_and_expires() -> void:
|
||||
if not _courier_bindings_available():
|
||||
pending("GdTradeLedger not found — courier GDExtension bindings not compiled in")
|
||||
return
|
||||
|
||||
var trade: RefCounted = _make_trade()
|
||||
var map_view: RefCounted = _make_map_view()
|
||||
|
||||
var ob: RefCounted = trade.call("open_borders_agreement_new", 0, 1, 40, 3)
|
||||
var agreement_id: int = ob.call("get_agreement_id")
|
||||
|
||||
var ledger: RefCounted = _make_ledger()
|
||||
ledger.call("add_open_borders", ob)
|
||||
|
||||
# Turn 1 — turns_remaining 3 → 2, no expiry yet.
|
||||
var events_t1: Array = trade.call("step_shared_map_agreements", ledger, map_view, 1)
|
||||
assert_eq(_collect_events_of_type(events_t1, "open_borders_expired").size(), 0,
|
||||
"no expiry event when turns_remaining > 0")
|
||||
var remaining_t1: int = ledger.call("iter_open_borders")[0].call("get_turns_remaining")
|
||||
assert_eq(remaining_t1, 2, "turns_remaining must decrement from 3 to 2")
|
||||
|
||||
# Turn 2 — turns_remaining 2 → 1.
|
||||
trade.call("step_shared_map_agreements", ledger, map_view, 2)
|
||||
var remaining_t2: int = ledger.call("iter_open_borders")[0].call("get_turns_remaining")
|
||||
assert_eq(remaining_t2, 1, "turns_remaining must decrement from 2 to 1")
|
||||
|
||||
# Turn 3 — turns_remaining 1 → 0; expiry event must fire.
|
||||
var events_t3: Array = trade.call("step_shared_map_agreements", ledger, map_view, 3)
|
||||
var expiry_events: Array = _collect_events_of_type(events_t3, "open_borders_expired")
|
||||
assert_eq(expiry_events.size(), 1,
|
||||
"open_borders_expired event must fire when turns_remaining reaches 0")
|
||||
assert_eq(expiry_events[0].get("agreement_id"), agreement_id,
|
||||
"expiry event must carry the correct agreement_id")
|
||||
Loading…
Add table
Reference in a new issue