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. /// 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)"); }