Files
aetherbound-guild/runtime/tests/dev5/outcome_recovery_test.gd
T

148 lines
8.2 KiB
GDScript

extends SceneTree
const DraftService = preload("res://entry/initial_company_draft_service.gd")
const ContractService = preload("res://entry/guild_contract_service.gd")
const FormationService = preload("res://entry/formation_risk_service.gd")
const BattleService = preload("res://entry/battle_service.gd")
const DRAFT_PATH := "user://m12_outcome_draft.json"
const COMPANY_PATH := "user://m12_outcome_company.json"
const SEED := "2468ace013579bdf2468ace013579bdf"
const CONTRACT_OP := "abcdef0123456789abcdef0123456789"
const HIRE_OP_1 := "11112222333344445555666677778888"
const HIRE_OP_2 := "88887777666655554444333322221111"
const ORDER_OP := "fedcba9876543210fedcba9876543210"
const READY_OP := "0123456789abcdef0123456789abcdef"
const BATTLE_OP := "13579bdf02468ace13579bdf02468ace"
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
_cleanup()
var company := _create_prepared_company()
_expect(not company.is_empty(), "fixture prepares the first four-member Company")
if not company.is_empty():
var option_id := String(company.active_contract.formation.risk_options[1].risk_option_id)
var service := _battle_service()
var started: Dictionary = service.begin_battle(BATTLE_OP, option_id)
_expect(started.ok, "observation fixture starts one battle receipt")
if started.ok:
var opened: Dictionary = service.begin_observation(BATTLE_OP, String(started.battle.snapshot_hash))
_expect(opened.ok, "observation opens without mutating the outcome")
for _phase in range(9):
var preview: Dictionary = service.preview_observation_event(BATTLE_OP)
_expect(preview.ok, "phase preview remains deterministic")
if not preview.ok:
break
var event: Dictionary = preview.event
var committed: Dictionary = service.commit_observation_event(BATTLE_OP, String(event.event_id), String(event.event_hash))
_expect(committed.ok, "phase commit accepts the sourced event")
var locked: Dictionary = service.load_company()
_expect(locked.ok and String(locked.company.active_contract.battle_state.status) == "outcome_locked", "nine events stop at an immutable outcome lock")
var before_settle := _settlement_snapshot(locked.company)
var settled: Dictionary = service.settle_observation_outcome(BATTLE_OP)
_expect(settled.ok and settled.code == "outcome_settled", "OUT-001 settlement applies once")
if settled.ok:
var result: Dictionary = settled.company.active_contract.battle_state.result
var draft: Dictionary = settled.company.active_contract.battle_state.reward_draft
_expect(String(settled.company.active_contract.status) == "result", "settlement enters the Result state")
_expect(int(result.reward_coin) > 0 and int(result.xp_award) > 0, "settlement applies Coin and survivor XP")
_expect(String(draft.status) == "open" and draft.options.size() >= 2, "settlement opens a deterministic reward draft")
var duplicate_settle: Dictionary = service.settle_observation_outcome(BATTLE_OP)
_expect(duplicate_settle.ok and duplicate_settle.code == "outcome_already_settled", "duplicate settlement returns the original result")
_expect(duplicate_settle.ok and String(duplicate_settle.company.company_hash) == String(settled.company.company_hash), "duplicate settlement does not duplicate Coin, XP or history")
_expect(_settlement_snapshot(settled.company).coin > before_settle.coin, "battle Coin is paid before reward choice")
var chosen: Dictionary = draft.options[0]
var reward_op := ("%s|test-reward|%s" % [BATTLE_OP, String(chosen.reward_id)]).sha256_text().substr(0, 32)
var claimed: Dictionary = service.claim_reward(reward_op, String(chosen.reward_id), BATTLE_OP)
_expect(claimed.ok and claimed.code == "reward_claimed", "OUT-003 claims one sourced reward")
if claimed.ok:
var reward_receipts: Array = claimed.company.active_contract.get("reward_receipts", [])
_expect(reward_receipts.size() == 1, "reward claim writes one receipt")
var duplicate_claim: Dictionary = service.claim_reward(reward_op, String(chosen.reward_id), BATTLE_OP)
_expect(duplicate_claim.ok and duplicate_claim.code == "reward_already_applied", "duplicate reward claim returns the original receipt")
_expect(String(duplicate_claim.company.company_hash) == String(claimed.company.company_hash), "duplicate reward claim does not mutate the Company")
var recovery_op := ("%s|test-recovery" % BATTLE_OP).sha256_text().substr(0, 32)
var recovered: Dictionary = service.recover_next_stage(recovery_op)
_expect(recovered.ok and recovered.code == "recovered", "OUT-004 commits next-stage recovery")
if recovered.ok:
_expect(String(recovered.company.active_contract.status) == "market" and not recovered.company.active_contract.has("formation"), "recovery clears stale formation and opens Market")
_expect(recovered.company.active_contract.get("recovery_receipts", []).size() == 1, "recovery writes one receipt")
var duplicate_recovery: Dictionary = service.recover_next_stage(recovery_op)
_expect(duplicate_recovery.ok and duplicate_recovery.code == "recovery_already_applied", "duplicate recovery returns the original receipt")
_expect(_recruit_ids(recovered.company) == _recruit_ids(company), "recovery preserves every Recruit identity")
_cleanup()
if failures.is_empty():
print("ABG_M12_OUTCOME_RECOVERY_DOMAIN_OK lock=true settlement=once coin_xp=true reward_draft=true reward_claim=once recovery=once duplicate_safe=true identity=true")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _create_prepared_company() -> Dictionary:
var draft_service := DraftService.new()
draft_service.path = DRAFT_PATH
draft_service.company_path = COMPANY_PATH
var draft: Dictionary = draft_service.begin_or_load(SEED)
if not draft.ok:
return {}
draft_service.set_selection([String(draft.draft.offers[0].offer_id), String(draft.draft.offers[1].offer_id)])
var committed: Dictionary = draft_service.commit_company()
if not committed.ok:
return {}
var contract_service := ContractService.new()
contract_service.company_path = COMPANY_PATH
var created: Dictionary = contract_service.create_contract(CONTRACT_OP, "CTR-R1-01", "standard")
if not created.ok:
return {}
var offers: Array = created.company.active_contract.market_board.recruit_offers
contract_service.hire_recruit(HIRE_OP_1, String(offers[0].source_offer_id), 14)
var hired: Dictionary = contract_service.hire_recruit(HIRE_OP_2, String(offers[1].source_offer_id), 14)
if not hired.ok:
return {}
var formation_service := FormationService.new()
formation_service.company_path = COMPANY_PATH
var begun: Dictionary = formation_service.begin_formation()
var saved: Dictionary = formation_service.save_order(ORDER_OP, begun.formation.base_front_to_rear_ids)
var ready: Dictionary = formation_service.accept_readiness(READY_OP, String(saved.receipt.order_hash))
return ready.company if ready.ok else {}
func _battle_service() -> RefCounted:
var service := BattleService.new()
service.company_path = COMPANY_PATH
return service
func _settlement_snapshot(company: Dictionary) -> Dictionary:
var contract: Dictionary = company.active_contract
var recruits: Array = []
for recruit in company.owned_recruits:
recruits.append({"id": String(recruit.recruit_id), "level": int(recruit.level), "xp": int(recruit.xp), "history": Dictionary(recruit.battle_history).duplicate(true)})
return {"coin": int(contract.coin), "recruits": recruits, "settlements": Array(contract.get("battle_settlement_receipts", [])).duplicate(true), "rewards": Array(contract.get("reward_receipts", [])).duplicate(true)}
func _recruit_ids(company: Dictionary) -> Array[String]:
var ids: Array[String] = []
for recruit in company.get("owned_recruits", []):
ids.append(String(recruit.recruit_id))
return ids
func _cleanup() -> void:
for path in [DRAFT_PATH, "%s.bak" % DRAFT_PATH, "%s.candidate" % DRAFT_PATH, "%s.bak.candidate" % DRAFT_PATH, COMPANY_PATH, "%s.bak" % COMPANY_PATH, "%s.candidate" % COMPANY_PATH, "%s.bak.candidate" % COMPANY_PATH, "%s.corrupt" % COMPANY_PATH]:
if FileAccess.file_exists(path):
DirAccess.remove_absolute(path)
func _expect(condition: bool, message: String) -> void:
if not condition:
failures.append("M12 outcome/recovery: %s" % message)