mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 07:29:00 -07:00
251 lines
8.8 KiB
C#
251 lines
8.8 KiB
C#
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);
|
|
}
|
|
}
|