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,400 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RadicadeServer.Admin;
|
||||
using RadicadeServer.API.Avatars.V2;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.RecNet.Avatar;
|
||||
using RadicadeServer.RecNet.Rooms;
|
||||
using RadicadeServer.Utils;
|
||||
using Xunit;
|
||||
|
||||
namespace RadicadeServer.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// A room hands players one or more boxes when they join. The boxes live in RoomJoinGifts, one row
|
||||
/// each, and the panel edits them as a list behind Room Details.
|
||||
///
|
||||
/// A room saved before that list existed still describes one box on its own Rooms row; it keeps
|
||||
/// handing that out, which is what every room did before the list existed. A room with a list is
|
||||
/// read from the list alone, and each box is claimed on its own, so editing one box re-issues that
|
||||
/// box without silencing the others.
|
||||
///
|
||||
/// These tests drive the real admin route and then read the boxes the way a client does -
|
||||
/// GET api/avatar/v2/gifts - rather than inspecting the rows the route answered with.
|
||||
[Collection(DbFactoryCollection.Name)]
|
||||
public class RoomJoinGiftListTests
|
||||
{
|
||||
private const string Secret = "room-join-gift-list-key";
|
||||
private const long RoomId = 911;
|
||||
private const long Joiner = 114903603133502227;
|
||||
private const long Sender = 733901112234567890;
|
||||
|
||||
private static RecDBContext NewDb() => new(
|
||||
TestDbOptions.Create()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options);
|
||||
|
||||
private static async Task<RecDBContext> NewRoomAsync(string name = "RecCenter")
|
||||
{
|
||||
var db = NewDb();
|
||||
db.Rooms.Add(new RoomData
|
||||
{
|
||||
RoomId = RoomId,
|
||||
Name = name,
|
||||
CreatorAccountId = 1
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
return db;
|
||||
}
|
||||
|
||||
/// <summary>Patches the room the way the panel does, and returns the answer.</summary>
|
||||
private static async Task<IActionResult> SaveRoomAsync(RecDBContext db, string payloadJson)
|
||||
{
|
||||
var api = new AdminPanelAPI(db);
|
||||
var originalKey = DBConfig.AdminApiKey;
|
||||
DBConfig.AdminApiKey = Secret;
|
||||
try
|
||||
{
|
||||
var payload = JsonDocument.Parse(payloadJson).RootElement;
|
||||
return await api.Update("rooms", RoomId, Secret, payload);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DBConfig.AdminApiKey = originalKey;
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<IActionResult> SaveGiftsAsync(RecDBContext db, params object[] gifts)
|
||||
{
|
||||
var payload = JsonSerializer.Serialize(new { join_gifts = gifts });
|
||||
return SaveRoomAsync(db, payload);
|
||||
}
|
||||
|
||||
/// <summary>One box as the panel sends it.</summary>
|
||||
private static object Gift(long id, string title, int currency, bool enabled = true,
|
||||
bool oncePerPlayer = true, string fromPlayerId = "", string message = "")
|
||||
{
|
||||
return new
|
||||
{
|
||||
id,
|
||||
enabled,
|
||||
once_per_player = oncePerPlayer,
|
||||
title,
|
||||
message,
|
||||
from_player_id = fromPlayerId,
|
||||
avatar_item = "",
|
||||
avatar_item_type = "Outfit",
|
||||
equipment_item = "",
|
||||
equipment_modification_guid = "",
|
||||
consumable_item = "",
|
||||
currency_type = "RecCenterTokens",
|
||||
currency_amount = currency,
|
||||
xp = 0,
|
||||
context = "Game_Drop",
|
||||
rarity = "Common"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>The boxes as the recipient's client reads them: GET api/avatar/v2/gifts.</summary>
|
||||
private static async Task<List<Gift>> ClientGiftsAsync(RecDBContext db, long playerId)
|
||||
{
|
||||
var api = new AvatarV2(db)
|
||||
{
|
||||
ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext
|
||||
{
|
||||
User = new ClaimsPrincipal(new ClaimsIdentity(
|
||||
[new Claim(ClaimTypes.NameIdentifier, playerId.ToString())], "test"))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var result = await api.GetGifts();
|
||||
var ok = Assert.IsType<OkObjectResult>(result);
|
||||
return Assert.IsAssignableFrom<IEnumerable<Gift>>(ok.Value).ToList();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TwoBoxesAreBothHandedToTheJoiner()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
Assert.IsType<OkObjectResult>(await SaveGiftsAsync(db,
|
||||
Gift(0, "Welcome!", 25),
|
||||
Gift(0, "Thanks!", 5)));
|
||||
|
||||
Assert.True(await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId));
|
||||
|
||||
var boxes = await ClientGiftsAsync(db, Joiner);
|
||||
Assert.Equal(2, boxes.Count);
|
||||
Assert.Equal(new[] { "Welcome!", "Thanks!" }, boxes.Select(x => x.FriendlyName).OrderBy(x => x == "Welcome!" ? 0 : 1));
|
||||
Assert.Equal(2, boxes.Select(x => x.GiftDropId).Distinct().Count());
|
||||
Assert.Equal([25, 5], boxes.Select(x => x.Currency).OrderByDescending(x => x));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnOrderedListIsHandedOutInTheOrderThePanelShowsIt()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db,
|
||||
Gift(0, "First", 1),
|
||||
Gift(0, "Second", 2),
|
||||
Gift(0, "Third", 3));
|
||||
|
||||
await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId);
|
||||
|
||||
// Each box names itself, so the client's list can be read in the order the room stores.
|
||||
var stored = await db.RoomJoinGifts.Where(x => x.RoomId == RoomId)
|
||||
.OrderBy(x => x.SortOrder).ToListAsync();
|
||||
Assert.Equal(new[] { "First", "Second", "Third" }, stored.Select(x => x.Title));
|
||||
Assert.Equal(new[] { 0, 1, 2 }, stored.Select(x => x.SortOrder));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EachBoxIsClaimedOnItsOwn()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db, Gift(0, "Welcome!", 25), Gift(0, "Thanks!", 5));
|
||||
|
||||
Assert.True(await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId));
|
||||
// Nothing changed, so the second visit is silent.
|
||||
Assert.False(await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId));
|
||||
|
||||
Assert.Equal(2, (await ClientGiftsAsync(db, Joiner)).Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AChangedBoxIsHandedOutAgainWithoutReissuingTheOther()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db, Gift(0, "Welcome!", 25), Gift(0, "Thanks!", 5));
|
||||
await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId);
|
||||
|
||||
var saved = (await db.RoomJoinGifts.Where(x => x.RoomId == RoomId)
|
||||
.OrderBy(x => x.SortOrder).ToListAsync());
|
||||
var firstId = saved[0].RoomJoinGiftId;
|
||||
var secondId = saved[1].RoomJoinGiftId;
|
||||
|
||||
// The panel posts the whole list every save: only the second box's amount changed.
|
||||
await SaveGiftsAsync(db,
|
||||
Gift(firstId, "Welcome!", 25),
|
||||
Gift(secondId, "Thanks!", 50));
|
||||
|
||||
Assert.True(await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId));
|
||||
|
||||
var boxes = await ClientGiftsAsync(db, Joiner);
|
||||
Assert.Equal(3, boxes.Count);
|
||||
Assert.Equal(3, boxes.Select(x => x.GiftDropId).Distinct().Count());
|
||||
Assert.Single(boxes, x => x.Currency == 50);
|
||||
|
||||
// The box that did not change was not handed out a second time.
|
||||
var unchanged = await db.RoomJoinGifts.SingleAsync(x => x.RoomJoinGiftId == firstId);
|
||||
Assert.Equal(1, unchanged.Version);
|
||||
Assert.Equal(2, (await db.RoomJoinGifts.SingleAsync(x => x.RoomJoinGiftId == secondId)).Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ABoxLeftOutOfTheListIsNoLongerHandedOut()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db, Gift(0, "Welcome!", 25), Gift(0, "Thanks!", 5));
|
||||
var first = (await db.RoomJoinGifts.OrderBy(x => x.SortOrder).FirstAsync()).RoomJoinGiftId;
|
||||
|
||||
await SaveGiftsAsync(db, Gift(first, "Welcome!", 25));
|
||||
|
||||
Assert.Single(await db.RoomJoinGifts.Where(x => x.RoomId == RoomId).ToListAsync());
|
||||
await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId);
|
||||
var boxes = Assert.Single(await ClientGiftsAsync(db, Joiner));
|
||||
Assert.Equal("Welcome!", boxes.FriendlyName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ADisabledBoxIsSkippedAndTheRestStillArrive()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db,
|
||||
Gift(0, "Welcome!", 25),
|
||||
Gift(0, "Thanks!", 5, enabled: false));
|
||||
|
||||
Assert.True(await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId));
|
||||
|
||||
var boxes = Assert.Single(await ClientGiftsAsync(db, Joiner));
|
||||
Assert.Equal("Welcome!", boxes.FriendlyName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ABoxThatGivesNothingIsNotAHandout()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
// The editor always keeps at least one card, and a blank one gives nothing at all.
|
||||
await SaveGiftsAsync(db, Gift(0, "Room Visit Gift", 0));
|
||||
|
||||
Assert.False(await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId));
|
||||
Assert.Empty(await ClientGiftsAsync(db, Joiner));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ARoomSavedBeforeTheListStillHandsOutItsOwnBox()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
var room = await db.Rooms.SingleAsync(x => x.RoomId == RoomId);
|
||||
room.JoinGiftEnabled = true;
|
||||
room.JoinGiftOncePerPlayer = true;
|
||||
room.JoinGiftTitle = "Room Visit Gift";
|
||||
room.JoinGiftCurrencyAmount = 25;
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
Assert.True(await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId));
|
||||
|
||||
var box = Assert.Single(await ClientGiftsAsync(db, Joiner));
|
||||
Assert.Equal(25, box.Currency);
|
||||
Assert.Empty(await db.RoomJoinGifts.ToListAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TheRoomsOwnFieldsDescribeTheFirstBox()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
Assert.IsType<OkObjectResult>(await SaveGiftsAsync(db,
|
||||
Gift(0, "Welcome!", 25, fromPlayerId: Sender.ToString()),
|
||||
Gift(0, "Thanks!", 5, enabled: false)));
|
||||
|
||||
var room = await db.Rooms.SingleAsync(x => x.RoomId == RoomId);
|
||||
Assert.Equal("Welcome!", room.JoinGiftTitle);
|
||||
Assert.Equal(25, room.JoinGiftCurrencyAmount);
|
||||
Assert.Equal(Sender, room.JoinGiftFromPlayerId);
|
||||
// The room is still handing boxes out even though only its first one is enabled.
|
||||
Assert.True(room.JoinGiftEnabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AListWithEveryBoxDisabledClosesTheRoom()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db,
|
||||
Gift(0, "Welcome!", 25, enabled: false),
|
||||
Gift(0, "Thanks!", 5, enabled: false));
|
||||
|
||||
var room = await db.Rooms.SingleAsync(x => x.RoomId == RoomId);
|
||||
Assert.False(room.JoinGiftEnabled);
|
||||
Assert.False(await RoomJoinGiftUtils.TryDeliverAsync(db, Joiner, RoomId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThePanelReadsTheWholeListBackWithSendersAsDigits()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db,
|
||||
Gift(0, "Welcome!", 25, fromPlayerId: Sender.ToString()),
|
||||
Gift(0, "Thanks!", 5));
|
||||
|
||||
var api = new AdminPanelAPI(db);
|
||||
var originalKey = DBConfig.AdminApiKey;
|
||||
DBConfig.AdminApiKey = Secret;
|
||||
string? firstSender;
|
||||
int count;
|
||||
try
|
||||
{
|
||||
var details = Assert.IsType<OkObjectResult>(await api.RoomPanelDetails(RoomId, Secret));
|
||||
using var doc = JsonDocument.Parse(JsonSerializer.Serialize(details.Value));
|
||||
var gifts = doc.RootElement.GetProperty("room").GetProperty("join_gifts");
|
||||
count = gifts.GetArrayLength();
|
||||
var sender = gifts[0].GetProperty("from_player_id");
|
||||
Assert.Equal(JsonValueKind.String, sender.ValueKind);
|
||||
firstSender = sender.GetString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
DBConfig.AdminApiKey = originalKey;
|
||||
}
|
||||
|
||||
Assert.Equal(2, count);
|
||||
Assert.Equal(Sender.ToString(), firstSender);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TheRoomsListThePanelLoadsCarriesEveryBox()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db, Gift(0, "Welcome!", 25), Gift(0, "Thanks!", 5));
|
||||
|
||||
// The panel's room table is what fills the editor, so the list has to travel with it - the
|
||||
// details route is not what opens the modal.
|
||||
var api = new AdminPanelAPI(db);
|
||||
var originalKey = DBConfig.AdminApiKey;
|
||||
DBConfig.AdminApiKey = Secret;
|
||||
JsonElement room;
|
||||
try
|
||||
{
|
||||
var result = Assert.IsType<OkObjectResult>(await api.List("rooms", Secret, null, null, null));
|
||||
using var doc = JsonDocument.Parse(JsonSerializer.Serialize(result.Value));
|
||||
room = doc.RootElement.EnumerateArray()
|
||||
.Single(x => x.GetProperty("id").GetInt64() == RoomId)
|
||||
.Clone();
|
||||
}
|
||||
finally
|
||||
{
|
||||
DBConfig.AdminApiKey = originalKey;
|
||||
}
|
||||
|
||||
var gifts = room.GetProperty("join_gifts");
|
||||
Assert.Equal(2, gifts.GetArrayLength());
|
||||
Assert.Equal("Welcome!", gifts[0].GetProperty("title").GetString());
|
||||
Assert.Equal(25, gifts[0].GetProperty("currency_amount").GetInt32());
|
||||
Assert.Equal("Thanks!", gifts[1].GetProperty("title").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ASenderThatIsNotAnAccountIdRefusesTheWholeList()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
|
||||
var result = await SaveGiftsAsync(db,
|
||||
Gift(0, "Welcome!", 25),
|
||||
Gift(0, "Thanks!", 5, fromPlayerId: "not-a-player"));
|
||||
|
||||
var bad = Assert.IsType<BadRequestObjectResult>(result);
|
||||
Assert.Contains("join_gifts[1].from_player_id", bad.Value?.ToString());
|
||||
// Nothing was written: a save the panel could not make sense of leaves no half-list behind.
|
||||
Assert.Empty(await db.RoomJoinGifts.ToListAsync());
|
||||
Assert.Equal(0, (await db.Rooms.SingleAsync(x => x.RoomId == RoomId)).JoinGiftCurrencyAmount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ABoxIdFromAnotherRoomIsRefused()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
db.RoomJoinGifts.Add(new RoomJoinGift
|
||||
{
|
||||
RoomId = 4242,
|
||||
SortOrder = 0,
|
||||
Title = "Someone else's",
|
||||
CurrencyAmount = 1
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
var foreignId = (await db.RoomJoinGifts.SingleAsync()).RoomJoinGiftId;
|
||||
|
||||
var result = await SaveGiftsAsync(db, Gift(foreignId, "Welcome!", 25));
|
||||
|
||||
Assert.IsType<BadRequestObjectResult>(result);
|
||||
Assert.Empty(await db.RoomJoinGifts.Where(x => x.RoomId == RoomId).ToListAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReorderingTheListReissuesNothing()
|
||||
{
|
||||
var db = await NewRoomAsync();
|
||||
await SaveGiftsAsync(db, Gift(0, "First", 1), Gift(0, "Second", 2));
|
||||
var ids = (await db.RoomJoinGifts.OrderBy(x => x.SortOrder).ToListAsync())
|
||||
.Select(x => x.RoomJoinGiftId).ToArray();
|
||||
|
||||
await SaveGiftsAsync(db, Gift(ids[1], "Second", 2), Gift(ids[0], "First", 1));
|
||||
|
||||
var stored = await db.RoomJoinGifts.OrderBy(x => x.SortOrder).ToListAsync();
|
||||
Assert.Equal(new[] { ids[1], ids[0] }, stored.Select(x => x.RoomJoinGiftId));
|
||||
Assert.All(stored, x => Assert.Equal(1, x.Version));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user