feat(empire): Add/update test cases for culture web and diplomacy interactions with unique test IDs

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-26 03:19:39 -07:00
parent 47a071015e
commit f7ab1b90d6
5 changed files with 192 additions and 0 deletions

View file

@ -0,0 +1 @@
uid://cbn8vt4kbgpef

View file

@ -0,0 +1,188 @@
extends GutTest
## Unit tests for Diplomacy GDScript wrapper and happiness.gd traded_luxuries extension.
##
## Diplomacy.process_turn() depends on GdTrade GDExtension — those tests are marked
## pending until the GDExtension surface lands. The helper methods (_apply_relation_changes,
## _apply_trade_changes, _collect_unique_luxury_ids) are pure and tested here without GdTrade.
const DiplomacyScript: GDScript = preload(
"res://engine/src/modules/empire/diplomacy.gd"
)
const HappinessScript: GDScript = preload(
"res://engine/src/modules/empire/happiness.gd"
)
const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
func _make_player(idx: int, is_human: bool = false) -> RefCounted:
var p: PlayerScript = PlayerScript.new()
p.index = idx
p.is_human = is_human
p.traded_luxuries = []
return p
func _get_traded(player: RefCounted) -> Array:
return player.get("traded_luxuries")
# ---------------------------------------------------------------------------
# _apply_relation_changes
# ---------------------------------------------------------------------------
func test_relation_change_writes_diplomacy_dict() -> void:
var changes: Array = [{"player_a": 0, "player_b": 1, "new_relation": "war"}]
var diplomacy: Dictionary = {}
# Patch GameState.diplomacy via a local dict — we call the private helper directly
# by calling the static via the loaded script class.
# Because _apply_relation_changes reads GameState.diplomacy directly, we test the
# key format it would produce rather than the autoload side-effect.
var key: String = "%d_%d" % [0, 1]
# Verify _relation_key produces the correct format.
assert_eq(DiplomacyScript._relation_key(0, 1), "0_1")
assert_eq(DiplomacyScript._relation_key(1, 0), "0_1", "min-max order must be consistent")
assert_eq(DiplomacyScript._relation_key(3, 5), "3_5")
assert_eq(DiplomacyScript._relation_key(5, 3), "3_5")
func test_relation_key_high_indices() -> void:
assert_eq(DiplomacyScript._relation_key(0, 10), "0_10")
assert_eq(DiplomacyScript._relation_key(10, 0), "0_10")
# ---------------------------------------------------------------------------
# _apply_trade_changes
# ---------------------------------------------------------------------------
func test_apply_trade_changes_populates_traded_luxuries() -> void:
var pa: RefCounted = _make_player(0)
var pb: RefCounted = _make_player(1)
var players: Array = [pa, pb]
var new_trades: Array = [
{"player_a": 0, "player_b": 1, "gives_a": "silk", "gives_b": "wine"}
]
var broken_trades: Array = []
DiplomacyScript._apply_trade_changes(players, new_trades, broken_trades)
# A gives silk → B receives silk
assert_true("silk" in _get_traded(pb), "B should receive silk from A")
# B gives wine → A receives wine
assert_true("wine" in _get_traded(pa), "A should receive wine from B")
func test_apply_trade_changes_clears_stale_luxuries() -> void:
var pa: RefCounted = _make_player(0)
pa.set("traded_luxuries", ["stale_gem"])
var pb: RefCounted = _make_player(1)
var players: Array = [pa, pb]
# No active trades this turn.
DiplomacyScript._apply_trade_changes(players, [], [])
assert_eq(_get_traded(pa).size(), 0, "stale traded luxury must be cleared")
func test_apply_trade_changes_no_duplicate_entries() -> void:
var pa: RefCounted = _make_player(0)
var pb: RefCounted = _make_player(1)
var players: Array = [pa, pb]
# Same luxury appears in two separate trades (edge case).
var new_trades: Array = [
{"player_a": 0, "player_b": 1, "gives_a": "silk", "gives_b": "wine"},
{"player_a": 0, "player_b": 1, "gives_a": "silk", "gives_b": "wine"},
]
DiplomacyScript._apply_trade_changes(players, new_trades, [])
var silk_count: int = 0
for luxury: String in _get_traded(pb):
if luxury == "silk":
silk_count += 1
assert_eq(silk_count, 1, "duplicate luxury from multiple trades must appear only once")
func test_apply_trade_changes_skips_invalid_indices() -> void:
var pa: RefCounted = _make_player(0)
var players: Array = [pa]
# Trade references player index 99 which doesn't exist.
var new_trades: Array = [
{"player_a": 0, "player_b": 99, "gives_a": "silk", "gives_b": "wine"}
]
DiplomacyScript._apply_trade_changes(players, new_trades, [])
# Should not crash; pa receives nothing because pb doesn't exist.
assert_eq(_get_traded(pa).size(), 0, "trade with missing partner must not crash or add luxuries")
# ---------------------------------------------------------------------------
# happiness.gd:_collect_unique_luxury_ids — traded_luxuries union
# ---------------------------------------------------------------------------
func _make_game_map_stub() -> RefCounted:
## Minimal stub: get_tile returns null for all positions.
return RefCounted.new()
func test_collect_unique_luxury_ids_includes_traded_luxuries() -> void:
var player: _PlayerShim = _PlayerShim.new()
player.traded_luxuries = ["silk", "wine"]
var result: Array[String] = HappinessScript._collect_unique_luxury_ids(player, _make_game_map_stub())
assert_true("silk" in result, "traded luxury 'silk' must appear in result")
assert_true("wine" in result, "traded luxury 'wine' must appear in result")
assert_eq(result.size(), 2, "result must contain exactly the 2 traded luxuries")
func test_collect_unique_luxury_ids_deduplicates_tile_and_trade() -> void:
var player: _PlayerShim = _PlayerShim.new()
player.traded_luxuries = ["diamond"]
# No cities (tile loop skipped) — diamond appears only via traded_luxuries.
# Duplicate would arise if diamond were also in owned_tiles; with cities=[] we
# test the traded-only dedup path, which is the safe headless route.
var result: Array[String] = HappinessScript._collect_unique_luxury_ids(player, _make_game_map_stub())
var diamond_count: int = 0
for id: String in result:
if id == "diamond":
diamond_count += 1
assert_eq(diamond_count, 1, "diamond must appear exactly once even if added via multiple paths")
func test_collect_unique_luxury_ids_empty_when_no_luxuries() -> void:
var player: _PlayerShim = _PlayerShim.new()
var result: Array[String] = HappinessScript._collect_unique_luxury_ids(player, _make_game_map_stub())
assert_eq(result.size(), 0, "result must be empty when player has no luxuries")
func test_collect_unique_luxury_ids_sorted() -> void:
var player: _PlayerShim = _PlayerShim.new()
player.traded_luxuries = ["wine", "gold_vein", "silk"]
var result: Array[String] = HappinessScript._collect_unique_luxury_ids(player, _make_game_map_stub())
assert_eq(result.size(), 3, "all 3 luxuries must be present")
assert_eq(result[0], "gold_vein", "first element must be 'gold_vein' (alphabetical)")
assert_eq(result[1], "silk", "second element must be 'silk'")
assert_eq(result[2], "wine", "third element must be 'wine'")
# ---------------------------------------------------------------------------
# GdTrade-dependent tests — pending until GDExtension surface lands
# ---------------------------------------------------------------------------
func test_process_turn_pending_gd_trade() -> void:
pending(
"Diplomacy.process_turn() requires GdTrade GDExtension (mc-trade crate)."
+ " Mark this test active once trade-rust-dev lands GdTrade."
)
# ---------------------------------------------------------------------------
# Inner helper class — minimal Player property shim for happiness tests
# ---------------------------------------------------------------------------
class _PlayerShim extends RefCounted:
var traded_luxuries: Array[String] = []
var cities: Array = []

View file

@ -0,0 +1 @@
uid://b5yc6dmj8rpx4

View file

@ -0,0 +1 @@
uid://csjskjvta1s4a

View file

@ -0,0 +1 @@
uid://cauoj7wwjhj25