Files
aetherbound-guild/runtime/tests/dev2/initial_company_commit_test.gd
T

218 lines
9.9 KiB
GDScript

extends SceneTree
const EntryShell = preload("res://entry/entry_shell.gd")
const SettingsStore = preload("res://entry/settings_store.gd")
const DraftService = preload("res://entry/initial_company_draft_service.gd")
const DRAFT_PATH := "user://dev2_company_commit_draft.json"
const COMPANY_PATH := "user://dev2_company_commit_company.json"
const SETTINGS_PATH := "user://dev2_company_commit_settings.json"
const SEED := "13579bdf2468ace00eca8642fdb97531"
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
_cleanup()
await verify_review_commit_handoff_and_restart()
verify_idempotency_lingering_draft_and_candidate_recovery()
await verify_corrupt_company_blocks_reroll()
_cleanup()
if failures.is_empty():
print("ABG_DEV2_COMMIT_OK review=true revise=true selected=2 unchosen=0 atomic=true idempotent=true candidate_recovery=true lingering_draft=true corrupt_blocks_reroll=true guild=true reasons=true settings_return=true continue=true restart=true")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func verify_review_commit_handoff_and_restart() -> void:
var service: RefCounted = _service()
var created: Dictionary = service.begin_or_load(SEED)
var selected_offer_ids := [String(created.draft.offers[1].offer_id), String(created.draft.offers[3].offer_id)]
var selected_recruit_ids := [String(created.draft.offers[1].recruit_id), String(created.draft.offers[3].recruit_id)]
var unchosen_recruit_ids := [String(created.draft.offers[0].recruit_id), String(created.draft.offers[2].recruit_id)]
var saved: Dictionary = service.set_selection(selected_offer_ids)
expect(saved.ok and saved.draft.selected_offer_ids == selected_offer_ids, "fixture persists the intended pair")
var shell = await _shell()
shell.show_initial_company()
await _frames(3)
var page = shell.initial_company_page
page.request_review()
await _frames(2)
expect(page.mode == "review" and page.find_child("ReviewSelected", true, false).get_child_count() == 2, "Review names exactly two selected offers")
expect(page.find_child("ReviewUnchosen", true, false).get_child_count() >= 4, "Review names both unchosen provisional offers")
page.revise_selection()
await _frames(2)
expect(page.mode == "choice" and page.draft.selected_offer_ids == selected_offer_ids, "Revise preserves the same draft and pair")
page.request_review()
page.create_company()
await _frames(4)
expect(shell.current_screen == "guild_handoff" and shell.find_child("GuildMasterDesignCanvas", true, false) != null, "Create Company reaches the bounded Guild handoff")
var loaded: Dictionary = service.load_company()
expect(loaded.ok and service.validate_company(loaded.company).ok, "committed Company reloads and validates")
var actual_recruit_ids: Array[String] = []
for recruit in loaded.company.owned_recruits:
actual_recruit_ids.append(String(recruit.recruit_id))
expect(actual_recruit_ids == selected_recruit_ids, "Company owns exactly the selected Recruit IDs in offer order")
var company_json := JSON.stringify(loaded.company)
for recruit_id in unchosen_recruit_ids:
expect(not company_json.contains(recruit_id), "unchosen Recruit %s is not stored" % recruit_id)
expect(not FileAccess.file_exists(DRAFT_PATH) and not FileAccess.file_exists("%s.bak" % DRAFT_PATH), "successful commit removes draft replicas")
expect(not shell.find_child("GuildContractBoard", true, false).disabled and shell.find_child("GuildMarketUnavailable", true, false).disabled, "current Guild enables Contract Board while Market waits for Contract creation")
expect(not shell.find_child("GuildContractReason", true, false).text.is_empty() and not shell.find_child("GuildMarketReason", true, false).text.is_empty(), "Guild destinations have truthful availability reasons")
shell.show_settings_from_guild()
await _frames(2)
shell.request_settings_back()
await _frames(2)
expect(shell.current_screen == "guild_handoff", "Settings returns to the Guild caller")
shell.back_to_title()
await _frames(2)
expect(shell.find_child("TitleNewGame", true, false).disabled and not shell.find_child("TitleContinue", true, false).disabled, "Title disables New Game and enables Continue after Company creation")
shell.open_continue_boundary()
await _frames(2)
expect(shell.current_screen == "guild_handoff", "Continue returns to the committed Guild")
shell.queue_free()
await _frames(3)
var settings_store = SettingsStore.new()
settings_store.path = SETTINGS_PATH
var settings := settings_store.defaults()
settings.setup_completed = true
settings.locale = "en"
settings_store.save_settings(settings)
var restarted = EntryShell.new()
restarted.auto_start = false
restarted.settings_store.path = SETTINGS_PATH
restarted.initial_company_service.path = DRAFT_PATH
restarted.initial_company_service.company_path = COMPANY_PATH
root.add_child(restarted)
await _frames(2)
restarted._open_after_boot()
await _frames(3)
expect(restarted.current_screen == "guild_handoff" and restarted.find_child("GuildRoster", true, false).get_child_count() == 2, "restart resumes the committed Guild with two members")
restarted.queue_free()
await _frames(3)
func verify_idempotency_lingering_draft_and_candidate_recovery() -> void:
var service: RefCounted = _service()
var company_loaded: Dictionary = service.load_company()
var company: Dictionary = company_loaded.company.duplicate(true)
var original_receipt := String(company.creation_receipt.receipt_hash)
var duplicate: Dictionary = service.commit_company()
expect(duplicate.ok and duplicate.code == "already_committed" and String(duplicate.company.creation_receipt.receipt_hash) == original_receipt, "duplicate commit returns the original receipt")
var draft_service: RefCounted = _service()
_remove_company_files()
var recreated: Dictionary = draft_service.begin_or_load(SEED)
var ids := [String(recreated.draft.offers[1].offer_id), String(recreated.draft.offers[3].offer_id)]
draft_service.set_selection(ids)
var lingering_draft: Dictionary = draft_service.load_draft().draft.duplicate(true)
var committed: Dictionary = draft_service.commit_company()
expect(committed.ok, "lingering-draft fixture commits Company")
_write(DRAFT_PATH, JSON.stringify(_canonical(lingering_draft)))
var replay: Dictionary = draft_service.commit_company()
expect(replay.ok and replay.code == "already_committed" and not FileAccess.file_exists(DRAFT_PATH), "matching lingering draft is cleaned without a second Company")
var persisted: Dictionary = draft_service.load_company().company.duplicate(true)
_remove_company_files()
_write("%s.candidate" % COMPANY_PATH, JSON.stringify(_canonical(persisted)))
var recovered: Dictionary = draft_service.load_company()
expect(recovered.ok and recovered.code == "recovered_candidate", "interrupted Company candidate promotes on restart")
expect(String(recovered.company.creation_receipt.receipt_hash) == String(persisted.creation_receipt.receipt_hash), "candidate recovery preserves the exact receipt")
func verify_corrupt_company_blocks_reroll() -> void:
_remove_company_files()
_write(COMPANY_PATH, "{broken")
var service: RefCounted = _service()
var blocked: Dictionary = service.begin_or_load(SEED)
expect(not blocked.ok and blocked.code == "COMPANY_CORRUPT", "corrupt Company blocks begin_or_load instead of rerolling")
expect(not FileAccess.file_exists(DRAFT_PATH), "corrupt Company does not create a replacement draft")
var shell = await _shell()
shell.show_title()
await _frames(2)
expect(shell.find_child("TitleNewGame", true, false).disabled and shell.find_child("TitleContinue", true, false).disabled, "Title blocks New Game and Continue while Company data is corrupt")
expect("attention" in shell.find_child("TitleContinueReason", true, false).text and shell.find_child("TitleSettings", true, false).has_focus(), "Title shows the recovery reason and focuses an available action")
shell.open_new_game_boundary()
await _frames(2)
expect(shell.current_screen == "title" and "COMPANY_CORRUPT" in shell.find_child("TitleNotice", true, false).text, "New Game boundary reports the corrupt Company without creating offers")
shell.queue_free()
await _frames(3)
func _shell():
var shell = EntryShell.new()
shell.auto_start = false
shell.initial_company_service.path = DRAFT_PATH
shell.initial_company_service.company_path = COMPANY_PATH
root.add_child(shell)
await _frames(2)
shell.settings = SettingsStore.new().defaults()
shell.settings.setup_completed = true
shell.settings.locale = "en"
return shell
func _service() -> RefCounted:
var service: RefCounted = DraftService.new()
service.path = DRAFT_PATH
service.company_path = COMPANY_PATH
return service
func _write(file_path: String, text: String) -> void:
var file := FileAccess.open(file_path, FileAccess.WRITE)
file.store_string(text)
file.close()
func _remove_company_files() -> void:
for suffix in ["", ".bak", ".candidate", ".bak.candidate", ".corrupt"]:
var file_path := "%s%s" % [COMPANY_PATH, suffix]
if FileAccess.file_exists(file_path):
DirAccess.remove_absolute(file_path)
func _cleanup() -> void:
for base in [DRAFT_PATH, COMPANY_PATH, SETTINGS_PATH]:
for suffix in ["", ".bak", ".candidate", ".bak.candidate", ".corrupt"]:
var file_path := "%s%s" % [base, suffix]
if FileAccess.file_exists(file_path):
DirAccess.remove_absolute(file_path)
func _canonical(value):
if value is float and is_equal_approx(value, round(value)):
return int(round(value))
if value is Dictionary:
var result := {}
var keys: Array = value.keys()
keys.sort_custom(func(left, right): return String(left) < String(right))
for key in keys:
result[key] = _canonical(value[key])
return result
if value is Array:
var result: Array = []
for item in value:
result.append(_canonical(item))
return result
return value
func _frames(count: int) -> void:
for frame in range(count):
await process_frame
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("DEV-2 Company commit: %s" % message)