Files
aetherbound-guild/runtime/tests/p9_9/persistence_test.gd
T

128 lines
6.0 KiB
GDScript

extends SceneTree
const CharterService = preload("res://p9_9/charter_service.gd")
const ProfileState = preload("res://p9_9/profile_state.gd")
const SaveService = preload("res://p9_9/save_service.gd")
const Fixture = preload("res://tests/p9_9/runtime_fixture.gd")
var failures: Array[String] = []
var save_root := ""
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
save_root = OS.get_environment("ABG_P9_9_SAVE_TEST_ROOT")
if save_root.is_empty():
save_root = "/private/tmp/abg-p99-persistence-default"
DirAccess.make_dir_recursive_absolute(save_root)
var closed := Fixture.closed_p98()
verify_v3_migration(closed)
verify_v4_round_trip(closed)
verify_time_corruption_and_backup(closed)
if failures.is_empty():
print("ABG_P9_9_PERSISTENCE_OK version=4 migration=3_to_4 p98_closed=exact renown=14 charter=preserved duplicate_after_reload=0 corruption=true backup=true rollback=true offline_cap=86400 offline_rewards=0 temp_clean=true")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func verify_v3_migration(closed: Dictionary) -> void:
var saves = fresh_saves("v3")
write_json(saves.save_path, saves.envelope_for_snapshot(closed, 3, 1000))
var loaded: Dictionary = saves.load_snapshot(1060)
expect(loaded.ok and loaded.migrated and loaded.source_version == 3, "v3 closed save migrates")
expect(loaded.snapshot.persistent_renown == 14 and loaded.snapshot.renown_reconciliation_receipts.size() == 1, "migration reconciles Renown once")
expect(loaded.snapshot.contract_clear_ids == closed.contract_clear_ids and loaded.snapshot.company_records == closed.company_records, "migration preserves clear and Record")
expect(loaded.elapsed_seconds == 60 and loaded.offline_renown == 0, "migration grants no offline value")
func verify_v4_round_trip(closed: Dictionary) -> void:
var service = CharterService.new(closed)
var operation_id := "p99-persist-charter"
expect(service.claim(operation_id, "CH-RG01-THREAT-01", service.state.revision).ok, "Charter fixture commits")
var saves = fresh_saves("v4")
var original: Dictionary = service.state.snapshot()
var saved: Dictionary = saves.save_snapshot(original, 2000)
expect(saved.ok and saved.version == 4, "v4 profile saves")
if not saved.ok:
return
expect(not FileAccess.file_exists(saves.save_path + ".tmp"), "atomic write leaves no temp")
var loaded: Dictionary = saves.load_snapshot(2010)
expect(loaded.ok and not loaded.migrated and loaded.snapshot == original, "v4 round trip is exact")
var restored = CharterService.new()
expect(restored.state.restore_snapshot(loaded.snapshot), "loaded profile restores")
var before: Dictionary = restored.state.snapshot()
var duplicate: Dictionary = restored.claim(operation_id, "CH-RG01-THREAT-01")
expect(duplicate.ok and duplicate.duplicate and restored.state.snapshot() == before, "reloaded duplicate is idempotent")
func verify_time_corruption_and_backup(closed: Dictionary) -> void:
var service = CharterService.new(closed)
var rollback = fresh_saves("rollback")
var rollback_saved: Dictionary = rollback.save_snapshot(service.state.snapshot(), 3000)
expect(rollback_saved.ok, "rollback fixture writes")
if not rollback_saved.ok:
return
var rolled: Dictionary = rollback.load_snapshot(2900)
expect(rolled.ok and rolled.clock_rollback and rolled.elapsed_seconds == 0, "clock rollback is inert")
var capped: Dictionary = rollback.load_snapshot(999999)
expect(capped.ok and capped.elapsed_seconds == SaveService.OFFLINE_CAP_SECONDS, "offline duration is capped")
expect(capped.offline_coin == 0 and capped.offline_xp == 0 and capped.offline_renown == 0 and capped.offline_battle_ticks == 0, "offline rewards are zero")
var malformed = fresh_saves("malformed")
write_text(malformed.save_path, "{")
expect(malformed.load_snapshot(4000).code == "malformed_json", "malformed JSON rejects")
var checksum = fresh_saves("checksum")
var bad_checksum: Dictionary = checksum.envelope_for_snapshot(service.state.snapshot(), 4, 4000)
bad_checksum.checksum = "0".repeat(64)
write_json(checksum.save_path, bad_checksum)
expect(checksum.load_snapshot(4001).code == "checksum_mismatch", "checksum drift rejects")
var unknown = fresh_saves("unknown")
write_json(unknown.save_path, unknown.envelope_for_snapshot(service.state.snapshot(), 99, 4000))
expect(unknown.load_snapshot(4001).code == "unsupported_version", "unknown version rejects")
var invalid = fresh_saves("invalid")
var invalid_state: Dictionary = service.state.snapshot()
invalid_state.persistent_renown = -1
write_json(invalid.save_path, invalid.envelope_for_snapshot(invalid_state, 4, 4000))
expect(invalid.load_snapshot(4001).code == "invalid_state", "valid-checksum invalid profile rejects")
var backup = fresh_saves("backup")
var first: Dictionary = service.state.snapshot()
expect(backup.save_snapshot(first, 5000).ok, "backup first save writes")
service.claim("p99-backup-claim", "CH-RG01-CATALOG-01", service.state.revision)
var second: Dictionary = service.state.snapshot()
expect(backup.save_snapshot(second, 5010).ok and FileAccess.file_exists(backup.save_path + ".bak"), "second save retains backup")
write_text(backup.save_path, "broken")
var recovered: Dictionary = backup.load_snapshot(5020)
expect(recovered.ok and recovered.code == "backup_recovered" and recovered.primary_error == "malformed_json", "corrupt primary recovers backup")
expect(recovered.snapshot == first and recovered.snapshot != second, "backup is prior complete state")
func fresh_saves(name: String):
var saves = SaveService.new()
saves.save_path = save_root.path_join(name + ".json")
saves.clear()
return saves
func write_json(path: String, value: Dictionary) -> void:
write_text(path, JSON.stringify(value))
func write_text(path: String, value: String) -> void:
var file := FileAccess.open(path, FileAccess.WRITE)
expect(file != null, "fixture opens %s" % path)
if file != null:
file.store_string(value)
file.flush()
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("P9.9 persistence: %s" % message)