mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 15:39:01 -07:00
247 lines
9.3 KiB
C#
247 lines
9.3 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API.Matchmaking;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Accounts;
|
|
using RadicadeServer.RecNet.Accounts.Local;
|
|
using RadicadeServer.RecNet.Enums;
|
|
using RadicadeServer.RecNet.Enums.Rooms;
|
|
using RadicadeServer.RecNet.Responses;
|
|
using RadicadeServer.RecNet.Rooms;
|
|
using RadicadeServer.Utils;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
/// <summary>
|
|
/// The account-creation flow ends on the launch-game step, and the client asks for its dorm by
|
|
/// route (<c>matchmake/dorm</c>) rather than by room name. That route resolves the dorm directly,
|
|
/// so it never ran through the Orientation redirect that lives on the room-name path in
|
|
/// <see cref="MatchmakingUtils.Matchmake"/> - a brand-new player was dropped in their dorm with
|
|
/// the Orientation marker still unspent. Orientation is a solo room (MaxPlayers 1), so the
|
|
/// hand-off mints one instance per player, the same shape the dorm uses.
|
|
///
|
|
/// These pin both dorm entry points, that the hand-off happens exactly once, and that a server
|
|
/// without an Orientation room still lands its players somewhere.
|
|
/// </summary>
|
|
[Collection(DbFactoryCollection.Name)]
|
|
public class OrientationHandoffTests
|
|
{
|
|
private const long PlayerId = 812;
|
|
private const long OrientationRoomId = 13;
|
|
private const long OrientationSubRoomId = 113;
|
|
private const long DormRoomId = 8121;
|
|
private const long DormSubRoomId = 8122;
|
|
|
|
private sealed class TestDbContextFactory : IDbContextFactory<RecDBContext>
|
|
{
|
|
private readonly DbContextOptions<RecDBContext> options = TestDbOptions.Create()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
public RecDBContext CreateDbContext() => new(options);
|
|
}
|
|
|
|
private static async Task<(TestDbContextFactory Factory, RecDBContext Db)> CreateContextAsync(
|
|
bool seedOrientationRoom = true)
|
|
{
|
|
var factory = new TestDbContextFactory();
|
|
RoomUtils.DbFactory = factory;
|
|
MatchmakingUtils.DbFactory = factory;
|
|
|
|
var db = factory.CreateDbContext();
|
|
|
|
if (seedOrientationRoom)
|
|
{
|
|
db.Rooms.Add(new RoomData
|
|
{
|
|
RoomId = OrientationRoomId,
|
|
Name = "Orientation",
|
|
Description = "An introductory tour of Rec Room!",
|
|
Accessibility = Accessibility.Public,
|
|
MaxPlayers = 1,
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
db.SubRooms.Add(new SubRoom
|
|
{
|
|
SubRoomId = OrientationSubRoomId,
|
|
RoomId = OrientationRoomId,
|
|
Name = "Home",
|
|
UnitySceneId = "c79709d8-a31b-48aa-9eb8-cc31ba9505e8",
|
|
MaxPlayers = 1,
|
|
Accessibility = Accessibility.Public
|
|
});
|
|
}
|
|
|
|
// The player's own dorm, which GetOrCreateDorm recognises by owner rather than by name.
|
|
db.Rooms.Add(new RoomData
|
|
{
|
|
RoomId = DormRoomId,
|
|
Name = "DormRoom",
|
|
IsDorm = true,
|
|
CreatorAccountId = PlayerId,
|
|
Accessibility = Accessibility.Private,
|
|
CreatedAt = DateTime.UtcNow
|
|
});
|
|
db.SubRooms.Add(new SubRoom
|
|
{
|
|
SubRoomId = DormSubRoomId,
|
|
RoomId = DormRoomId,
|
|
Name = "Home",
|
|
UnitySceneId = "cbad71af-0831-44d8-b8ef-69edafa841f6",
|
|
MaxPlayers = 4,
|
|
Accessibility = Accessibility.Private
|
|
});
|
|
|
|
await db.SaveChangesAsync();
|
|
return (factory, db);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An account the account-creation flow just created, with the settings that flow writes:
|
|
/// the Orientation marker, and no <c>HAS_COMPLETED_ORIENTATION</c>.
|
|
/// </summary>
|
|
private static async Task SeedOnboardingPlayerAsync(
|
|
RecDBContext db,
|
|
long playerId = PlayerId,
|
|
bool needsOrientation = true)
|
|
{
|
|
var settings = new List<Setting>
|
|
{
|
|
new() { Key = "Recroom.AccountCreation.HasStarted", Value = "True" },
|
|
new() { Key = "Recroom.AccountCreation.HasFinished", Value = "False" },
|
|
};
|
|
|
|
if (needsOrientation)
|
|
settings.Add(new Setting { Key = "Radicade.NeedsOrientation", Value = "1" });
|
|
|
|
db.PlayerSettings.Add(new PlayerSettings
|
|
{
|
|
AccountId = playerId,
|
|
Settings = settings.ToJson()
|
|
});
|
|
db.Presences.Add(new RealPresence { playerId = playerId });
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
private static MatchmakingAPI CreateMatchmakingApi(RecDBContext db, long accountId)
|
|
{
|
|
var identity = new ClaimsIdentity("TestAuth");
|
|
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, accountId.ToString()));
|
|
|
|
var api = new MatchmakingAPI(db)
|
|
{
|
|
ControllerContext = new ControllerContext
|
|
{
|
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(identity) }
|
|
}
|
|
};
|
|
|
|
return api;
|
|
}
|
|
|
|
private static async Task<MatchResponse> MatchmakeDormAsync(MatchmakingAPI api)
|
|
{
|
|
var result = Assert.IsType<OkObjectResult>(await api.MatchmakeDorm());
|
|
return Assert.IsType<MatchResponse>(result.Value);
|
|
}
|
|
|
|
private static async Task<bool> StillOwesOrientationAsync(TestDbContextFactory factory)
|
|
{
|
|
await using var verify = factory.CreateDbContext();
|
|
var settings = await verify.PlayerSettings.SingleAsync(x => x.AccountId == PlayerId);
|
|
return settings.DeserializeSettings()
|
|
.Any(setting => setting.Key == "Radicade.NeedsOrientation");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TheLaunchStepTheOnboardingFlowEndsOnOpensOrientation()
|
|
{
|
|
var (factory, db) = await CreateContextAsync();
|
|
await using var _db = db;
|
|
await SeedOnboardingPlayerAsync(db);
|
|
|
|
var match = await MatchmakeDormAsync(CreateMatchmakingApi(db, PlayerId));
|
|
|
|
Assert.Equal(JoinStatus.Success, match.errorCode);
|
|
Assert.NotNull(match.roomInstance);
|
|
Assert.Equal(OrientationRoomId, match.roomInstance!.roomId);
|
|
|
|
await using var verify = factory.CreateDbContext();
|
|
var presence = await verify.Presences.SingleAsync(x => x.playerId == PlayerId);
|
|
Assert.Equal(match.roomInstance.roomInstanceId, presence.roomInstance);
|
|
Assert.False(await StillOwesOrientationAsync(factory));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TheRoomNameDormPathOpensOrientationToo()
|
|
{
|
|
var (factory, db) = await CreateContextAsync();
|
|
await using var _db = db;
|
|
await SeedOnboardingPlayerAsync(db);
|
|
|
|
var match = await MatchmakingUtils.Matchmake(JoinMode.PublicMatchmaking, "DormRoom", PlayerId);
|
|
|
|
Assert.Equal(JoinStatus.Success, match.errorCode);
|
|
Assert.NotNull(match.roomInstance);
|
|
Assert.Equal(OrientationRoomId, match.roomInstance!.roomId);
|
|
Assert.False(await StillOwesOrientationAsync(factory));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OrientationIsHandedOutOnceAndThenTheDormIsWhereTheyLand()
|
|
{
|
|
var (factory, db) = await CreateContextAsync();
|
|
await using var _db = db;
|
|
await SeedOnboardingPlayerAsync(db);
|
|
|
|
var first = await MatchmakeDormAsync(CreateMatchmakingApi(db, PlayerId));
|
|
Assert.Equal(OrientationRoomId, first.roomInstance!.roomId);
|
|
|
|
// The marker was spent, so the second launch is an ordinary dorm launch: the player is not
|
|
// sent back to Orientation every time they go home.
|
|
var second = await MatchmakeDormAsync(CreateMatchmakingApi(db, PlayerId));
|
|
await using var verify = factory.CreateDbContext();
|
|
var room = await verify.Rooms.AsNoTracking().SingleAsync(x => x.RoomId == second.roomInstance!.roomId);
|
|
Assert.True(room.IsDorm);
|
|
Assert.Equal(DormRoomId, room.RoomId);
|
|
Assert.NotEqual(first.roomInstance.roomInstanceId, second.roomInstance!.roomInstanceId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task APlayerWhoOwesNoOrientationGoesStraightToTheirDorm()
|
|
{
|
|
var (factory, db) = await CreateContextAsync();
|
|
await using var _db = db;
|
|
await SeedOnboardingPlayerAsync(db, needsOrientation: false);
|
|
|
|
var match = await MatchmakeDormAsync(CreateMatchmakingApi(db, PlayerId));
|
|
|
|
await using var verify = factory.CreateDbContext();
|
|
var room = await verify.Rooms.AsNoTracking().SingleAsync(x => x.RoomId == match.roomInstance!.roomId);
|
|
Assert.Equal(DormRoomId, room.RoomId);
|
|
Assert.True(room.IsDorm);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AServerWithNoOrientationRoomStillLandsThePlayerInTheirDorm()
|
|
{
|
|
var (factory, db) = await CreateContextAsync(seedOrientationRoom: false);
|
|
await using var _db = db;
|
|
await SeedOnboardingPlayerAsync(db);
|
|
|
|
var match = await MatchmakeDormAsync(CreateMatchmakingApi(db, PlayerId));
|
|
|
|
await using var verify = factory.CreateDbContext();
|
|
var room = await verify.Rooms.AsNoTracking().SingleAsync(x => x.RoomId == match.roomInstance!.roomId);
|
|
Assert.Equal(DormRoomId, room.RoomId);
|
|
|
|
// Nothing was handed out, so the marker stays: the player still gets Orientation if the room
|
|
// is added later.
|
|
Assert.True(await StillOwesOrientationAsync(factory));
|
|
}
|
|
}
|