using System.Text.Json.Nodes; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using RadicadeServer.Admin; using RadicadeServer.DB; using RadicadeServer.RecNet; using RadicadeServer.RecNet.Enums; using RadicadeServer.RecNet.Enums.Rooms; using RadicadeServer.RecNet.Rooms; using Xunit; namespace RadicadeServer.Tests; /// /// Room creation through POST admin/rooms/create/room, which is how every import puts a /// room on this server (the Taco archives, tools/import_room_dump.mjs). /// /// The route reads its body with a helper that only knew scalars. Enums and arrays are neither, /// so Accessibility, WarningMask, MaxPlayerCalculationMode and Tags /// silently took their defaults while the strings and bools around them bound: an imported room /// arrived Private (invisible to every list, unsearchable) and untagged, with nothing in the /// response to say a field had been dropped. These pin the whole payload, and the defaults the /// route still falls back to when a field is absent. /// public class AdminRoomCreateTests { private static RecDBContext NewDb() => new( TestDbOptions.Create() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options); private const string Secret = "test-bot-secret"; private static async Task CreateAsync(string json) { var db = NewDb(); var api = new AdminRoomAPI(db); var original = DBConfig.AdminApiKey; DBConfig.AdminApiKey = Secret; try { var result = await api.CreateRoom(Secret, 2, JsonNode.Parse(json)!); Assert.IsType(result); } finally { DBConfig.AdminApiKey = original; } return db; } private const string RoomPayload = """ { "Name": "MakeItToMidnight", "Description": "A night of horror ensues.", "Accessibility": 1, "State": 0, "IsRRO": true, "IsDorm": false, "ImageName": "default.png", "MaxPlayers": 5, "MaxPlayerCalculationMode": 1, "WarningMask": 1, "CloningAllowed": false, "SupportsScreens": 1, "SupportsWalkVR": true, "SupportsTeleportVR": "true", "SupportsJuniors": "1", "Tags": [ { "Tag": "import", "Type": 1 } ], "CreatedAt": "2023-07-26T23:23:35Z", "SubRooms": [ { "Name": "Home", "Accessibility": 1, "IsSandbox": true, "MaxPlayers": 5, "UnitySceneId": "a75f7547-79eb-47c6-8986-6767abcb4f92", "DataBlob": "aq4a6u116zm6uathf9i9b7n0y.room" } ] } """; [Fact] public async Task AnImportedRoomKeepsItsAccessibilityWarningMaskAndPlayerCountMode() { var db = await CreateAsync(RoomPayload); var room = db.Rooms.Single(); Assert.Equal(Accessibility.Public, room.Accessibility); Assert.Equal(WarningMask.Scary, room.WarningMask); Assert.Equal(MaxPlayerMode.OnlyEntrySubrooms, room.MaxPlayerCalculationMode); Assert.Equal(5, room.MaxPlayers); Assert.True(room.IsRRO); Assert.False(room.IsDorm); Assert.Equal(2, room.CreatorAccountId); Assert.Equal(new DateTime(2023, 7, 26, 23, 23, 35), room.CreatedAt); } [Fact] public async Task TheTagListIsKeptWithItsTagTypes() { var db = await CreateAsync(RoomPayload); // One tag, of type Auto - the same "name,type|name,type" string the room's own tag // screen writes, which is what the tag doors and the list queries read back. Assert.Equal("import,1", db.Rooms.Single().Tags); } [Fact] public async Task EnumsArriveAsNumbersOrNamesAndBoolsAsTrueOnesOrStrings() { var db = await CreateAsync(""" { "Name": "NamedEnums", "Accessibility": "Public", "WarningMask": "Scary", "MaxPlayerCalculationMode": "OnlyEntrySubrooms", "SupportsScreens": 1, "SupportsWalkVR": true, "SupportsQuest2": "true", "SupportsTeleportVR": "false", "IsRRO": 1 } """); var room = db.Rooms.Single(); Assert.Equal(Accessibility.Public, room.Accessibility); Assert.Equal(WarningMask.Scary, room.WarningMask); Assert.Equal(MaxPlayerMode.OnlyEntrySubrooms, room.MaxPlayerCalculationMode); Assert.True(room.SupportsScreens); Assert.True(room.SupportsWalkVR); Assert.True(room.SupportsQuest2); Assert.False(room.SupportsTeleportVR); Assert.True(room.IsRRO); } [Fact] public async Task AnAbsentFieldStillTakesTheRoutesDefault() { var db = await CreateAsync("""{ "Name": "BareRoom" }"""); var room = db.Rooms.Single(); Assert.Equal(Accessibility.Private, room.Accessibility); Assert.Equal(WarningMask.None, room.WarningMask); Assert.Equal(MaxPlayerMode.AllSubrooms, room.MaxPlayerCalculationMode); Assert.Equal("", room.Tags); Assert.False(room.IsRRO); // LoadScreens is the one field whose absence used to fail the insert outright: the // column is NOT NULL and the payload's missing array was written over the default. Assert.Equal("[]", room.loadScreen); } [Fact] public async Task TheSubroomKeepsTheSceneAndTheSaveItWasImportedWith() { var db = await CreateAsync(RoomPayload); var subRoom = db.SubRooms.Single(); Assert.Equal("Home", subRoom.Name); Assert.Equal("a75f7547-79eb-47c6-8986-6767abcb4f92", subRoom.UnitySceneId); Assert.Equal("aq4a6u116zm6uathf9i9b7n0y.room", subRoom.DataBlob); Assert.Equal(2, subRoom.SavedByAccountId); Assert.True(subRoom.IsSandbox); Assert.Equal(Accessibility.Public, subRoom.Accessibility); } [Fact] public async Task ASceneTheClientDoesNotKnowBecomesTheMakerRoom() { var db = await CreateAsync(""" { "Name": "OddScene", "SubRooms": [ { "Name": "Home", "UnitySceneId": "not-a-scene", "DataBlob": "empty.room" } ] } """); Assert.Equal(SceneLocations.MakerRoom, db.SubRooms.Single().UnitySceneId); } }