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)