mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
270 lines
10 KiB
C#
270 lines
10 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API.Moderation;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet.Accounts;
|
|
using RadicadeServer.RecNet.Auth;
|
|
using RadicadeServer.RecNet.Enums.Moderation;
|
|
using RadicadeServer.RecNet.Moderation;
|
|
using RadicadeServer.Utils;
|
|
using Xunit;
|
|
|
|
namespace RadicadeServer.Tests;
|
|
|
|
[Collection(DbFactoryCollection.Name)]
|
|
public class ModerationBlockDetailsTests
|
|
{
|
|
private static RecDBContext NewDb() => new(
|
|
TestDbOptions.Create()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options);
|
|
|
|
private static PlayerReportingAPI NewApi(RecDBContext db, long? accountId)
|
|
{
|
|
var identity = new ClaimsIdentity("TestAuth");
|
|
if (accountId.HasValue)
|
|
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, accountId.Value.ToString()));
|
|
return new PlayerReportingAPI(
|
|
db)
|
|
{
|
|
ControllerContext = new ControllerContext
|
|
{
|
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(identity) }
|
|
}
|
|
};
|
|
}
|
|
|
|
private static void AssertReportSucceeded(ActionResult<OkResponse> result)
|
|
{
|
|
var ok = Assert.IsType<OkObjectResult>(result.Result);
|
|
Assert.True(Assert.IsType<OkResponse>(ok.Value).Success);
|
|
}
|
|
|
|
private static async Task AddReporterAsync(RecDBContext db, long accountId, string? roles = null)
|
|
{
|
|
db.Profiles.Add(new Profile
|
|
{
|
|
accountId = accountId,
|
|
username = $"reporter{accountId}",
|
|
displayName = $"Reporter {accountId}",
|
|
profileImage = DBConfig.DefaultProfileImage,
|
|
role = roles
|
|
});
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0, false)]
|
|
[InlineData(5, false)]
|
|
[InlineData(6, true)]
|
|
[InlineData(7, true)]
|
|
public void AutomaticTimeoutRequiresMoreThanFiveReportsFromAPlayer(int reportCount, bool expected)
|
|
{
|
|
Assert.Equal(expected, PlayerReportingAPI.ShouldApplyAutomaticTimeout(reportCount, reporterIsModerator: false));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TheSixthPublicReportCreatesAnAutomaticTimeout()
|
|
{
|
|
using var db = NewDb();
|
|
await AddReporterAsync(db, 6);
|
|
db.Reputations.Add(new Reputation { AccountId = 42, ReportCount = 5 });
|
|
await db.SaveChangesAsync();
|
|
|
|
AssertReportSucceeded(await NewApi(db, 6).CreateReport(new CreateReportRequest
|
|
{
|
|
PlayerIdReported = 42,
|
|
ReportCategory = ReportCategory.CoC_Trolling,
|
|
Details = "report details",
|
|
RoomId = 0
|
|
}));
|
|
|
|
var moderation = await db.Moderation.SingleAsync();
|
|
Assert.Equal("Automatic Timeout", moderation.BanMethod);
|
|
Assert.Equal("You have received a timeout due to a high number of reports for Trolling.", moderation.Message);
|
|
Assert.True(moderation.CreatedAt <= DateTime.UtcNow);
|
|
Assert.Equal(6, (await db.Reputations.SingleAsync(x => x.AccountId == 42)).ReportCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SelfAndUnauthenticatedReportsDoNotCountTowardAutomaticTimeouts()
|
|
{
|
|
using var db = NewDb();
|
|
db.Reputations.Add(new Reputation { AccountId = 42, ReportCount = 5 });
|
|
await db.SaveChangesAsync();
|
|
|
|
AssertReportSucceeded(await NewApi(db, 42).CreateReport(new CreateReportRequest
|
|
{
|
|
PlayerIdReported = 42,
|
|
ReportCategory = ReportCategory.CoC_Trolling
|
|
}));
|
|
Assert.Empty(await db.Moderation.ToListAsync());
|
|
Assert.Equal(5, (await db.Reputations.SingleAsync(x => x.AccountId == 42)).ReportCount);
|
|
|
|
AssertReportSucceeded(await NewApi(db, null).CreateReport(new CreateReportRequest
|
|
{
|
|
PlayerIdReported = 42,
|
|
ReportCategory = ReportCategory.CoC_Trolling
|
|
}));
|
|
Assert.Empty(await db.Moderation.ToListAsync());
|
|
Assert.Equal(5, (await db.Reputations.SingleAsync(x => x.AccountId == 42)).ReportCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ModeratorReportCreatesDirectTimeoutWithoutAlsoUsingPublicReportEscalation()
|
|
{
|
|
using var db = NewDb();
|
|
await AddReporterAsync(db, 6, "moderator");
|
|
db.Reputations.Add(new Reputation { AccountId = 42, ReportCount = 0 });
|
|
await db.SaveChangesAsync();
|
|
|
|
AssertReportSucceeded(await NewApi(db, 6).CreateReport(new CreateReportRequest
|
|
{
|
|
PlayerIdReported = 42,
|
|
ReportCategory = ReportCategory.CoC_Trolling,
|
|
Details = "Moderator observed trolling"
|
|
}));
|
|
|
|
var moderation = await db.Moderation.SingleAsync();
|
|
Assert.Equal("Moderator Report", moderation.BanMethod);
|
|
Assert.Equal("Moderator observed trolling", moderation.Message);
|
|
Assert.Equal(0, (await db.Reputations.SingleAsync(x => x.AccountId == 42)).ReportCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InvalidReportCategoryIsRejectedBeforeItCounts()
|
|
{
|
|
using var db = NewDb();
|
|
await AddReporterAsync(db, 6);
|
|
|
|
var result = await NewApi(db, 6).CreateReport(new CreateReportRequest
|
|
{
|
|
PlayerIdReported = 42,
|
|
ReportCategory = (ReportCategory)9999
|
|
});
|
|
|
|
Assert.IsType<BadRequestObjectResult>(result.Result);
|
|
Assert.Empty(await db.Reputations.ToListAsync());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(ReportCategory.CoC_Trolling, "Trolling")]
|
|
[InlineData(ReportCategory.CoC_Sexual, "Sexual behavior")]
|
|
public void AutomaticTimeoutReasonUsesTheReportCategory(ReportCategory category, string expected)
|
|
{
|
|
Assert.Equal(expected, PlayerReportingAPI.GetAutomaticTimeoutReason(category));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task VoiceModerationBlockDetailsCarryStartTimeButNotModeratorOnlyNote()
|
|
{
|
|
using var db = NewDb();
|
|
var startedAt = DateTime.UtcNow;
|
|
db.Moderation.Add(new ModerationDB
|
|
{
|
|
PlayerId = 42,
|
|
IsBan = true,
|
|
Message = "Sexually explicit behavior or photos",
|
|
ModeratorNote = "Sexual harassment in the Rec Center",
|
|
BanMethod = "Voice Moderation",
|
|
Category = ReportCategory.CoC_Sexual,
|
|
CreatedAt = startedAt,
|
|
ExpireDuration = startedAt.AddDays(1),
|
|
AppealCode = "ABC12345"
|
|
});
|
|
await db.SaveChangesAsync();
|
|
|
|
var api = NewApi(db, 42);
|
|
api.ControllerContext.HttpContext.Request.Headers["X-Forwarded-For"] = "192.0.2.42";
|
|
var result = Assert.IsType<OkObjectResult>(await api.GetBlockDetails());
|
|
var details = Assert.IsType<BlockDetails>(result.Value);
|
|
|
|
Assert.Equal(startedAt, details.TimeoutStartedAt);
|
|
Assert.Equal("VoiceModeration", details.BanType);
|
|
Assert.Equal("Sexually explicit behavior or photos", details.Message);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Automatic Timeout", "You have received a timeout due to a high number of reports for Trolling", "You have received a timeout due to a high number of reports for Trolling.")]
|
|
[InlineData("Moderator Report", "You were reported by a moderator", "You have received a timeout. You were reported by a moderator.")]
|
|
[InlineData("Moderator Report", "You were reported by a moderator.", "You have received a timeout. a moderator report.")]
|
|
[InlineData("Timeout", "Trolling", "You have received a timeout due to a high number of reports for Trolling.")]
|
|
[InlineData("Automatic Timeout", "You have received a timeout due to a high number of reports for Trolling.", "You have received a timeout due to a high number of reports for Trolling.")]
|
|
public async Task PersistedTimeoutBanHasClientFacingTimeoutCopy(string banMethod, string storedReason, string expectedMessage)
|
|
{
|
|
using var db = NewDb();
|
|
var startedAt = DateTime.UtcNow;
|
|
db.Moderation.Add(new ModerationDB
|
|
{
|
|
PlayerId = 42,
|
|
IsBan = true,
|
|
Message = storedReason,
|
|
BanMethod = banMethod,
|
|
Category = ReportCategory.CoC_Trolling,
|
|
CreatedAt = startedAt,
|
|
ExpireDuration = startedAt.AddHours(1),
|
|
AppealCode = "ABC12345"
|
|
});
|
|
await db.SaveChangesAsync();
|
|
|
|
var api = NewApi(db, 42);
|
|
api.ControllerContext.HttpContext.Request.Headers["X-Forwarded-For"] = "192.0.2.42";
|
|
var result = Assert.IsType<OkObjectResult>(await api.GetBlockDetails());
|
|
var details = Assert.IsType<BlockDetails>(result.Value);
|
|
|
|
Assert.Equal("Timeout", details.BanType);
|
|
Assert.Equal(startedAt, details.TimeoutStartedAt);
|
|
Assert.Equal(expectedMessage, details.DisplayReason);
|
|
Assert.Equal(expectedMessage, details.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HardwareBanShowsTheOriginalRelatedAccountOnItsOwnLine()
|
|
{
|
|
using var db = NewDb();
|
|
var startedAt = DateTime.UtcNow;
|
|
db.Players.Add(new PlayerData { AccountId = 42, DeviceId = "shared-device" });
|
|
db.Profiles.Add(new Profile
|
|
{
|
|
accountId = 12,
|
|
username = "HealthyMoose640",
|
|
displayName = "HealthyMoose640",
|
|
profileImage = DBConfig.DefaultProfileImage
|
|
});
|
|
db.HardwareBans.Add(new HardwareBan
|
|
{
|
|
DeviceId = "shared-device",
|
|
BannedAccountId = 12,
|
|
Reason = "Moderator. Account ban",
|
|
CreatedAt = startedAt,
|
|
ExpiresAt = startedAt.AddDays(1)
|
|
});
|
|
await db.SaveChangesAsync();
|
|
|
|
var api = NewApi(db, 42);
|
|
api.ControllerContext.HttpContext.Request.Headers["X-Forwarded-For"] = "192.0.2.42";
|
|
var result = Assert.IsType<OkObjectResult>(await api.GetBlockDetails());
|
|
var details = Assert.IsType<BlockDetails>(result.Value);
|
|
|
|
Assert.Contains("\nRelated account: @HealthyMoose640", details.Message);
|
|
Assert.Equal(startedAt, details.TimeoutStartedAt);
|
|
}
|
|
|
|
/* Every ban the client is shown has to carry the moment it started, because
|
|
that is the "Banned since" line. Kicks must not: they are not bans and the
|
|
screen has nothing to date. */
|
|
[Fact]
|
|
public void BanBlockDetailsCarryBannedSinceWhileKicksDoNot()
|
|
{
|
|
var startedAt = DateTime.UtcNow.AddDays(-3);
|
|
|
|
var ban = PlayerUtils.BuildBlockDetails(true, ReportCategory.Misc, "reason", startedAt.AddDays(5), startedAtUtc: startedAt);
|
|
Assert.Equal(startedAt, ban.TimeoutStartedAt);
|
|
|
|
var kick = PlayerUtils.BuildBlockDetails(true, ReportCategory.Misc, "kicked", DateTime.UtcNow.AddMinutes(15));
|
|
Assert.Null(kick.TimeoutStartedAt);
|
|
}
|
|
}
|