using Mr.BrickAdventures.Progression.Adapters; namespace Mr.BrickAdventures.Progression.Tests; /// /// These run against a real directory on disk. The store takes a path rather than using Godot's /// FileAccess, which is the whole reason this is testable at all. /// public sealed class JsonPlaythroughStoreTests : IDisposable { private readonly string _directory = Path.Combine(Path.GetTempPath(), "ppc-progression-" + Guid.NewGuid().ToString("N")); private static readonly LevelId First = LevelId.Of("village_1"); private static readonly LevelId Second = LevelId.Of("village_2"); private static readonly PlaythroughId Id = PlaythroughId.Of(new Guid("55555555-5555-5555-5555-555555555555")); private static Campaign TwoLevels() => Campaign.Of(new[] { First, Second }); public void Dispose() { if (Directory.Exists(_directory)) Directory.Delete(_directory, recursive: true); } [Fact] public void A_playthrough_survives_a_round_trip_through_a_real_file() { var store = new JsonPlaythroughStore(_directory); var campaign = TwoLevels(); var playthrough = Playthrough.StartNew(Id, campaign, Lives.Of(3)); var attempt = new Attempt(First); attempt.RecordCoinsFound(Coins.Of(50)); attempt.TryRecordPurchase(SkillId.Of("brick_throw"), SkillLevel.Of(2), Coins.Of(30)); playthrough.SettleCompletion(attempt, campaign); store.Save(playthrough); var result = store.Load(Id); Assert.Equal(LoadOutcome.Loaded, result.Outcome); Assert.Equal(Coins.Of(20), result.Value!.Bank); Assert.Equal(Second, result.Value.CurrentLevel); Assert.Equal(SkillLevel.Of(2), result.Value.OwnedSkills[SkillId.Of("brick_throw")]); Assert.True(result.Value.HasCleared(First)); } [Fact] public void No_file_means_absent() { Assert.Equal(LoadOutcome.Absent, new JsonPlaythroughStore(_directory).Load(Id).Outcome); } [Fact] public void Unreadable_json_is_corrupt_not_absent() { var store = new JsonPlaythroughStore(_directory); store.Save(Playthrough.StartNew(Id, TwoLevels(), Lives.Of(3))); File.WriteAllText(Directory.GetFiles(_directory, "*.json").Single(), "{ this is not json"); var result = store.Load(Id); Assert.Equal(LoadOutcome.Corrupt, result.Outcome); Assert.NotNull(result.Problem); } [Fact] public void A_save_from_a_newer_build_is_corrupt_rather_than_treated_as_no_save() { // The bug this replaces, exactly: SaveSystem compared versions with != and returned false, // ContinueGame read false as "no save", called StartNewGame, and overwrote the file. Here the // caller can tell the difference and say so instead. var store = new JsonPlaythroughStore(_directory); store.Save(Playthrough.StartNew(Id, TwoLevels(), Lives.Of(3))); var file = Directory.GetFiles(_directory, "*.json").Single(); File.WriteAllText(file, File.ReadAllText(file).Replace("\"version\": 1", "\"version\": 99")); var result = store.Load(Id); Assert.Equal(LoadOutcome.Corrupt, result.Outcome); Assert.Contains("newer build", result.Problem); } [Fact] public void A_snapshot_the_domain_rejects_is_corrupt() { var store = new JsonPlaythroughStore(_directory); store.Save(Playthrough.StartNew(Id, TwoLevels(), Lives.Of(3))); var file = Directory.GetFiles(_directory, "*.json").Single(); File.WriteAllText(file, File.ReadAllText(file).Replace("\"lives\": 3", "\"lives\": -1")); Assert.Equal(LoadOutcome.Corrupt, store.Load(Id).Outcome); } [Fact] public void Saving_leaves_no_temporary_file_behind() { var store = new JsonPlaythroughStore(_directory); store.Save(Playthrough.StartNew(Id, TwoLevels(), Lives.Of(3))); store.Save(Playthrough.StartNew(Id, TwoLevels(), Lives.Of(2))); Assert.Empty(Directory.GetFiles(_directory, "*.tmp")); Assert.Single(Directory.GetFiles(_directory, "*.json")); Assert.Equal(2, store.Load(Id).Value!.Lives.Value); } [Fact] public void Each_playthrough_gets_its_own_file_so_slots_do_not_disturb_each_other() { // The reason for a flat record per aggregate rather than one combined document. var other = PlaythroughId.Of(new Guid("66666666-6666-6666-6666-666666666666")); var store = new JsonPlaythroughStore(_directory); store.Save(Playthrough.StartNew(Id, TwoLevels(), Lives.Of(3))); store.Save(Playthrough.StartNew(other, TwoLevels(), Lives.Of(1))); Assert.Equal(2, Directory.GetFiles(_directory, "*.json").Length); Assert.Equal(3, store.Load(Id).Value!.Lives.Value); Assert.Equal(1, store.Load(other).Value!.Lives.Value); } }