feat(progression): snapshots, ports, in-memory adapters (slice 5)
flat snapshot record per aggregate - separate files mean saving a run doesn't rewrite the Profile, deleting a slot is deleting a file, and a corrupt slot doesn't cost achievements or best times. snapshots hold primitives so Restore re-runs the value-object factories; a serialiser building LevelId directly would bypass every guard. LoadResult has three outcomes: Absent != Corrupt. Collapsing them is the bug where a version bump made ContinueGame overwrite the save. Profile.Restore writes collections directly - replaying UnlockAchievement would re-fire popups and Steam pushes on every load. 44 tests.
This commit is contained in:
52
progression.tests/ProfileSnapshotTests.cs
Normal file
52
progression.tests/ProfileSnapshotTests.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace Mr.BrickAdventures.Progression.Tests;
|
||||
|
||||
public class ProfileSnapshotTests
|
||||
{
|
||||
private static readonly LevelId Level = LevelId.Of("village_1");
|
||||
private static readonly AchievementId RescueTen = AchievementId.Of("rescue_ten_kids");
|
||||
|
||||
[Fact]
|
||||
public void A_profile_survives_a_round_trip_through_its_snapshot()
|
||||
{
|
||||
var profile = Profile.Empty();
|
||||
profile.RecordTime(Level, LevelTime.Of(42.5));
|
||||
profile.UnlockAchievement(RescueTen);
|
||||
profile.Record(StatName.Deaths, 7);
|
||||
|
||||
var restored = Profile.Restore(profile.Export());
|
||||
|
||||
Assert.Equal(LevelTime.Of(42.5), restored.BestTimeFor(Level));
|
||||
Assert.True(restored.HasAchievement(RescueTen));
|
||||
Assert.Equal(7, restored.ReadStat(StatName.Deaths));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Restoring_a_profile_does_not_re_announce_its_achievements()
|
||||
{
|
||||
// If Restore replayed UnlockAchievement it would raise AchievementUnlocked for every
|
||||
// achievement on every load — so the popup would fire on startup and Steam would be
|
||||
// re-pushed each time. Loading is not earning.
|
||||
var profile = Profile.Empty();
|
||||
profile.UnlockAchievement(RescueTen);
|
||||
|
||||
var restored = Profile.Restore(profile.Export());
|
||||
|
||||
Assert.Empty(restored.DrainEvents());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_unknown_statistic_is_dropped_rather_than_failing_the_load()
|
||||
{
|
||||
// A save written by a build that tracked a statistic this one no longer has. Losing one
|
||||
// counter is acceptable; refusing to load the Profile — and with it every achievement and
|
||||
// best time — is not.
|
||||
var snapshot = new ProfileSnapshot(
|
||||
BestTimes: new Dictionary<string, double>(),
|
||||
Achievements: Array.Empty<string>(),
|
||||
Statistics: new Dictionary<string, int> { ["Deaths"] = 4, ["BricksThrown"] = 99 });
|
||||
|
||||
var restored = Profile.Restore(snapshot);
|
||||
|
||||
Assert.Equal(4, restored.ReadStat(StatName.Deaths));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user