using RadicadeServer.RecNet.Enums.Moderation; using RadicadeServer.RecNet.Moderation; using Xunit; namespace RadicadeServer.Tests; /// /// Unit tests for the reputation system spec: /// - every player starts at ReputationScore = 10 /// - Noteriety starts at 0 and grows +0.5 per cheer / subscriber received /// - bans drop reputation: -0.5 per ban, -1 per cheating ban, -4 per permanent ban (floored at 0) /// - Trust label: Trusted (8-10), Neutral (6-7), Not Trusted (0-5) /// - reports increment ReportCount, which never feeds Noteriety (so reports can't lock /// the client's room-comment / club-creation features) /// public class ReputationTests { [Fact] public void CheerCategory_MatchesTheClientsLadder() { // Copied off the client's own enum in dump.cs (and matching the archived 2023 // servers FemRec and Rubidium). Numbering them sequentially silently broke every // cheer except General: none of 10/20/30/40 matched a switch arm. Assert.Equal(0, (int)CheerCategory.General); Assert.Equal(10, (int)CheerCategory.Helpful); Assert.Equal(20, (int)CheerCategory.Sportsman); Assert.Equal(30, (int)CheerCategory.GreatHost); Assert.Equal(40, (int)CheerCategory.Creative); // The badge only developers can equip. The client asks for it by this value, so // leaving it out of the enum made the request look like an unknown category. Assert.Equal(9000, (int)CheerCategory.RecRoomDeveloper); } [Fact] public void NewReputation_StartsAtTen() { var rep = new Reputation { AccountId = 42 }; Assert.Equal(Reputation.DefaultReputation, rep.ReputationScore); Assert.Equal(10.0, rep.ReputationScore); Assert.Equal(0, rep.Noteriety); Assert.Equal(0, rep.ReportCount); } [Theory] [InlineData(10.0, "Trusted")] [InlineData(9.5, "Trusted")] [InlineData(8.0, "Trusted")] [InlineData(7.99, "Neutral")] [InlineData(7.0, "Neutral")] [InlineData(6.0, "Neutral")] [InlineData(5.99, "Not Trusted")] [InlineData(5.0, "Not Trusted")] [InlineData(1.0, "Not Trusted")] [InlineData(0.0, "Not Trusted")] public void Trust_FollowsTheScoreBands(double score, string expected) { var rep = new Reputation { AccountId = 1, ReputationScore = score }; Assert.Equal(expected, rep.Trust); } [Fact] public void NormalBan_DropsReputationByHalf() { var rep = new Reputation { AccountId = 1, ReputationScore = 10 }; rep.ApplyBanPenalty(permanent: false, cheating: false); Assert.Equal(9.5, rep.ReputationScore); } [Fact] public void CheatingBan_DropsReputationByOne() { var rep = new Reputation { AccountId = 1, ReputationScore = 10 }; rep.ApplyBanPenalty(permanent: false, cheating: true); Assert.Equal(9.0, rep.ReputationScore); } [Fact] public void PermanentBan_DropsReputationByFour() { var rep = new Reputation { AccountId = 1, ReputationScore = 10 }; rep.ApplyBanPenalty(permanent: true, cheating: false); Assert.Equal(6.0, rep.ReputationScore); } [Fact] public void CheatingPermanentBan_AppliesTheLargerPenalty() { // A cheating offense that escalates to permanent takes the permanent (-4) penalty. var rep = new Reputation { AccountId = 1, ReputationScore = 10 }; rep.ApplyBanPenalty(permanent: true, cheating: true); Assert.Equal(6.0, rep.ReputationScore); } [Fact] public void RepeatedBans_NeverPushReputationNegative() { var rep = new Reputation { AccountId = 1, ReputationScore = 1.0 }; rep.ApplyBanPenalty(permanent: true, cheating: true); // -4 rep.ApplyBanPenalty(permanent: false, cheating: false); // -0.5 Assert.Equal(0.0, rep.ReputationScore); } [Fact] public void AddCheer_GrowsNoterietyByHalfPerCheer() { var rep = new Reputation { AccountId = 1 }; rep.AddCheer(CheerCategory.General); rep.AddCheer(CheerCategory.Creative); Assert.Equal(1.0, rep.Noteriety); // 2 x 0.5 Assert.Equal(1, rep.CheerGeneral); Assert.Equal(1, rep.CheerCreative); } [Fact] public void AddSubscriber_GrowsNoterietyByHalf() { var rep = new Reputation { AccountId = 1 }; rep.AddSubscriber(); rep.AddSubscriber(); Assert.Equal(1.0, rep.Noteriety); } [Fact] public void Reports_NeverTouchNoteriety() { // Simulates the report flow: ReportCount goes up, Noteriety stays a fame score. var rep = new Reputation { AccountId = 1 }; for (var i = 0; i < 20; i++) rep.ReportCount += 1; Assert.Equal(20, rep.ReportCount); Assert.Equal(0.0, rep.Noteriety); } [Fact] public void ForClient_PassesThroughScoreTrustAndReportCount() { var rep = new Reputation { AccountId = 7, ReputationScore = 6.5, ReportCount = 3, Noteriety = 1.0 }; var client = rep.ForClient(); Assert.Equal(7, client.AccountId); Assert.Equal(6.5, client.ReputationScore); Assert.Equal("Neutral", client.Trust); Assert.Equal(3, client.ReportCount); // Noteriety is normalized to a tiny 0-1 fraction so the client's moderation // gates (room comments / club creation) never trip on it. Assert.Equal(Math.Min(0.05, rep.Noteriety / 100.0), client.Noteriety); Assert.True(client.Noteriety <= 0.05); } [Fact] public void ForClient_NormalizesNoterietyButNeverAboveTheCeiling() { var rep = new Reputation { AccountId = 1, Noteriety = 5000 }; var client = rep.ForClient(); Assert.Equal(0.05, client.Noteriety); } }