mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
127 lines
4.6 KiB
C#
127 lines
4.6 KiB
C#
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.Admin;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet.Auth;
|
|
using RadicadeServer.RecNet.Auth.Cached;
|
|
using RadicadeServer.RecNet.Enums.Players;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
/// <summary>
|
|
/// The players list sends joined_at and last_online_at so the panel can render them in the
|
|
/// admin's own time zone. SQL Server hands a datetime2 back with Kind=Unspecified, and
|
|
/// System.Text.Json writes such a value with no trailing "Z"; a browser parses a zone-less ISO
|
|
/// string as already-local time, so an offline player's "Last Online" sat one admin time-zone
|
|
/// offset away from the truth - it looked hours into the future. Only the online branch carried
|
|
/// the marker, because there the value is DateTime.UtcNow. These tests pin the instants down
|
|
/// exactly: the fix is the marker, never a shift of the clock itself.
|
|
/// </summary>
|
|
public class AdminPanelPlayerTimesTests
|
|
{
|
|
private const string Secret = "test-bot-secret";
|
|
private const long Player = 2;
|
|
|
|
/// <summary>Unspecified is what SQL Server gives back for a datetime2, so that is what the fixtures store.</summary>
|
|
private static readonly DateTime SignedUp = new(2026, 7, 29, 0, 56, 0, DateTimeKind.Unspecified);
|
|
private static readonly DateTime LastLogin = new(2026, 9, 30, 21, 38, 0, DateTimeKind.Unspecified);
|
|
|
|
private static RecDBContext NewDb() => new(
|
|
TestDbOptions.Create()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options);
|
|
|
|
/// <summary>A database holding one player, with or without the cached login row that records their last sign-in.</summary>
|
|
private static async Task<RecDBContext> WithPlayer(bool withLogin)
|
|
{
|
|
var db = NewDb();
|
|
db.Profiles.Add(new Profile
|
|
{
|
|
accountId = Player,
|
|
username = "coach",
|
|
displayName = "Coach",
|
|
profileImage = "",
|
|
createdAt = SignedUp
|
|
});
|
|
|
|
if (withLogin)
|
|
{
|
|
db.CachedLogins.Add(new CachedLogin
|
|
{
|
|
accountId = Player,
|
|
platform = Platforms.Steam,
|
|
platformId = "76561198000000000",
|
|
lastLoginTime = LastLogin
|
|
});
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
return db;
|
|
}
|
|
|
|
/// <summary>The two timestamps exactly as the panel's JSON.parse receives them.</summary>
|
|
private static async Task<(string Joined, string LastOnline)> PlayerTimes(RecDBContext db)
|
|
{
|
|
var context = new DefaultHttpContext();
|
|
context.Request.QueryString = new QueryString("?key=" + Secret);
|
|
var api = new AdminPanelAPI(db)
|
|
{
|
|
ControllerContext = new ControllerContext { HttpContext = context }
|
|
};
|
|
|
|
var original = DBConfig.AdminApiKey;
|
|
DBConfig.AdminApiKey = Secret;
|
|
try
|
|
{
|
|
var ok = Assert.IsType<OkObjectResult>(await api.List("players", Secret, null, null, null, true));
|
|
|
|
// Same writer the panel is served by, so this is the wire text the browser sees
|
|
// rather than a stand-in for it.
|
|
using var document = JsonDocument.Parse(JsonSerializer.Serialize(ok.Value));
|
|
var row = document.RootElement[0];
|
|
return (row.GetProperty("joined_at").GetString()!, row.GetProperty("last_online_at").GetString()!);
|
|
}
|
|
finally
|
|
{
|
|
DBConfig.AdminApiKey = original;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnOfflinePlayersLastOnlineIsMarkedAsUtc()
|
|
{
|
|
var db = await WithPlayer(withLogin: true);
|
|
|
|
var (_, lastOnline) = await PlayerTimes(db);
|
|
|
|
// 21:38 UTC is the moment they signed out, and the panel turns that into whatever o'clock
|
|
// the admin's own zone reads it as. Without the "Z" the browser read it as 21:38 local.
|
|
Assert.Equal("2026-09-30T21:38:00Z", lastOnline);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TheAccountAgeIsMarkedAsUtcToo()
|
|
{
|
|
var db = await WithPlayer(withLogin: true);
|
|
|
|
var (joined, _) = await PlayerTimes(db);
|
|
|
|
Assert.Equal("2026-07-29T00:56:00Z", joined);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task APlayerWhoNeverLoggedInFallsBackToTheirAccountAge()
|
|
{
|
|
var db = await WithPlayer(withLogin: false);
|
|
|
|
var (joined, lastOnline) = await PlayerTimes(db);
|
|
|
|
// No cached login row means the profile's own createdAt is the newest thing known about them.
|
|
Assert.Equal("2026-07-29T00:56:00Z", joined);
|
|
Assert.Equal("2026-07-29T00:56:00Z", lastOnline);
|
|
}
|
|
}
|