Files
aetherbound-guild/runtime/tests/p9_1/foundation_atomicity_test.gd
T

231 lines
10 KiB
GDScript

extends SceneTree
const Contracts = preload("res://domain/p9_1/contracts.gd")
const FirstSessionServiceScript = preload("res://domain/p9_1/session_service.gd")
const FirstSessionStateScript = preload("res://domain/p9_1/session_state.gd")
var failures: Array[String] = []
var successful_commands := 0
var rejected_commands := 0
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
_test_success_matrix()
_test_rejection_matrix()
_test_exact_guild_ids()
if failures.is_empty():
print("ABG_P9_1_FOUNDATION_ATOMICITY_OK success=%d rejected=%d exact_guild_ids=2 legacy_leaks=0" % [successful_commands, rejected_commands])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _test_success_matrix() -> void:
_success(&"reset_transient")
_success(&"open_new_session")
_success(&"toggle_recruit", ["Hearthguard"], {"screen": Contracts.SCREEN_RECRUIT_SELECTION})
_success(&"review_formation", [], _formation_fields(Contracts.SCREEN_RECRUIT_SELECTION))
_success(&"revise_formation", [], {"screen": Contracts.SCREEN_FORMATION_REVIEW})
_success(&"commit_formation", [], _formation_fields(Contracts.SCREEN_FORMATION_REVIEW))
_success(&"begin_compat_first_battle", [], {"front_choice": "Hearthguard", "rear_choice": "Farstring"})
_success(&"open_adaptation", [], {"screen": Contracts.SCREEN_RESULT_1, "first_reward_committed": true})
_success(&"open_guild_response", [], {"screen": Contracts.SCREEN_RESULT_2, "adaptation_committed": true})
_success(&"open_route_board", [], {"screen": Contracts.SCREEN_RESULT_3, "guild_committed": true})
_success(&"open_growth", [], {"screen": Contracts.SCREEN_RESULT_4, "route_committed": true, "route_reward_committed": true})
_success(&"commit_reward", [&"battle_1", 20, Contracts.SCREEN_RESULT_1], {"screen": Contracts.SCREEN_RESULT_1})
_success(&"choose_adaptation", ["EQ-002"], {"screen": Contracts.SCREEN_ADAPTATION})
_success(&"commit_adaptation", [], {"screen": Contracts.SCREEN_ADAPTATION, "adaptation_choice": "EQ-002"})
_success(&"choose_guild_response", ["retain_loan"], {"screen": Contracts.SCREEN_GUILD_RESPONSE, "coin": 20})
_success(&"commit_guild_response", [], _guild_fields("retain_loan"))
_success(&"choose_route", ["R6-01"], {"screen": Contracts.SCREEN_ROUTE_BOARD})
_success(&"review_route", [], {"screen": Contracts.SCREEN_ROUTE_BOARD, "route_choice": "R6-01"})
_success(&"cancel_route_review", [], {"screen": Contracts.SCREEN_ROUTE_REVIEW})
_success(&"commit_route", [], {"screen": Contracts.SCREEN_ROUTE_REVIEW, "route_choice": "R6-01"})
_success(&"choose_growth", ["Hearthguard"], _growth_fields(Contracts.SCREEN_GROWTH))
_success(&"review_growth", [], _growth_fields(Contracts.SCREEN_GROWTH, "Hearthguard"))
_success(&"cancel_growth_review", [], _growth_fields(Contracts.SCREEN_GROWTH_REVIEW))
_success(&"commit_growth", [], _growth_fields(Contracts.SCREEN_GROWTH_REVIEW, "Hearthguard"))
_success(&"commit_impact", [Contracts.ENCOUNTER_1, 0, &"atomicity", {"delta": 0}], _battle_fields(Contracts.SCREEN_BATTLE_1, 0, true))
_success(&"resolve_impact", [Contracts.ENCOUNTER_1, 2], _battle_fields(Contracts.SCREEN_BATTLE_1, 2, true))
_success(&"resolve_compat_impact", [Contracts.ENCOUNTER_1, 2], _battle_fields(Contracts.SCREEN_BATTLE_1, 2, false))
_success(&"complete_phase", [Contracts.ENCOUNTER_1, 0], _battle_fields(Contracts.SCREEN_BATTLE_1, 0, true))
func _test_rejection_matrix() -> void:
_reject(&"toggle_recruit", ["Hearthguard"])
_reject(&"review_formation")
_reject(&"revise_formation")
_reject(&"commit_formation")
_reject(&"begin_compat_first_battle")
_reject(&"open_adaptation")
_reject(&"open_guild_response")
_reject(&"open_route_board")
_reject(&"open_growth")
_reject(&"commit_reward", [&"battle_1", 20, Contracts.SCREEN_RESULT_1])
_reject(&"choose_adaptation", ["EQ-002"])
_reject(&"commit_adaptation")
_reject(&"choose_guild_response", ["retain_loan"])
_reject(&"commit_guild_response")
_reject(&"choose_route", ["R6-01"])
_reject(&"review_route")
_reject(&"cancel_route_review")
_reject(&"commit_route")
_reject(&"choose_growth", ["Hearthguard"])
_reject(&"review_growth")
_reject(&"cancel_growth_review")
_reject(&"commit_growth")
_reject(&"commit_impact", [Contracts.ENCOUNTER_1, 0, &"atomicity", {}])
_reject(&"resolve_impact", [Contracts.ENCOUNTER_1, 2])
_reject(&"resolve_compat_impact", [Contracts.ENCOUNTER_1, 2])
_reject(&"complete_phase", [Contracts.ENCOUNTER_1, 0])
func _test_exact_guild_ids() -> void:
var pairs: Array[Array] = [
["Hearthguard", "Wayrunner"],
["Hearthguard", "Farstring"],
["Hearthguard", "Kindhand"],
["Wayrunner", "Farstring"],
["Wayrunner", "Kindhand"],
["Farstring", "Kindhand"],
]
for pair in pairs:
for adaptation in Contracts.ADAPTATION_IDS:
for guild_id in Contracts.GUILD_RESPONSE_IDS:
var service = _service(_guild_fields_for_pair(pair, adaptation, guild_id))
var before: Dictionary = service.snapshot()
_assert_success("choose_guild_response:%s:%s:%s" % [pair_key(pair), adaptation, guild_id], service, service.callv(&"choose_guild_response", [guild_id]), before)
before = service.snapshot()
_assert_success("commit_guild_response:%s:%s:%s" % [pair_key(pair), adaptation, guild_id], service, service.callv(&"commit_guild_response", []), before)
expect(service.state.guild_response == guild_id, "%s remains the committed exact Guild ID" % guild_id)
if guild_id == "retain_loan":
expect(service.state.added_rear.is_empty(), "%s retain_loan does not add a Recruit" % pair_key(pair))
expect(service.state.field_order == pair, "%s retain_loan preserves the ordered pair" % pair_key(pair))
else:
var expected_added := _expected_guild_recruit(pair, adaptation)
expect(service.state.added_rear == expected_added, "%s add_recruit maps actual=%s expected=%s front=%s rear=%s adaptation=%s" % [pair_key(pair), service.state.added_rear, expected_added, service.state.front_choice, service.state.rear_choice, adaptation])
expect(service.state.field_order == _ordered_pair_with(expected_added, pair), "%s add_recruit orders the expanded field" % pair_key(pair))
for legacy_id in ["retain_item", "recruit_rear"]:
var service = _service(_guild_fields_for_pair(["Hearthguard", "Farstring"], "EQ-002", ""))
var before: Dictionary = service.snapshot()
var result: Dictionary = service.choose_guild_response(legacy_id)
_assert_rejected("legacy_guild_id:%s" % legacy_id, service, before, result)
expect(service.state.guild_response.is_empty(), "%s does not leak into state" % legacy_id)
func _success(method: StringName, args: Array = [], fields: Dictionary = {}) -> void:
var service = _service(fields)
var before: Dictionary = service.snapshot()
var result: Dictionary = service.callv(method, args)
_assert_success(String(method), service, result, before)
func _reject(method: StringName, args: Array = [], fields: Dictionary = {}) -> void:
var service = _service(fields)
var before: Dictionary = service.snapshot()
var result: Dictionary = service.callv(method, args)
_assert_rejected(String(method), service, before, result)
func _assert_success(label: String, service, result: Dictionary, before: Dictionary) -> void:
var after: Dictionary = service.snapshot()
expect(result.ok, "%s succeeds" % label)
expect(after.revision == before.revision + 1, "%s revision delta is exactly 1, got %d" % [label, after.revision - before.revision])
expect(result.snapshot == after, "%s returns the committed snapshot" % label)
if result.ok:
successful_commands += 1
func _assert_rejected(label: String, service, before: Dictionary, result: Dictionary) -> void:
var after: Dictionary = service.snapshot()
expect(not result.ok, "%s rejects" % label)
expect(after.revision == before.revision, "%s preserves revision" % label)
expect(after == before, "%s preserves the complete snapshot" % label)
expect(result.snapshot == before, "%s returns the unchanged snapshot" % label)
expect(result.events.is_empty(), "%s emits no event" % label)
if not result.ok:
rejected_commands += 1
func _service(fields: Dictionary = {}):
var service = FirstSessionServiceScript.new(FirstSessionStateScript.new())
for field in fields:
service.state.set(field, fields[field])
return service
func _formation_fields(screen: StringName) -> Dictionary:
var selected: Array[String] = ["Hearthguard", "Farstring"]
return {"screen": screen, "selected_recruits": selected, "front_choice": "Hearthguard", "rear_choice": "Farstring"}
func _guild_fields(guild_id: String) -> Dictionary:
return _guild_fields_for_pair(["Hearthguard", "Farstring"], "EQ-002", guild_id)
func _guild_fields_for_pair(pair: Array, adaptation: String, guild_id: String) -> Dictionary:
var selected: Array[String] = [String(pair[0]), String(pair[1])]
return {
"screen": Contracts.SCREEN_GUILD_RESPONSE,
"coin": 20,
"guild_response": guild_id,
"adaptation_choice": adaptation,
"selected_recruits": selected,
"front_choice": selected[0],
"rear_choice": selected[1],
"initial_second_id": selected[1],
"marked_id": selected[1],
"field_order": selected.duplicate(),
}
func _expected_guild_recruit(pair: Array, adaptation: String) -> String:
return {
"Hearthguard:Wayrunner:EQ-002": "Farstring",
"Hearthguard:Wayrunner:EQ-061": "Kindhand",
"Hearthguard:Farstring:EQ-002": "Kindhand",
"Hearthguard:Farstring:EQ-061": "Kindhand",
"Hearthguard:Kindhand:EQ-002": "Farstring",
"Hearthguard:Kindhand:EQ-061": "Farstring",
"Wayrunner:Farstring:EQ-002": "Kindhand",
"Wayrunner:Farstring:EQ-061": "Kindhand",
"Wayrunner:Kindhand:EQ-002": "Farstring",
"Wayrunner:Kindhand:EQ-061": "Farstring",
"Farstring:Kindhand:EQ-002": "Hearthguard",
"Farstring:Kindhand:EQ-061": "Hearthguard",
}.get("%s:%s" % [pair_key(pair), adaptation], "")
func _ordered_pair_with(added_id: String, pair: Array) -> Array[String]:
var members: Array[String] = [String(pair[0]), String(pair[1]), added_id]
var ordered: Array[String] = []
for member_id in Contracts.AUTO_FORMATION_ORDER:
if member_id in members:
ordered.append(member_id)
return ordered
func pair_key(pair: Array) -> String:
return "%s:%s" % [String(pair[0]), String(pair[1])]
func _growth_fields(screen: StringName, member_id := "") -> Dictionary:
return {"screen": screen, "front_choice": "Hearthguard", "rear_choice": "Farstring", "growth_member_id": member_id}
func _battle_fields(screen: StringName, phase: int, playing: bool) -> Dictionary:
return {"screen": screen, "front_choice": "Hearthguard", "rear_choice": "Farstring", "battle_phase": phase, "battle_playing": playing}
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("P9.1 foundation atomicity: %s" % message)