mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
140 lines
5.0 KiB
C#
140 lines
5.0 KiB
C#
using System.Reflection;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet.Auth;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
/// <summary>
|
|
/// The role lookup the client uses to decide whether to show developer-only content.
|
|
///
|
|
/// The 2023 client builds this URL from a literal of the form <c>role/{0}/{1}</c> and resolves
|
|
/// it against its RecNet base, and the reference implementation serves it at the host root
|
|
/// (<c>GET /role/developer/:id</c>). This server mounted it only under <c>auth/role/</c>, so on a
|
|
/// client whose base does not end in <c>auth/</c> the lookup 404s - measured against the live
|
|
/// server: <c>/role/developer/6</c> answered 404 while <c>/auth/role/developer/6</c> answered 200.
|
|
/// A 404 here does not error visibly; it just silently reports "not a developer", which hides
|
|
/// everything the role gates.
|
|
///
|
|
/// This is the same class of bug the equipment backpack hit - a correct handler behind a missing
|
|
/// prefix - which is why the route assertions are the point of this file.
|
|
/// </summary>
|
|
[Collection(DbFactoryCollection.Name)]
|
|
public class RoleLookupTests
|
|
{
|
|
private static RecDBContext NewDb() => new(
|
|
TestDbOptions.Create()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options);
|
|
|
|
private static async Task<RecDBContext> WithProfile(long accountId, string? roles)
|
|
{
|
|
var db = NewDb();
|
|
db.Profiles.Add(new Profile
|
|
{
|
|
accountId = accountId,
|
|
role = roles,
|
|
username = $"player{accountId}",
|
|
displayName = $"Player {accountId}",
|
|
profileImage = DBConfig.DefaultProfileImage
|
|
});
|
|
await db.SaveChangesAsync();
|
|
return db;
|
|
}
|
|
|
|
/// <summary>Every route the client can reach this lookup on: prefix + "{roleType}/{id}".</summary>
|
|
private static string[] Routes()
|
|
{
|
|
var method = typeof(RolesAPI).GetMethod(nameof(RolesAPI.GetDeveloperRole))!;
|
|
var templates = method.GetCustomAttributes<HttpGetAttribute>().Select(a => a.Template).ToArray();
|
|
|
|
return typeof(RolesAPI).GetCustomAttributes<RouteAttribute>()
|
|
.SelectMany(prefix => templates.Select(suffix => $"{prefix.Template}/{suffix}"))
|
|
.ToArray();
|
|
}
|
|
|
|
[Fact]
|
|
public void TheRoleLookupAnswersAtTheRootPathTheClientBuilds()
|
|
{
|
|
var routes = Routes();
|
|
|
|
// What the client's literal resolves to when its base is the host root, and what the
|
|
// reference implementation serves.
|
|
Assert.Contains("role/{roleType}/{id}", routes);
|
|
|
|
// The path this server already had, kept so nothing that works today starts 404ing.
|
|
Assert.Contains("auth/role/{roleType}/{id}", routes);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("developer")]
|
|
[InlineData("moderator")]
|
|
public void BothWalkedRolesReachTheLookup(string roleType)
|
|
{
|
|
var routes = Routes().Select(route => route.Replace("{roleType}", roleType)).ToArray();
|
|
|
|
Assert.Contains($"role/{roleType}/{{id}}", routes);
|
|
Assert.Contains($"auth/role/{roleType}/{{id}}", routes);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TheLookupReportsTheProfilesRoles()
|
|
{
|
|
using var db = await WithProfile(6, "developer,moderator");
|
|
var api = new RolesAPI(db);
|
|
|
|
Assert.True(await api.GetDeveloperRole("developer", 6));
|
|
Assert.True(await api.GetDeveloperRole("moderator", 6));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ARoleTheProfileDoesNotHoldIsFalse()
|
|
{
|
|
using var db = await WithProfile(6, "developer");
|
|
var api = new RolesAPI(db);
|
|
|
|
// The client reads a bare boolean, so a role that is not held has to answer false
|
|
// rather than error - it is a gate, not a lookup that can fail.
|
|
Assert.False(await api.GetDeveloperRole("moderator", 6));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnUnknownOrRolelessAccountIsNotADeveloper()
|
|
{
|
|
using var roleless = await WithProfile(1, null);
|
|
Assert.False(await new RolesAPI(roleless).GetDeveloperRole("developer", 1));
|
|
|
|
using var db = NewDb();
|
|
Assert.False(await new RolesAPI(db).GetDeveloperRole("developer", 99999));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RolesAreMatchedCaseInsensitivelyAndTrimmed()
|
|
{
|
|
// The admin panel writes whatever the operator picked, joined with commas.
|
|
using var db = await WithProfile(6, " Developer , Moderator ");
|
|
var api = new RolesAPI(db);
|
|
|
|
Assert.True(await api.GetDeveloperRole("developer", 6));
|
|
Assert.True(await api.GetDeveloperRole("moderator", 6));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GrantingTheDeveloperRoleFlipsTheLookup()
|
|
{
|
|
using var db = await WithProfile(6, null);
|
|
var api = new RolesAPI(db);
|
|
|
|
Assert.False(await api.GetDeveloperRole("developer", 6));
|
|
|
|
var profile = await db.Profiles.FirstAsync(x => x.accountId == 6);
|
|
profile.role = "developer";
|
|
await db.SaveChangesAsync();
|
|
|
|
Assert.True(await api.GetDeveloperRole("developer", 6));
|
|
}
|
|
}
|