mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-28 08:28:58 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.Leaderboard;
|
||||
using RadicadeServer.RecNet.Auth;
|
||||
using RadicadeServer.RecNet.Leaderboards;
|
||||
using Xunit;
|
||||
|
||||
namespace RadicadeServer.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The room scoreboard / watch leaderboard path: the client posts a stat with
|
||||
/// CheckAndSetStat (SetStatRequestDTO: StatChannel + RoomId + StatValue, with identity
|
||||
/// coming from the auth token rather than the body), then reads it back with GetRanks,
|
||||
/// GetNearbyScores or GetPlayerRank. These tests drive the store end to end, because the
|
||||
/// conformance suite only ever exercised an empty board.
|
||||
/// </summary>
|
||||
public class LeaderboardTests
|
||||
{
|
||||
private const long RoomA = 25;
|
||||
private const long RoomB = 32;
|
||||
|
||||
private sealed class TestDbContextFactory : IDbContextFactory<RecDBContext>
|
||||
{
|
||||
private readonly DbContextOptions<RecDBContext> options;
|
||||
|
||||
public TestDbContextFactory()
|
||||
{
|
||||
var root = new InMemoryDatabaseRoot();
|
||||
options = TestDbOptions.Create()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString(), root)
|
||||
.Options;
|
||||
}
|
||||
|
||||
public RecDBContext CreateDbContext() => new(options);
|
||||
}
|
||||
|
||||
private static async Task SeedProfilesAsync(IDbContextFactory<RecDBContext> factory, params long[] ids)
|
||||
{
|
||||
await using var db = factory.CreateDbContext();
|
||||
foreach (var id in ids)
|
||||
db.Profiles.Add(new Profile
|
||||
{
|
||||
accountId = id,
|
||||
profileImage = "default.png",
|
||||
username = $"user{id}",
|
||||
displayName = $"User {id}",
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static string Stat(long roomId, int channel, int value) =>
|
||||
$"{{\"StatChannel\":{channel},\"RoomId\":{roomId},\"StatValue\":{value}}}";
|
||||
|
||||
[Fact]
|
||||
public async Task SubmitScore_RanksPlayersAndKeepsRoomsSeparate()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
await SeedProfilesAsync(factory, 2, 3);
|
||||
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, Stat(RoomA, 1, 10), 2, 0);
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, Stat(RoomA, 1, 40), 3, 0);
|
||||
// A different room's board on the same channel number must not leak into RoomA.
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, Stat(RoomB, 1, 999), 2, 0);
|
||||
|
||||
var board = await LeaderboardStore.GetRanksAsync(factory, new GetRanksRequest
|
||||
{
|
||||
RoomId = RoomA,
|
||||
StatChannel = 1,
|
||||
RankStart = 1,
|
||||
RankEnd = 10,
|
||||
});
|
||||
|
||||
Assert.Equal(2, board.Total);
|
||||
Assert.Equal(2, board.Rows.Count);
|
||||
Assert.Equal(3, board.Rows[0].PlayerId);
|
||||
Assert.Equal(40, board.Rows[0].Score);
|
||||
Assert.Equal(1, board.Rows[0].Rank);
|
||||
Assert.Equal(2, board.Rows[1].PlayerId);
|
||||
Assert.Equal(2, board.Rows[1].Rank);
|
||||
Assert.Equal("User 3", board.Rows[0].PlayerName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubmitScore_KeepsEachPlayersBestRatherThanTheirLast()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
await SeedProfilesAsync(factory, 2);
|
||||
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, Stat(RoomA, 1, 50), 2, 0);
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, Stat(RoomA, 1, 10), 2, 0);
|
||||
|
||||
var board = await LeaderboardStore.GetRanksAsync(factory, new GetRanksRequest
|
||||
{
|
||||
RoomId = RoomA,
|
||||
StatChannel = 1,
|
||||
RankStart = 1,
|
||||
RankEnd = 10,
|
||||
});
|
||||
|
||||
Assert.Equal(1, board.Total);
|
||||
Assert.Equal(50, board.Rows[0].Score);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubmitScore_AscendingBoardRanksTheLowerTimeFirst()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
await SeedProfilesAsync(factory, 2, 3);
|
||||
|
||||
// A race board: lower is better, and the client marks it with SortAscending.
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, "{\"StatChannel\":2,\"RoomId\":25,\"Time\":9000}", 2, 0);
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, "{\"StatChannel\":2,\"RoomId\":25,\"Time\":4000}", 3, 0);
|
||||
|
||||
var board = await LeaderboardStore.GetRanksAsync(factory, new GetRanksRequest
|
||||
{
|
||||
RoomId = RoomA,
|
||||
StatChannel = 2,
|
||||
SortAscending = true,
|
||||
RankStart = 1,
|
||||
RankEnd = 10,
|
||||
});
|
||||
|
||||
Assert.Equal(2, board.Total);
|
||||
Assert.Equal(3, board.Rows[0].PlayerId);
|
||||
Assert.Equal(1, board.Rows[0].Rank);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubmitScore_FallsBackToTheAuthenticatedPlayerWhenTheBodyHasNoPlayerId()
|
||||
{
|
||||
// The client's SetStatRequestDTO has no player field at all; identity is the token.
|
||||
var factory = new TestDbContextFactory();
|
||||
await SeedProfilesAsync(factory, 2);
|
||||
|
||||
var result = await LeaderboardStore.SubmitScoreAsync(factory, Stat(RoomA, 3, 7), 2, 0);
|
||||
Assert.NotNull(result);
|
||||
|
||||
await using var db = factory.CreateDbContext();
|
||||
var stored = await db.LeaderboardScores.SingleAsync();
|
||||
Assert.Equal(2, stored.PlayerId);
|
||||
Assert.Equal(7, stored.Score);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetNearbyScores_CentresTheWindowOnTheAskingPlayer()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
await SeedProfilesAsync(factory, 2, 3, 4, 5, 6);
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, Stat(RoomA, 1, 10 * (i + 1)), 2 + i, 0);
|
||||
|
||||
// Player 4 scored 30, so they sit third of five.
|
||||
var nearby = await LeaderboardStore.GetNearbyScoresAsync(factory, new GetNearbyScoresRequest
|
||||
{
|
||||
RoomId = RoomA,
|
||||
StatChannel = 1,
|
||||
PlayerId = 4,
|
||||
WindowSize = 3,
|
||||
});
|
||||
|
||||
Assert.Equal(5, nearby.Total);
|
||||
Assert.Equal(3, nearby.MyRank);
|
||||
Assert.Contains(nearby.Rows, r => r.PlayerId == 4 && r.Rank == 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EmptyBoard_StillNamesTheAskingPlayer()
|
||||
{
|
||||
// The scoreboard renders the row the server hands back, so the synthetic row for
|
||||
// a player who has not scored yet must still carry their name rather than an
|
||||
// empty string. Names were loaded only for players already on the board, which on
|
||||
// an empty board is nobody.
|
||||
var factory = new TestDbContextFactory();
|
||||
await SeedProfilesAsync(factory, 2);
|
||||
|
||||
var ranks = await LeaderboardStore.GetRanksAsync(factory, new GetRanksRequest
|
||||
{
|
||||
RoomId = RoomA,
|
||||
StatChannel = 1,
|
||||
PlayerId = 2,
|
||||
RankStart = 1,
|
||||
RankEnd = 10,
|
||||
});
|
||||
Assert.Equal("User 2", ranks.Rows[0].PlayerName);
|
||||
|
||||
var nearby = await LeaderboardStore.GetNearbyScoresAsync(factory, new GetNearbyScoresRequest
|
||||
{
|
||||
RoomId = RoomA,
|
||||
StatChannel = 1,
|
||||
PlayerId = 2,
|
||||
WindowSize = 10,
|
||||
});
|
||||
Assert.Equal("User 2", nearby.Rows[0].PlayerName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPlayerRank_NamesTheAskingPlayerWhenTheyHaveNotScored()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
await SeedProfilesAsync(factory, 2);
|
||||
|
||||
var rank = await LeaderboardStore.GetPlayerRankAsync(factory, new GetRankRequest
|
||||
{
|
||||
RoomId = RoomA,
|
||||
StatChannel = 1,
|
||||
PlayerId = 2,
|
||||
});
|
||||
|
||||
var name = rank.GetType().GetProperty("PlayerName")?.GetValue(rank) as string;
|
||||
Assert.Equal("User 2", name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPlayerRank_ReportsZeroForAPlayerWhoHasNotScored()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
await SeedProfilesAsync(factory, 2, 3);
|
||||
await LeaderboardStore.SubmitScoreAsync(factory, Stat(RoomA, 1, 10), 3, 0);
|
||||
|
||||
var rank = await LeaderboardStore.GetPlayerRankAsync(factory, new GetRankRequest
|
||||
{
|
||||
RoomId = RoomA,
|
||||
StatChannel = 1,
|
||||
PlayerId = 2,
|
||||
});
|
||||
|
||||
Assert.NotNull(rank);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user