mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 15:39:01 -07:00
235 lines
11 KiB
C#
235 lines
11 KiB
C#
using System.Security.Claims;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer;
|
|
using RadicadeServer.API.Config;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet.Accounts.Local;
|
|
using RadicadeServer.RecNet.Auth;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
public class AccountOnboardingSettingsTests
|
|
{
|
|
// Every key the 2023 title screen reads to decide which page of its own new-user flow to open.
|
|
// They all have to be written, not just the ones this server checks itself: a key the client looks
|
|
// for and cannot find makes it resume at a page it believes it already showed, and a true one skips
|
|
// that page outright. This is the whole reason a brand-new account opens on "Welcome to Rec Room!".
|
|
private static readonly string[] AccountCreationKeys =
|
|
[
|
|
"Recroom.AccountCreation.HasStarted",
|
|
"Recroom.AccountCreation.HasChosenUsername",
|
|
"Recroom.AccountCreation.HasCreatedPassword",
|
|
"Recroom.AccountCreation.HasFinished",
|
|
"Recroom.AccountCreation.FinishedAvatarCustomization",
|
|
"Recroom.AccountCreation.AcceptedCodeOfConduct",
|
|
"Recroom.AccountCreation.HasImportedFriends"
|
|
];
|
|
|
|
[Fact]
|
|
public void NewAccountSettings_LeaveOnboardingIncompleteAndRequireOrientation()
|
|
{
|
|
var settings = JsonSerializer.Deserialize<List<Setting>>(DBConfig.NewAccountSettings2023)!;
|
|
string Value(string key) => settings.Single(setting => setting.Key == key).Value;
|
|
|
|
foreach (var key in AccountCreationKeys)
|
|
Assert.Equal("False", Value(key));
|
|
|
|
Assert.Equal("0", Value("Recroom.OOBE"));
|
|
Assert.Equal("1", Value("Radicade.NeedsOrientation"));
|
|
Assert.DoesNotContain(settings, setting => setting.Key == "HAS_COMPLETED_ORIENTATION");
|
|
}
|
|
|
|
[Fact]
|
|
public void AccountCreationInProgressSettings_ResumeAtThePageAfterWhatThePlayerSubmitted()
|
|
{
|
|
var settings = JsonSerializer.Deserialize<List<Setting>>(
|
|
DBConfig.AccountCreationInProgressSettings2023(usernameChosen: true, passwordCreated: true))!;
|
|
string Value(string key) => settings.Single(setting => setting.Key == key).Value;
|
|
|
|
// An account the flow itself created: it started, the name and password are in, and the pages
|
|
// the player has not reached yet (avatar, Code of Conduct, finished) are still open.
|
|
Assert.Equal("True", Value("Recroom.AccountCreation.HasStarted"));
|
|
Assert.Equal("True", Value("Recroom.AccountCreation.HasChosenUsername"));
|
|
Assert.Equal("True", Value("Recroom.AccountCreation.HasCreatedPassword"));
|
|
Assert.Equal("False", Value("Recroom.AccountCreation.HasFinished"));
|
|
Assert.Equal("False", Value("Recroom.AccountCreation.FinishedAvatarCustomization"));
|
|
Assert.Equal("False", Value("Recroom.AccountCreation.AcceptedCodeOfConduct"));
|
|
Assert.Equal("1", Value("Radicade.NeedsOrientation"));
|
|
|
|
// A create that carried neither: the client still has to ask for both.
|
|
var bare = JsonSerializer.Deserialize<List<Setting>>(
|
|
DBConfig.AccountCreationInProgressSettings2023(usernameChosen: false, passwordCreated: false))!;
|
|
Assert.Equal("False", bare.Single(setting => setting.Key == "Recroom.AccountCreation.HasChosenUsername").Value);
|
|
Assert.Equal("False", bare.Single(setting => setting.Key == "Recroom.AccountCreation.HasCreatedPassword").Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void Regular2023DefaultSettings_DoNotForceNewAccountOnboarding()
|
|
{
|
|
var settings = JsonSerializer.Deserialize<List<Setting>>(DBConfig.DefaultSettings2023)!;
|
|
|
|
Assert.DoesNotContain(settings, setting => setting.Key.StartsWith("Recroom.AccountCreation.", StringComparison.Ordinal));
|
|
Assert.DoesNotContain(settings, setting => setting.Key == "Radicade.NeedsOrientation");
|
|
Assert.Contains(settings, setting => setting.Key == "HAS_COMPLETED_ORIENTATION" && setting.Value == "True");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CannotMarkOnboardingFinishedUntilPasswordBirthdayAndCocAreSaved()
|
|
{
|
|
await using var db = CreateDb();
|
|
db.Profiles.Add(new Profile { accountId = 2, username = "NewUser", displayName = "NewUser", profileImage = "default" });
|
|
db.Players.Add(new PlayerData { AccountId = 2, Hashword = "hashed", Birthday = new DateTime(2000, 1, 1) });
|
|
db.PlayerSettings.Add(new PlayerSettings
|
|
{
|
|
AccountId = 2,
|
|
Settings = "[{\"Key\":\"Recroom.AccountCreation.HasChosenUsername\",\"Value\":\"True\"}]"
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var api = CreateApi(db);
|
|
|
|
await api.SetSetting(new Setting { Key = "Recroom.AccountCreation.HasFinished", Value = "True" });
|
|
|
|
var saved = (await db.PlayerSettings.SingleAsync()).DeserializeSettings();
|
|
Assert.DoesNotContain(saved, setting => setting.Key == "Recroom.AccountCreation.HasFinished");
|
|
Assert.DoesNotContain(saved, setting => setting.Key == "Radicade.NeedsOrientation");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CompletingOnboardingSetsOrientationMarkerOnlyAfterRequiredFieldsArePresent()
|
|
{
|
|
await using var db = CreateDb();
|
|
db.Profiles.Add(new Profile { accountId = 2, username = "NewUser", displayName = "NewUser", profileImage = "default" });
|
|
db.Players.Add(new PlayerData
|
|
{
|
|
AccountId = 2,
|
|
Hashword = "hashed",
|
|
Birthday = new DateTime(2000, 1, 1),
|
|
CodeOfConductAccepted = true
|
|
});
|
|
db.PlayerSettings.Add(new PlayerSettings
|
|
{
|
|
AccountId = 2,
|
|
Settings = "[{\"Key\":\"Recroom.AccountCreation.HasChosenUsername\",\"Value\":\"True\"}]"
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var api = CreateApi(db);
|
|
|
|
await api.SetSetting(new Setting { Key = "Recroom.AccountCreation.HasFinished", Value = "True" });
|
|
|
|
var saved = (await db.PlayerSettings.SingleAsync()).DeserializeSettings();
|
|
Assert.Contains(saved, setting => setting.Key == "Recroom.AccountCreation.HasFinished" && setting.Value == "True");
|
|
Assert.Contains(saved, setting => setting.Key == "Radicade.NeedsOrientation" && setting.Value == "1");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The flow records the acceptance as a setting on this build's own Code of Conduct page; the
|
|
/// account's <c>account/me/acceptcoc</c> route is never called. Before this, an account whose
|
|
/// player had walked that page still answered <c>hasAcceptedCodeOfConduct = false</c> - so the
|
|
/// client offered the page again on the next boot, and the finish marker was refused over a flag
|
|
/// the client's own pages never write.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task TheConductPageRecordsTheAcceptanceOnTheAccountAndUnblocksTheFinishMarker()
|
|
{
|
|
await using var db = CreateDb();
|
|
db.Profiles.Add(new Profile { accountId = 2, username = "NewUser", displayName = "NewUser", profileImage = "default" });
|
|
db.Players.Add(new PlayerData { AccountId = 2, Hashword = "hashed", Birthday = new DateTime(2000, 1, 1) });
|
|
db.PlayerSettings.Add(new PlayerSettings
|
|
{
|
|
AccountId = 2,
|
|
Settings = "[{\"Key\":\"Recroom.AccountCreation.HasChosenUsername\",\"Value\":\"True\"}]"
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var api = CreateApi(db);
|
|
|
|
await api.SetSetting(new Setting { Key = "Recroom.AccountCreation.AcceptedCodeOfConduct", Value = "True" });
|
|
|
|
Assert.True((await db.Players.AsNoTracking().SingleAsync()).CodeOfConductAccepted);
|
|
|
|
// And that is now enough for the flow to be able to finish.
|
|
await api.SetSetting(new Setting { Key = "Recroom.AccountCreation.HasFinished", Value = "True" });
|
|
|
|
var saved = (await db.PlayerSettings.SingleAsync()).DeserializeSettings();
|
|
Assert.Contains(saved, setting => setting.Key == "Recroom.AccountCreation.HasFinished" && setting.Value == "True");
|
|
Assert.Contains(saved, setting => setting.Key == "Radicade.NeedsOrientation" && setting.Value == "1");
|
|
}
|
|
|
|
/// <summary>
|
|
/// A birthday that never arrived must not hold the finish marker hostage. Every account the flow
|
|
/// has created on this server still reads 0001-01-01 - its age page has never handed this server
|
|
/// one - so requiring it meant no player could ever leave the flow, and the title screen kept
|
|
/// opening on "Welcome back! - Continue creating account" for an account whose every other page
|
|
/// was done.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task TheFinishMarkerIsRecordedForAnAccountWhoseAgePageNeverArrived()
|
|
{
|
|
await using var db = CreateDb();
|
|
db.Profiles.Add(new Profile { accountId = 2, username = "NewUser", displayName = "NewUser", profileImage = "default" });
|
|
db.Players.Add(new PlayerData { AccountId = 2, Hashword = "hashed", Birthday = null });
|
|
db.PlayerSettings.Add(new PlayerSettings
|
|
{
|
|
AccountId = 2,
|
|
Settings = "[{\"Key\":\"Recroom.AccountCreation.HasChosenUsername\",\"Value\":\"True\"}]"
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var api = CreateApi(db);
|
|
|
|
await api.SetSetting(new Setting { Key = "Recroom.AccountCreation.AcceptedCodeOfConduct", Value = "True" });
|
|
await api.SetSetting(new Setting { Key = "Recroom.AccountCreation.HasFinished", Value = "True" });
|
|
|
|
var saved = (await db.PlayerSettings.SingleAsync()).DeserializeSettings();
|
|
Assert.Contains(saved, setting => setting.Key == "Recroom.AccountCreation.HasFinished" && setting.Value == "True");
|
|
|
|
// Still owed, so the account keeps asking for it until the age page hands one over.
|
|
Assert.Null((await db.Players.AsNoTracking().SingleAsync()).Birthday);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData("hashed-password")]
|
|
public async Task SettingsDoNotSkipAnUnfinishedPasswordPage(string passwordHash)
|
|
{
|
|
await using var db = CreateDb();
|
|
db.Players.Add(new PlayerData { AccountId = 2, Hashword = passwordHash });
|
|
db.PlayerSettings.Add(new PlayerSettings
|
|
{
|
|
AccountId = 2,
|
|
Settings = "[{\"Key\":\"Recroom.AccountCreation.HasCreatedPassword\",\"Value\":\"True\"}," +
|
|
"{\"Key\":\"Recroom.AccountCreation.HasFinished\",\"Value\":\"True\"}," +
|
|
"{\"Key\":\"Recroom.OOBE\",\"Value\":\"100\"}]"
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var settings = CreateApi(db).GetSettings();
|
|
var expected = passwordHash.Length == 0 ? "False" : "True";
|
|
Assert.Equal(expected, settings.Single(x => x.Key == "Recroom.AccountCreation.HasCreatedPassword").Value);
|
|
Assert.Equal(expected, settings.Single(x => x.Key == "Recroom.AccountCreation.HasFinished").Value);
|
|
Assert.Equal(passwordHash.Length == 0 ? "0" : "100", settings.Single(x => x.Key == "Recroom.OOBE").Value);
|
|
}
|
|
|
|
private static RecDBContext CreateDb()
|
|
{
|
|
var options = TestDbOptions.Create()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
return new RecDBContext(options);
|
|
}
|
|
|
|
private static PlayerSettingsAPI CreateApi(RecDBContext db)
|
|
{
|
|
var context = new DefaultHttpContext
|
|
{
|
|
User = new ClaimsPrincipal(new ClaimsIdentity(
|
|
[new Claim(ClaimTypes.NameIdentifier, "2")], "test"))
|
|
};
|
|
return new PlayerSettingsAPI(db)
|
|
{
|
|
ControllerContext = new ControllerContext { HttpContext = context }
|
|
};
|
|
}
|
|
}
|