Files
aetherbound-guild/runtime/tests/p10_0/transaction_atomicity_test.gd
T

107 lines
7.9 KiB
GDScript

extends SceneTree
const Service = preload("res://p10_0/market_service.gd")
var failures: Array[String] = []
var rejected = 0
var applied = 0
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
var service = created_service()
var recruit_id = String(service.state.current_run.available_recruit_offer_ids[0])
var equipment_id = String(service.state.current_run.available_equipment_offer_ids[0])
var battle_id = String(service.state.current_run.available_battle_offer_ids[0])
var revision = service.state.revision
check_reject(service, func(): return service.hire("", revision, recruit_id, 14, false), "operation_id_required")
check_reject(service, func(): return service.hire("bad-stale", revision + 1, recruit_id, 14, false), "stale_revision")
check_reject(service, func(): return service.hire("bad-price", revision, recruit_id, 15, false), "price_mismatch")
check_reject(service, func(): return service.hire("bad-offer", revision, "missing", 14, false), "recruit_offer_unavailable")
check_reject(service, func(): return service.buy_equipment("bad-equip-price", revision, equipment_id, 999), "price_mismatch")
check_reject(service, func(): return service.buy_equipment("bad-equip-offer", revision, "missing", 12), "equipment_offer_unavailable")
check_reject(service, func(): return service.sell_equipment("bad-sell", revision, "missing", 0), "equipment_unavailable")
check_reject(service, func(): return service.equip("bad-target", revision, "missing", "missing", ""), "equipment_target_invalid")
check_reject(service, func(): return service.unequip("bad-unequip", revision, String(service.state.current_run.company[0]), "missing"), "equipped_item_mismatch")
check_reject(service, func(): return service.set_offer_lock("bad-lock-price", revision, recruit_id, true, 3), "price_mismatch")
check_reject(service, func(): return service.set_offer_lock("bad-lock-kind", revision, battle_id, true, 2), "offer_not_lockable")
check_reject(service, func(): return service.refresh_market("bad-refresh-price", revision, 999), "price_mismatch")
check_reject(service, func(): return service.commit_deployment("bad-deploy-empty", revision, []), "deployment_invalid")
check_reject(service, func(): return service.commit_deployment("bad-deploy-dupe", revision, [service.state.current_run.company[0], service.state.current_run.company[0]]), "deployment_invalid")
check_reject(service, func(): return service.commit_deployment("bad-deploy-unknown", revision, ["missing"]), "deployment_invalid")
check_reject(service, func(): return service.confirm_dismissal("bad-dismiss", revision, "missing", 0, 2, ""), "recruit_unavailable")
check_reject(service, func(): return service.commit_battle_offer("bad-board", revision, battle_id, "bad"), "board_hash_mismatch")
check_reject(service, func(): return service.commit_battle_offer("bad-battle", revision, "missing", String(service.state.current_run.market_board.board_hash)), "battle_offer_unavailable")
var before_bad_restore: Dictionary = service.state.snapshot()
var duplicate_deployment := before_bad_restore.duplicate(true)
duplicate_deployment.current_run.deployed_recruit_ids.append(duplicate_deployment.current_run.deployed_recruit_ids[0])
expect(not service.restore_snapshot(duplicate_deployment) and service.state.snapshot() == before_bad_restore, "duplicate deployed member snapshot rejects without replacing active state")
var bad_wearer := before_bad_restore.duplicate(true)
var first_instance: String = String(bad_wearer.current_run.inventory.keys()[0])
bad_wearer.current_run.inventory[first_instance].wearer_id = "missing"
expect(not service.restore_snapshot(bad_wearer) and service.state.snapshot() == before_bad_restore, "unknown equipment wearer snapshot rejects without replacing active state")
var locked = service.set_offer_lock("atomic-lock", service.state.revision, recruit_id, true, 2)
check_applied(service, locked, revision + 1, "lock success")
var after_lock = service.state.snapshot()
var duplicate = service.set_offer_lock("atomic-lock", revision, recruit_id, true, 2)
expect(duplicate.ok and duplicate.duplicate and service.state.snapshot() == after_lock, "same operation returns stored applied receipt")
check_reject(service, func(): return service.set_offer_lock("atomic-lock", revision, equipment_id, true, 2), "operation_id_conflict")
var no_coin = service.state.snapshot()
no_coin.current_run.coin = 0
expect(service.restore_snapshot(no_coin), "zero-Coin fixture remains valid")
check_reject(service, func(): return service.hire("no-coin-hire", service.state.revision, recruit_id, 14, false), "insufficient_coin")
check_reject(service, func(): return service.buy_equipment("no-coin-buy", service.state.revision, equipment_id, int(service.inspect_equipment_offer(equipment_id).receipt.list_price)), "insufficient_coin")
check_reject(service, func(): return service.refresh_market("no-coin-refresh", service.state.revision, Service.refresh_cost(0, 1)), "insufficient_coin")
var encounter_service = created_service()
var encounter_battle = String(encounter_service.state.current_run.available_battle_offer_ids[0])
expect(encounter_service.commit_battle_offer("enter-battle", encounter_service.state.revision, encounter_battle, String(encounter_service.state.current_run.market_board.board_hash)).ok, "fixture enters Encounter")
check_reject(encounter_service, func(): return encounter_service.hire("battle-hire", encounter_service.state.revision, String(encounter_service.state.current_run.available_recruit_offer_ids[0]), 14, false), "market_required")
check_reject(encounter_service, func(): return encounter_service.refresh_market("battle-refresh", encounter_service.state.revision, 6), "market_required")
check_reject(encounter_service, func(): return encounter_service.commit_deployment("battle-deploy", encounter_service.state.revision, Array(encounter_service.state.current_run.deployed_recruit_ids).duplicate()), "market_required")
if failures.is_empty():
print("ABG_P10_0_TRANSACTION_ATOMICITY_OK applied=%d rejected=%d malformed_restore=2 public_snapshot=preserved internal_fields=preserved stale=true price=true capacity=true eligibility=true conflict=true duplicate=true encounter_lock=true" % [applied, rejected])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func created_service():
var service = Service.new()
expect(service.begin_run_creation("fixture-begin", 0, "CTR-R1-01", "standard").ok, "fixture begin")
var draft_id = String(service.state.run_creation_draft.draft_id)
expect(service.materialize_opening_offers("fixture-materialize", service.state.revision, draft_id).ok, "fixture materialize")
var ids: Array[String] = []
for offer in service.state.run_creation_draft.opening_offers:
ids.append(String(offer.source_offer_id))
expect(service.set_opening_selection("fixture-select", service.state.revision, [ids[0], ids[1]]).ok, "fixture select")
expect(service.commit_run_creation("fixture-commit", service.state.revision, draft_id, 1, [ids[0], ids[1]], String(service.state.run_creation_draft.opening_offer_set_hash)).ok, "fixture commit")
return service
func check_reject(service, call: Callable, expected_code: String) -> void:
var before = service.state.snapshot()
var result: Dictionary = call.call()
expect(not result.ok and result.code == expected_code and service.state.snapshot() == before, "%s rejection preserves complete state; got %s" % [expected_code, str(result)])
if not result.ok and result.code == expected_code and service.state.snapshot() == before:
rejected += 1
func check_applied(service, result: Dictionary, expected_revision: int, label: String) -> void:
expect(result.ok and result.applied and service.state.revision == expected_revision, "%s increments exactly one revision" % label)
if result.ok and result.applied and service.state.revision == expected_revision:
applied += 1
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("P10.0 atomicity: %s" % message)