using System.Text.Json; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using RadicadeServer.API; using RadicadeServer.DB; using RadicadeServer.RecNet.Config; using RadicadeServer.RecNet.Enums; using RadicadeServer.RecNet.VersionCheck; using Xunit; namespace RadicadeServer.Tests; /// /// A Game Update is not a notification. The client registers no handler for 26 or 27 - and none for /// ModerationUpdateRequired (21) either - so a frame under any of those ids is dropped in silence. /// What the client does read is its REST version check, api/versioncheck/v4, and that answer is what /// raises its own "Update Required" screen. So a published Game Update row has to change this /// answer, and these tests pin both halves: the status flip, and the no-game-update case staying /// exactly the shape it has always been. /// public class GameUpdateVersionCheckTests { private const string Live = "[{\"id\":1,\"type\":\"Game Update\"}]"; private const string Expired = "[{\"id\":1,\"type\":\"Game Update\",\"expires_at\":\"2000-01-01T00:00:00Z\"}]"; private const string Future = "[{\"id\":1,\"type\":\"Game Update\",\"expires_at\":\"2999-01-01T00:00:00Z\"}]"; private const string Regular = "[{\"id\":1,\"type\":\"Regular Announcement\"}]"; private static RecDBContext NewDb() => new( TestDbOptions.Create() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options); [Fact] public void NoAnnouncementConfigLeavesTheVersionCheckAlone() { var result = GameUpdateVersionCheck.Resolve(null, DateTime.UtcNow); Assert.Equal(VersionCheckStatus.ValidForPlay, result.VersionStatus); Assert.Equal(0, result.UpdateNotificationStage); } [Theory] [InlineData("")] [InlineData("[]")] [InlineData(Regular)] [InlineData("not json at all")] [InlineData("{\"type\":\"Game Update\"}")] [InlineData(Expired)] public void AnythingThatIsNotALiveGameUpdateKeepsPlayingAllowed(string json) { var result = GameUpdateVersionCheck.Resolve(json, DateTime.UtcNow); Assert.Equal(VersionCheckStatus.ValidForPlay, result.VersionStatus); } [Theory] [InlineData(Live)] [InlineData(Future)] [InlineData("[{\"id\":1,\"type\":\"game update\"}]")] [InlineData("[{\"id\":1,\"type\":\"Regular Announcement\"},{\"id\":2,\"type\":\"Game Update\"}]")] public void ALiveGameUpdateAsksTheClientToUpdate(string json) { var result = GameUpdateVersionCheck.Resolve(json, DateTime.UtcNow); Assert.Equal(VersionCheckStatus.UpdateRequired, result.VersionStatus); // Not 0: 0 is what a healthy boot already gets, so it is the one value that could also mean // "nothing to show". Assert.True(result.UpdateNotificationStage > 0); } [Fact] public async Task TheVersionCheckAnswerCarriesUpdateRequiredWhileAGameUpdateIsLive() { using var db = NewDb(); db.ServerConfig.Add(new ServerConfigVal(GameUpdateVersionCheck.AnnouncementConfigKey, Live)); await db.SaveChangesAsync(); var api = new VersionCheckAPI(db); var ok = Assert.IsType(await api.Get("20230831", "20230831")); using var doc = JsonDocument.Parse(JsonSerializer.Serialize(ok.Value)); Assert.Equal((int)VersionCheckStatus.UpdateRequired, doc.RootElement.GetProperty("VersionStatus").GetInt32()); Assert.True(doc.RootElement.GetProperty("UpdateNotificationStage").GetInt32() > 0); Assert.Equal(0, doc.RootElement.GetProperty("ValidVersion").GetInt32()); Assert.False(doc.RootElement.GetProperty("IsVersionIslanded").GetBoolean()); Assert.False(doc.RootElement.GetProperty("IsCrossPlayDisabled").GetBoolean()); // The key set is what the client's strict lookups depend on: ValidVersion must be present or // the client aborts boot. It must not grow or shrink either. Assert.Equal(5, doc.RootElement.EnumerateObject().Count()); } [Fact] public async Task TheVersionCheckAnswerIsUnchangedWhenThereIsNoGameUpdate() { using var db = NewDb(); db.ServerConfig.Add(new ServerConfigVal(GameUpdateVersionCheck.AnnouncementConfigKey, Regular)); await db.SaveChangesAsync(); var api = new VersionCheckAPI(db); var ok = Assert.IsType(await api.Get("20230831", "20230831")); using var doc = JsonDocument.Parse(JsonSerializer.Serialize(ok.Value)); Assert.Equal(0, doc.RootElement.GetProperty("VersionStatus").GetInt32()); Assert.Equal(0, doc.RootElement.GetProperty("UpdateNotificationStage").GetInt32()); } [Fact] public void TheLiveRecheckFrameGoesOutUnderTheNameTheClientRegisters() { // The one game-update-relevant key in the client's handler table; 26 and 27 are not in it. Assert.Equal("AppVersionUpdate", GameUpdateVersionCheck.AppVersionUpdateNotificationId); } }