mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RadicadeServer.Admin;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.RecNet.Auth;
|
||||
using RadicadeServer.RecNet.Enums;
|
||||
using RadicadeServer.RecNet.Rooms;
|
||||
using RadicadeServer.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace RadicadeServer.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// <c>IsDeveloperOwned</c> is the flag that files a room under the client's Dev Rooms row, and
|
||||
/// the room payload never carried it at all - so every room read as "not developer-owned" and a
|
||||
/// developer's own rooms were missing from that list. It is a property of the room, stamped when
|
||||
/// the room is made (a player makes a room by CLONING a template - there is no separate create
|
||||
/// route) from whether the maker holds the developer role.
|
||||
///
|
||||
/// These pin the three ways it can come out wrong: stamped from nothing (always false), stamped
|
||||
/// on a dorm (a dev's dorm is a home, not a project), and stamping the room row while the room
|
||||
/// the client is handed still defaults to false.
|
||||
/// </summary>
|
||||
[Collection(DbFactoryCollection.Name)]
|
||||
public class RoomDeveloperOwnedTests
|
||||
{
|
||||
private const long DeveloperId = 2;
|
||||
private const long PlainPlayerId = 3;
|
||||
private const long MakerRoomTemplateId = 23;
|
||||
private const long DormTemplateId = 1;
|
||||
|
||||
private sealed class TestDbContextFactory : IDbContextFactory<RecDBContext>
|
||||
{
|
||||
private readonly DbContextOptions<RecDBContext> options;
|
||||
|
||||
public TestDbContextFactory()
|
||||
{
|
||||
options = TestDbOptions.Create()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
}
|
||||
|
||||
public RecDBContext CreateDbContext() => new(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The shape a player's create-room flow runs against: a clonable template (room 23 is the
|
||||
/// Maker Room the client clones) and the per-player dorm template. Room 1 is what
|
||||
/// <c>GetOrCreateDorm</c> clones, so it has to be a dorm for the dorm case to be real.
|
||||
/// </summary>
|
||||
private static async Task<(TestDbContextFactory Factory, RecDBContext Db)> TemplatesAsync(bool withDeveloperProfile = true)
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
RoomUtils.DbFactory = factory;
|
||||
|
||||
var db = factory.CreateDbContext();
|
||||
|
||||
db.Rooms.Add(new RoomData
|
||||
{
|
||||
RoomId = MakerRoomTemplateId,
|
||||
Name = "MakerRoom",
|
||||
CloningAllowed = true,
|
||||
Accessibility = Accessibility.Public,
|
||||
CreatorAccountId = 1,
|
||||
MaxPlayers = 8,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
});
|
||||
db.SubRooms.Add(new SubRoom
|
||||
{
|
||||
RoomId = MakerRoomTemplateId,
|
||||
Name = "Home",
|
||||
UnitySceneId = SceneLocations.MakerRoom,
|
||||
Accessibility = Accessibility.Public,
|
||||
MaxPlayers = 8,
|
||||
DataBlob = "",
|
||||
});
|
||||
|
||||
db.Rooms.Add(new RoomData
|
||||
{
|
||||
RoomId = DormTemplateId,
|
||||
Name = "DormRoom",
|
||||
IsDorm = true,
|
||||
CloningAllowed = true,
|
||||
Accessibility = Accessibility.Private,
|
||||
CreatorAccountId = 1,
|
||||
MaxPlayers = 4,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
});
|
||||
db.SubRooms.Add(new SubRoom
|
||||
{
|
||||
RoomId = DormTemplateId,
|
||||
Name = "DormRoom",
|
||||
UnitySceneId = "76d98498-60a1-430c-ab76-b54a29b7a163",
|
||||
Accessibility = Accessibility.Private,
|
||||
MaxPlayers = 4,
|
||||
DataBlob = "",
|
||||
});
|
||||
|
||||
db.Profiles.Add(new Profile
|
||||
{
|
||||
accountId = DeveloperId,
|
||||
role = withDeveloperProfile ? "developer" : null,
|
||||
username = "devplayer",
|
||||
displayName = "Dev Player",
|
||||
profileImage = DBConfig.DefaultProfileImage,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return (factory, db);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ARoomMadeByADeveloperIsDeveloperOwned()
|
||||
{
|
||||
var (_, db) = await TemplatesAsync();
|
||||
|
||||
var made = await RoomUtils.CloneRoom(MakerRoomTemplateId, DeveloperId, "My Dev Room");
|
||||
|
||||
Assert.NotNull(made);
|
||||
Assert.Equal(DeveloperId, made!.CreatorAccountId);
|
||||
Assert.True(made.IsDeveloperOwned);
|
||||
|
||||
// The room the client is handed and the row it came from have to agree - the response is
|
||||
// what the Dev Rooms row is built from, and the row is what the next read answers with.
|
||||
Assert.True(db.Rooms.Single(x => x.Name == "My Dev Room").IsDeveloperOwned);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ARoomMadeByAPlainPlayerIsNotDeveloperOwned()
|
||||
{
|
||||
var (_, db) = await TemplatesAsync();
|
||||
|
||||
var made = await RoomUtils.CloneRoom(MakerRoomTemplateId, PlainPlayerId, "Plain Room");
|
||||
|
||||
Assert.NotNull(made);
|
||||
Assert.False(made!.IsDeveloperOwned);
|
||||
Assert.False(db.Rooms.Single(x => x.Name == "Plain Room").IsDeveloperOwned);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ADevelopersDormIsNotDeveloperOwned()
|
||||
{
|
||||
var (_, db) = await TemplatesAsync();
|
||||
|
||||
var dorm = await RoomUtils.GetOrCreateDorm(DeveloperId);
|
||||
|
||||
Assert.NotNull(dorm);
|
||||
Assert.True(dorm!.IsDorm);
|
||||
|
||||
// The dorm is cloned by the same code path that creates rooms, so without the dorm guard
|
||||
// a developer's own dorm came back developer-owned.
|
||||
Assert.False(dorm.IsDeveloperOwned);
|
||||
Assert.False(db.Rooms.Single(x => x.IsDorm && x.CreatorAccountId == DeveloperId).IsDeveloperOwned);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TheRoomTheClientReadsCarriesTheFlag()
|
||||
{
|
||||
await TemplatesAsync();
|
||||
await RoomUtils.CloneRoom(MakerRoomTemplateId, DeveloperId, "Read Back");
|
||||
|
||||
var read = await RoomUtils.GetRoom("Read Back");
|
||||
|
||||
Assert.NotNull(read);
|
||||
Assert.True(read!.IsDeveloperOwned);
|
||||
|
||||
// The 2023 client reads room payloads by their declared names (Program.cs pins the
|
||||
// serializer to no naming policy), the same PascalCase the real API answers with.
|
||||
var json = JsonSerializer.Serialize<RoomDetail>(read);
|
||||
Assert.Contains("\"IsDeveloperOwned\":true", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APayloadWithoutTheFieldReadsAsFalse()
|
||||
{
|
||||
// Every room that predates the column, and every room an importer does not say otherwise
|
||||
// about, serializes the field explicitly - a missing property is what makes the client's
|
||||
// parser fall back to its own default.
|
||||
var json = JsonSerializer.Serialize<RoomDetail>(new FullRoom { Name = "Imported" });
|
||||
|
||||
Assert.Contains("\"IsDeveloperOwned\":false", json);
|
||||
}
|
||||
|
||||
// ---- The admin create route, the path every import uses -------------------------------
|
||||
|
||||
private const string Secret = "test-bot-secret";
|
||||
|
||||
private static async Task<IActionResult> CreateRoomAsync(RecDBContext db, long newOwner, string json)
|
||||
{
|
||||
var api = new AdminRoomAPI(db);
|
||||
var original = DBConfig.AdminApiKey;
|
||||
DBConfig.AdminApiKey = Secret;
|
||||
try
|
||||
{
|
||||
return await api.CreateRoom(Secret, newOwner, JsonNode.Parse(json)!);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DBConfig.AdminApiKey = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnImportedRoomWithNoOpinionTakesTheOwnersRoles()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
var db = factory.CreateDbContext();
|
||||
db.Profiles.Add(new Profile
|
||||
{
|
||||
accountId = DeveloperId,
|
||||
role = "developer",
|
||||
username = "devplayer",
|
||||
displayName = "Dev Player",
|
||||
profileImage = DBConfig.DefaultProfileImage,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
Assert.IsType<OkObjectResult>(await CreateRoomAsync(db, DeveloperId, """{ "Name": "DerivedDevRoom" }"""));
|
||||
|
||||
Assert.True(db.Rooms.Single(x => x.Name == "DerivedDevRoom").IsDeveloperOwned);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnImportedRoomThatSaysItIsDeveloperOwnedIsBelieved()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
var db = factory.CreateDbContext();
|
||||
|
||||
// A captured RRO room is developer-owned on the real service even though it is recreated
|
||||
// here under a local (non-developer) owner, so an explicit value wins.
|
||||
Assert.IsType<OkObjectResult>(await CreateRoomAsync(db, PlainPlayerId,
|
||||
"""{ "Name": "ImportedRRO", "IsRRO": true, "IsDeveloperOwned": true }"""));
|
||||
|
||||
Assert.True(db.Rooms.Single(x => x.Name == "ImportedRRO").IsDeveloperOwned);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ANonDeveloperOwnerMakesAPlainRoom()
|
||||
{
|
||||
var factory = new TestDbContextFactory();
|
||||
var db = factory.CreateDbContext();
|
||||
|
||||
Assert.IsType<OkObjectResult>(await CreateRoomAsync(db, PlainPlayerId,
|
||||
"""{ "Name": "PlayerRoom", "IsDeveloperOwned": false }"""));
|
||||
|
||||
Assert.False(db.Rooms.Single(x => x.Name == "PlayerRoom").IsDeveloperOwned);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user