Files
przygody-pana-cegly/progression.tests/ProfileTests.cs
Gabriel Kaszewski 05149bb4a9 feat(progression): Profile aggregate (slice 4)
best times only improve; achievement unlock announces on the transition
only, so popup/Steam subscribers cannot double-fire; statistics are
increase-only (IncrementStat took any int).

RelockAchievement is console-only and documented as not reaching Steam -
a Steam achievement cannot be un-earned, so the disagreement is
permanent and deliberate.

refactor: extract EventLog, composed by both aggregates now that the
duplication actually exists. 34 tests.
2026-07-28 18:56:58 +02:00

75 lines
2.7 KiB
C#

namespace Mr.BrickAdventures.Progression.Tests;
public class ProfileTests
{
private static readonly LevelId Level = LevelId.Of("village_1");
[Fact]
public void A_slower_run_does_not_replace_a_better_time()
{
var profile = Profile.Empty();
Assert.True(profile.RecordTime(Level, LevelTime.Of(42.5)));
Assert.False(profile.RecordTime(Level, LevelTime.Of(51.0)));
Assert.Equal(LevelTime.Of(42.5), profile.BestTimeFor(Level));
}
[Fact]
public void Unlocking_the_same_achievement_twice_announces_it_once()
{
// AchievementManager shows a popup and pushes to Steam on every unlock call, guarded only by
// a Contains check on a list it reached into. Announcing only on the transition means
// subscribers cannot double-fire regardless of how often they are told.
var rescueTen = AchievementId.Of("rescue_ten_kids");
var profile = Profile.Empty();
profile.UnlockAchievement(rescueTen);
profile.UnlockAchievement(rescueTen);
Assert.True(profile.HasAchievement(rescueTen));
Assert.Single(profile.DrainEvents().OfType<AchievementUnlocked>());
}
[Fact]
public void Statistics_accumulate_and_an_unrecorded_one_reads_as_zero()
{
var profile = Profile.Empty();
profile.Record(StatName.CoinsCollected, 5);
profile.Record(StatName.CoinsCollected, 3);
Assert.Equal(8, profile.ReadStat(StatName.CoinsCollected));
Assert.Equal(0, profile.ReadStat(StatName.Deaths));
}
[Fact]
public void A_statistic_cannot_be_decremented()
{
// IncrementStat currently takes any int. A lifetime counter that can go down is not a
// lifetime counter, and nothing in the game has a reason to reduce one.
var profile = Profile.Empty();
Assert.Throws<ArgumentOutOfRangeException>(() => profile.Record(StatName.Deaths, -1));
Assert.Throws<ArgumentOutOfRangeException>(() => profile.Record(StatName.Deaths, 0));
}
[Fact]
public void Relocking_an_achievement_removes_it_and_lets_it_announce_again()
{
// Console-only. Note the asymmetry: unlocking is mirrored to Steam by a subscriber, but
// relocking cannot be, because a Steam achievement cannot be un-earned.
var rescueTen = AchievementId.Of("rescue_ten_kids");
var profile = Profile.Empty();
profile.UnlockAchievement(rescueTen);
profile.DrainEvents();
profile.RelockAchievement(rescueTen);
Assert.False(profile.HasAchievement(rescueTen));
profile.UnlockAchievement(rescueTen);
Assert.Single(profile.DrainEvents().OfType<AchievementUnlocked>());
}
}