using Theriapolis.Core; using Theriapolis.Core.Data; using Theriapolis.Core.Persistence; using Theriapolis.Core.Rules.Reputation; using Xunit; namespace Theriapolis.Tests.Reputation; /// /// Phase 6 M2 — round-trip through the /// codec. Every field must survive Capture → Restore identically. Plus /// the SaveCodec serialization path itself: write a SaveBody with rep /// data, parse it back, compare. /// public sealed class ReputationRoundTripTests { [Fact] public void CaptureRestore_FactionStandings_RoundTrips() { var factions = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory)).Factions; var rep = new PlayerReputation(); rep.Factions.Apply("inheritors", 30, factions); rep.Factions.Apply("merchant_guilds", 50, factions); var snap = ReputationCodec.Capture(rep); var restored = ReputationCodec.Restore(snap); Assert.Equal(rep.Factions.Get("inheritors"), restored.Factions.Get("inheritors")); Assert.Equal(rep.Factions.Get("covenant_enforcers"), restored.Factions.Get("covenant_enforcers")); Assert.Equal(rep.Factions.Get("merchant_guilds"), restored.Factions.Get("merchant_guilds")); } [Fact] public void CaptureRestore_PersonalDispositions_RoundTrip() { var rep = new PlayerReputation(); var pd = rep.PersonalFor("millhaven.innkeeper"); pd.Score = 25; pd.Trust = TrustLevel.Familiar; pd.Betrayed = false; pd.Memory.Add("saved-her-kit"); pd.Memory.Add("paid-for-the-window"); pd.Log.Add(new RepEvent { Kind = RepEventKind.Aid, RoleTag = "millhaven.innkeeper", Magnitude = 10, Note = "first" }); var snap = ReputationCodec.Capture(rep); var restored = ReputationCodec.Restore(snap); Assert.True(restored.Personal.ContainsKey("millhaven.innkeeper")); var rPd = restored.Personal["millhaven.innkeeper"]; Assert.Equal(25, rPd.Score); Assert.Equal(TrustLevel.Familiar, rPd.Trust); Assert.Contains("saved-her-kit", rPd.Memory); Assert.Contains("paid-for-the-window", rPd.Memory); Assert.Single(rPd.Log); Assert.Equal("first", rPd.Log[0].Note); } [Fact] public void CaptureRestore_Ledger_RoundTrips() { var factions = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory)).Factions; var rep = new PlayerReputation(); rep.Submit(new RepEvent { Kind = RepEventKind.Quest, FactionId = "inheritors", Magnitude = 10, Note = "a" }, factions); rep.Submit(new RepEvent { Kind = RepEventKind.Betrayal, FactionId = "thorn_council", Magnitude = -25, Note = "b" }, factions); var snap = ReputationCodec.Capture(rep); var restored = ReputationCodec.Restore(snap); Assert.Equal(rep.Ledger.Count, restored.Ledger.Count); Assert.Equal(rep.Ledger.Entries[0].Note, restored.Ledger.Entries[0].Note); Assert.Equal(rep.Ledger.Entries[1].Kind, restored.Ledger.Entries[1].Kind); } [Fact] public void SaveCodec_RoundTripsReputationState() { var factions = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory)).Factions; var body = new SaveBody(); body.Player.Name = "Tester"; body.Player.PositionX = 100; body.Player.PositionY = 200; // Populate reputation state. var rep = new PlayerReputation(); rep.Submit(new RepEvent { Kind = RepEventKind.Quest, FactionId = "inheritors", Magnitude = 10 }, factions); var pd = rep.PersonalFor("millhaven.constable_fenn"); pd.Score = 7; pd.Memory.Add("met-at-the-magistrate"); body.ReputationState = ReputationCodec.Capture(rep); var header = new SaveHeader { Version = C.SAVE_SCHEMA_VERSION, WorldSeedHex = "0xCAFE" }; var bytes = SaveCodec.Serialize(header, body); var (h2, b2) = SaveCodec.Deserialize(bytes); Assert.Equal(C.SAVE_SCHEMA_VERSION, h2.Version); Assert.Equal( 10, b2.ReputationState.FactionStandings["inheritors"]); Assert.Equal( -5, b2.ReputationState.FactionStandings["covenant_enforcers"]); Assert.Single(b2.ReputationState.Personal); Assert.Equal("millhaven.constable_fenn", b2.ReputationState.Personal[0].RoleTag); Assert.Contains("met-at-the-magistrate", b2.ReputationState.Personal[0].MemoryTags); Assert.Single(b2.ReputationState.Ledger); } }