From 099c92e7f50f5d6b38cd1c175380264e7b16d848 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Wed, 7 Oct 2026 10:31:11 +0200 Subject: [PATCH 1/2] feat(auth): add ban_detail to ban info Add the player-facing ban_detail field to LootLockerBanInfo, which is shared by the Get Player Ban Status response and the player_banned error data returned when a banned player attempts to start a session. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Runtime/Game/Requests/LootLockerBanRequest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Runtime/Game/Requests/LootLockerBanRequest.cs b/Runtime/Game/Requests/LootLockerBanRequest.cs index ff98da223..0c47c97e0 100644 --- a/Runtime/Game/Requests/LootLockerBanRequest.cs +++ b/Runtime/Game/Requests/LootLockerBanRequest.cs @@ -12,6 +12,11 @@ public class LootLockerBanInfo /// public string ban_reason { get; set; } + /// + /// A player-facing message describing the ban. Empty when no detail was provided. + /// + public string ban_detail { get; set; } + /// /// The time the ban was issued, as an ISO 8601 timestamp. /// From d14b9815a981c58272cdfef5e302f0985b616865 Mon Sep 17 00:00:00 2001 From: Erik Bylund Date: Wed, 7 Oct 2026 11:44:58 +0200 Subject: [PATCH 2/2] test(auth): assert ban_detail round-trips through both ban paths Extend LootLockerTestPlayerBan.BanPlayer with an optional banDetail parameter and assert the value is returned by both GetPlayerBanStatus and the banned session-start error data. Without this, a misspelled JSON property would compile and pass every test while leaving the new field permanently empty. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../LootLockerTestConfigurationBan.cs | 17 +++++++++++++++-- Tests/LootLockerTests/PlayMode/BanTest.cs | 10 ++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Tests/LootLockerTestUtils/LootLockerTestConfigurationBan.cs b/Tests/LootLockerTestUtils/LootLockerTestConfigurationBan.cs index 2a07ee457..3d02d6c9a 100644 --- a/Tests/LootLockerTestUtils/LootLockerTestConfigurationBan.cs +++ b/Tests/LootLockerTestUtils/LootLockerTestConfigurationBan.cs @@ -5,12 +5,21 @@ namespace LootLockerTestConfigurationUtils { public static class LootLockerTestPlayerBan { + /// + /// Request body for the admin ban endpoint. + /// + private class BanPlayerRequest + { + public string ban_detail { get; set; } + } + /// /// Bans a player using the admin API. Creates a permanent manual ban. /// /// The ULID of the player to ban. /// Called with the raw response when the request completes. - public static void BanPlayer(string playerUlid, Action onComplete) + /// Optional player-facing ban message to store with the ban. + public static void BanPlayer(string playerUlid, Action onComplete, string banDetail = null) { if (string.IsNullOrEmpty(LootLockerConfig.current.adminToken)) { @@ -21,7 +30,11 @@ public static void BanPlayer(string playerUlid, Action onCom var endpoint = LootLockerTestConfigurationEndpoints.banPlayer; string formattedEndpoint = string.Format(endpoint.endPoint, playerUlid); - LootLockerAdminRequest.Send(formattedEndpoint, endpoint.httpMethod, "{}", serverResponse => + string json = string.IsNullOrEmpty(banDetail) + ? "{}" + : LootLockerJson.SerializeObject(new BanPlayerRequest { ban_detail = banDetail }); + + LootLockerAdminRequest.Send(formattedEndpoint, endpoint.httpMethod, json, serverResponse => { onComplete?.Invoke(serverResponse); }, true); diff --git a/Tests/LootLockerTests/PlayMode/BanTest.cs b/Tests/LootLockerTests/PlayMode/BanTest.cs index 51607ac95..08ebc0a97 100644 --- a/Tests/LootLockerTests/PlayMode/BanTest.cs +++ b/Tests/LootLockerTests/PlayMode/BanTest.cs @@ -131,13 +131,15 @@ public IEnumerator StartSession_ForBannedPlayer_Returns403WithBanInfo() string playerUlid = sessionResponse.player_ulid; LootLockerStateData.ClearAllSavedStates(); + const string banDetail = "Banned by the integration test"; + bool banCompleted = false; LootLockerResponse banResponse = null; LootLockerTestPlayerBan.BanPlayer(playerUlid, response => { banResponse = response; banCompleted = true; - }); + }, banDetail); yield return new WaitUntil(() => banCompleted); Assert.IsTrue(banResponse?.success, "Failed to ban player via admin API"); @@ -157,6 +159,7 @@ public IEnumerator StartSession_ForBannedPlayer_Returns403WithBanInfo() Assert.AreEqual("player_banned", bannedSessionResponse.errorData?.code, "Expected player_banned error code"); Assert.IsNotNull(bannedSessionResponse.errorData?.ban, "Expected ban info to be present in the error data"); Assert.IsFalse(string.IsNullOrEmpty(bannedSessionResponse.errorData?.ban?.ban_reason), "Expected ban_reason to be populated"); + Assert.AreEqual(banDetail, bannedSessionResponse.errorData?.ban?.ban_detail, "Expected ban_detail to round-trip through the session start error data"); } [UnityTest, Category("LootLocker"), Category("LootLockerCI")] @@ -178,13 +181,15 @@ public IEnumerator GetPlayerBanStatus_ForBannedPlayer_ReturnsIsBannedWithDetails string playerUlid = sessionResponse.player_ulid; LootLockerStateData.ClearAllSavedStates(); + const string banDetail = "Banned by the integration test"; + bool banCompleted = false; LootLockerResponse banResponse = null; LootLockerTestPlayerBan.BanPlayer(playerUlid, response => { banResponse = response; banCompleted = true; - }); + }, banDetail); yield return new WaitUntil(() => banCompleted); Assert.IsTrue(banResponse?.success, "Failed to ban player via admin API"); @@ -203,6 +208,7 @@ public IEnumerator GetPlayerBanStatus_ForBannedPlayer_ReturnsIsBannedWithDetails Assert.IsTrue(banStatusResponse.is_banned, "Expected player to be banned"); Assert.IsNotNull(banStatusResponse.ban, "Expected ban details to be populated"); Assert.IsFalse(string.IsNullOrEmpty(banStatusResponse.ban?.ban_reason), "Expected ban_reason to be populated"); + Assert.AreEqual(banDetail, banStatusResponse.ban?.ban_detail, "Expected ban_detail to round-trip through the ban status response"); Assert.IsFalse(string.IsNullOrEmpty(banStatusResponse.ban?.banned_on), "Expected banned_on to be populated"); Assert.IsTrue(banStatusResponse.ban?.permanent ?? false, "Expected permanent ban (no banned_until)"); }