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);
}
}