using System.Text.Json; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using RadicadeServer.Admin; using RadicadeServer.DB; using RadicadeServer.RecNet.Auth; using RadicadeServer.RecNet.Moderation; using Xunit; namespace RadicadeServer.Tests; /// /// PATCH admin/panel/players/{id} with the values the panel echoes back from its own GET. /// /// Noteriety and ReputationScore are doubles on Reputation (NoterietyPerCheer is 0.5), so an account /// that has been cheered carries a fractional value. The endpoint used to read them with GetInt32, /// which throws System.FormatException on a fractional JSON number - the whole edit answered 500 and /// saved nothing. Because only accounts with a fractional score hit it, it looked account-specific. /// public class AdminPanelFractionalReputationTests { private const string Secret = "test-bot-secret"; private const long Player = 3; private static RecDBContext NewDb() => new( TestDbOptions.Create() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options); private static async Task WithPlayer() { var db = NewDb(); db.Profiles.Add(new Profile { accountId = Player, username = "fractional", displayName = "Fractional Player", profileImage = "" }); db.Reputations.Add(new Reputation { AccountId = Player, Noteriety = 0.5, ReputationScore = 7.5 }); await db.SaveChangesAsync(); return db; } private static async Task Patch(RecDBContext db, string json) { var payload = JsonDocument.Parse(json).RootElement.Clone(); var api = new AdminPanelAPI(db); var original = DBConfig.AdminApiKey; DBConfig.AdminApiKey = Secret; try { return await api.Update("players", Player, Secret, payload); } finally { DBConfig.AdminApiKey = original; } } [Fact] public async Task AFractionalNoterietyIsAcceptedAndStoredAsADouble() { using var db = await WithPlayer(); // Exactly what the panel sends for an account whose score is not a whole number. var result = await Patch(db, """ { "display_name": "Fractional Player", "reputation": 9.5, "noteriety": 1.5, "cheer_general": 3 } """); Assert.IsType(result); var rep = await db.Reputations.SingleAsync(x => x.AccountId == Player); Assert.Equal(1.5, rep.Noteriety); Assert.Equal(9.5, rep.ReputationScore); Assert.Equal(3, rep.CheerGeneral); } [Fact] public async Task AnOutOfRangeOrNumericStringIntegerDoesNotThrow() { using var db = await WithPlayer(); // A value Int32 cannot hold, and an integer sent as a string, must both land (clamped / // parsed) rather than 500 the edit. var result = await Patch(db, """ { "noteriety": 0.5, "cheer_helpful": "4", "cheer_creative": 9999999999999999999 } """); Assert.IsType(result); var rep = await db.Reputations.SingleAsync(x => x.AccountId == Player); Assert.Equal(4, rep.CheerHelpful); Assert.Equal(int.MaxValue, rep.CheerCreative); } }