191 lines
9.4 KiB
GDScript
191 lines
9.4 KiB
GDScript
extends SceneTree
|
|
|
|
const Controller = preload("res://entry/battle_result_page_controller.gd")
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
var controller := Controller.new()
|
|
var company := _fixture_company()
|
|
var battle := _fixture_battle()
|
|
var result: Dictionary = battle.result
|
|
|
|
var closed: Dictionary = controller.snapshot(company, battle, result, false, "", "en")
|
|
expect(closed.ok and closed.page_id == "battle_result", "snapshot identifies Battle Result")
|
|
expect(closed.company.owned_count == 4 and closed.contract.coin == 51, "snapshot copies Company and Contract facts")
|
|
expect(closed.battle.status == "result" and closed.battle.operation_id == "battle-op-001", "snapshot copies settled Battle identity")
|
|
expect(closed.result.outcome == "victory" and closed.economy.reward_coin == 27 and closed.economy.xp_award == 48, "snapshot copies outcome Coin and XP facts")
|
|
expect(closed.decisive_event.kind == "damage" and closed.causal_chain.count == 9, "snapshot resolves decisive event and causal chain")
|
|
expect(closed.knockouts.ids == ["r-1"] and closed.knockouts.identities[0].recruit.recruit_id == "r-1", "snapshot binds battle-local knockout identity")
|
|
expect(closed.reward.pending and closed.reward.options_count == 2, "snapshot copies open reward draft")
|
|
expect(closed.diagnosis.state == "closed" and closed.commands.diagnosis_enabled, "closed diagnosis remains reviewable")
|
|
expect(closed.continuation.eligible and closed.continuation.target == "battle_knockout_review", "Continue routes knockout before reward")
|
|
expect(closed.receipt.duplicate_safe and closed.receipt.identity.receipt_hash == "settlement-hash-001", "settlement receipt identity is duplicate-safe")
|
|
|
|
var open: Dictionary = controller.snapshot(company, battle, result, true, "review", "zh_CN")
|
|
expect(open.ok and open.diagnosis.open and open.diagnosis.notice == "review", "diagnosis-open state copies notice")
|
|
|
|
var copied_event: Dictionary = open.decisive_event
|
|
copied_event.state_delta.amount = 999
|
|
open.causal_chain.events[0].caption_id = "changed"
|
|
open.knockouts.identities[0].recruit.rendered_name_snapshot.display_name = "changed"
|
|
open.reward.draft.options[0].reward_id = "changed"
|
|
expect(int(battle.phase_events[6].state_delta.amount) == 22, "decisive event is deeply copied")
|
|
expect(String(battle.phase_events[0].caption_id) == "opening", "causal events are deeply copied")
|
|
expect(String(company.owned_recruits[0].rendered_name_snapshot.display_name) == "Ari", "knockout Recruit identity is deeply copied")
|
|
expect(String(battle.reward_draft.options[0].reward_id) == "reward-1", "reward draft is deeply copied")
|
|
|
|
var no_knockout := battle.duplicate(true)
|
|
no_knockout.knocked_out_ids = []
|
|
no_knockout.result = result.duplicate(true)
|
|
no_knockout.result.knocked_out_ids = []
|
|
no_knockout.reward_draft = {}
|
|
var recovery: Dictionary = controller.snapshot(company, no_knockout, no_knockout.result)
|
|
expect(recovery.ok and recovery.continuation.target == "battle_recovery" and not recovery.reward.pending, "Continue routes directly to recovery without pending consequences")
|
|
|
|
var reward_only := no_knockout.duplicate(true)
|
|
reward_only.reward_draft = _fixture_reward_draft()
|
|
var reward_view: Dictionary = controller.snapshot(company, reward_only, reward_only.result)
|
|
expect(reward_view.ok and reward_view.continuation.target == "battle_reward_choice", "Continue routes to reward choice without knockout")
|
|
|
|
var missing: Dictionary = controller.snapshot({}, battle, result)
|
|
expect(not missing.ok and missing.code == "COMPANY_REQUIRED", "missing Company is rejected")
|
|
var active := battle.duplicate(true)
|
|
active.status = "active"
|
|
var not_settled: Dictionary = controller.snapshot(company, active, result)
|
|
expect(not not_settled.ok and not_settled.code == "RESULT_BATTLE_NOT_SETTLED", "unsettled Battle is rejected")
|
|
var missing_receipt := battle.duplicate(true)
|
|
missing_receipt.erase("settlement_receipt")
|
|
missing_receipt.get("result", {}).erase("result_hash")
|
|
var company_without_receipt := company.duplicate(true)
|
|
company_without_receipt.active_contract.battle_settlement_receipts = []
|
|
var missing_receipt_result: Dictionary = controller.snapshot(company_without_receipt, missing_receipt, missing_receipt.result)
|
|
expect(not missing_receipt_result.ok and missing_receipt_result.code == "RESULT_SETTLEMENT_RECEIPT_REQUIRED", "missing settlement receipt is rejected")
|
|
var unknown_knockout := battle.duplicate(true)
|
|
unknown_knockout.result = result.duplicate(true)
|
|
unknown_knockout.result.knocked_out_ids = ["missing"]
|
|
unknown_knockout.knocked_out_ids = ["missing"]
|
|
var unknown_knockout_result: Dictionary = controller.snapshot(company, unknown_knockout, unknown_knockout.result)
|
|
expect(not unknown_knockout_result.ok and unknown_knockout_result.code == "RESULT_KNOCKOUT_RECRUIT_UNKNOWN", "unknown knockout identity is rejected")
|
|
var duplicate_receipt := battle.duplicate(true)
|
|
duplicate_receipt.settlement_receipt = duplicate_receipt.settlement_receipt.duplicate(true)
|
|
var duplicate_contract := company.duplicate(true)
|
|
duplicate_contract.active_contract.battle_settlement_receipts.append(duplicate_receipt.settlement_receipt.duplicate(true))
|
|
var duplicate_receipt_result: Dictionary = controller.snapshot(duplicate_contract, duplicate_receipt, duplicate_receipt.result)
|
|
expect(not duplicate_receipt_result.ok and duplicate_receipt_result.code == "RESULT_SETTLEMENT_RECEIPT_DUPLICATE", "duplicate settlement receipt is rejected")
|
|
var missing_decisive := battle.duplicate(true)
|
|
missing_decisive.result = result.duplicate(true)
|
|
missing_decisive.result.decisive_event_hash = "missing-event-hash"
|
|
var missing_decisive_result: Dictionary = controller.snapshot(company, missing_decisive, missing_decisive.result)
|
|
expect(not missing_decisive_result.ok and missing_decisive_result.code == "RESULT_DECISIVE_EVENT_MISSING", "missing decisive event is rejected")
|
|
var mismatched_result := result.duplicate(true)
|
|
mismatched_result.result_hash = "different-result-hash"
|
|
var mismatched: Dictionary = controller.snapshot(company, battle, mismatched_result)
|
|
expect(not mismatched.ok and mismatched.code == "RESULT_IDENTITY_MISMATCH", "result identity mismatch is rejected")
|
|
|
|
if failures.is_empty():
|
|
print("ABG_M27_BATTLE_RESULT_CONTROLLER_OK company=4 settled=result decisive=damage causal=9 coin=24,51 xp=48 knockout=r-1 reward=open diagnosis=closed,open continue=knockout,reward,recovery receipt=duplicate_safe copies=true")
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func _fixture_company() -> Dictionary:
|
|
return {
|
|
"company_id": "company-001",
|
|
"owned_recruits": [
|
|
{"recruit_id": "r-1", "rendered_name_snapshot": {"display_name": "Ari"}, "level": 1, "xp": 0},
|
|
{"recruit_id": "r-2", "rendered_name_snapshot": {"display_name": "Bram"}, "level": 1, "xp": 16},
|
|
{"recruit_id": "r-3", "rendered_name_snapshot": {"display_name": "Cera"}, "level": 1, "xp": 16},
|
|
{"recruit_id": "r-4", "rendered_name_snapshot": {"display_name": "Dain"}, "level": 1, "xp": 16},
|
|
],
|
|
"active_contract": {
|
|
"contract_id": "CTR-R1-01",
|
|
"run_id": "run-001",
|
|
"status": "result",
|
|
"coin": 51,
|
|
"line_capacity": 4,
|
|
"contract_hash": "contract-hash-001",
|
|
"battle_settlement_receipts": [_fixture_receipt()],
|
|
},
|
|
}
|
|
|
|
|
|
func _fixture_battle() -> Dictionary:
|
|
var events: Array = []
|
|
var kinds := ["opening", "telegraph", "guard", "ranged", "magic", "healing", "damage", "knockout", "result"]
|
|
for index in range(kinds.size()):
|
|
events.append({
|
|
"event_id": "event-%d" % index,
|
|
"event_hash": "event-hash-%d" % index,
|
|
"kind": kinds[index],
|
|
"caption_id": "opening" if index == 0 else "decisive_damage" if index == 6 else "knockout" if index == 7 else "victory" if index == 8 else "incoming_threat",
|
|
"actor_id": "r-1" if index in [2, 4, 6] else "enemy-1",
|
|
"target_id": "r-1" if index in [1, 5, 7] else "enemy-1",
|
|
"state_delta": {"amount": 22 if index == 6 else index},
|
|
})
|
|
return {
|
|
"status": "result",
|
|
"operation_id": "battle-op-001",
|
|
"battle_hash": "battle-hash-001",
|
|
"snapshot_id": "snapshot-001",
|
|
"snapshot_hash": "snapshot-hash-001",
|
|
"front_to_rear_ids": ["r-1", "r-2", "r-3", "r-4"],
|
|
"enemy_ids": ["enemy-1", "enemy-2", "enemy-3"],
|
|
"phase_index": 9,
|
|
"total_phases": 9,
|
|
"phase_events": events,
|
|
"knocked_out_ids": ["r-1"],
|
|
"result": {
|
|
"settlement_version": 1,
|
|
"outcome": "victory",
|
|
"outcome_cause": "enemy_line_broken",
|
|
"reward_coin": 27,
|
|
"xp_award": 48,
|
|
"xp_per_recruit": 16,
|
|
"coin_before": 24,
|
|
"coin_after": 51,
|
|
"knocked_out_ids": ["r-1"],
|
|
"decisive_event_hash": "event-hash-6",
|
|
"event_journal_hash": "journal-hash-001",
|
|
"outcome_hash": "outcome-hash-001",
|
|
"result_hash": "result-hash-001",
|
|
"xp_by_recruit": {"r-1": {"award": 0}, "r-2": {"award": 16}, "r-3": {"award": 16}, "r-4": {"award": 16}},
|
|
},
|
|
"reward_draft": _fixture_reward_draft(),
|
|
"settlement_receipt": _fixture_receipt(),
|
|
}
|
|
|
|
|
|
func _fixture_receipt() -> Dictionary:
|
|
return {
|
|
"operation_id": "battle-op-001",
|
|
"battle_operation_id": "battle-op-001",
|
|
"receipt_hash": "settlement-hash-001",
|
|
"outcome_hash": "outcome-hash-001",
|
|
"outcome": "victory",
|
|
}
|
|
|
|
|
|
func _fixture_reward_draft() -> Dictionary:
|
|
return {
|
|
"draft_id": "draft-001",
|
|
"status": "open",
|
|
"options": [
|
|
{"reward_id": "reward-1", "kind": "equipment", "catalog_id": "EQ-001"},
|
|
{"reward_id": "reward-2", "kind": "coin", "amount": 8},
|
|
],
|
|
}
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("M27 Battle Result Controller: %s" % message)
|