Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions Jobs/TemporaryBansJob.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Net;
using Discord;
using Discord.Net;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using Morpheus.Database;
Expand Down Expand Up @@ -36,9 +38,12 @@ public async Task Execute(IJobExecutionContext context)
continue;
}

await guild.RemoveBanAsync(ban.UserId);
ban.UnbannedAt = DateTime.UtcNow;
Log($"Unbanned user {ban.UserId} from guild {ban.GuildId} (temp ban {ban.Id}).");
bool wasAlreadyUnbanned = await CompleteUnbanAsync(
ban,
() => guild.RemoveBanAsync(ban.UserId));
Log(wasAlreadyUnbanned
? $"User {ban.UserId} was already unbanned from guild {ban.GuildId} (temp ban {ban.Id})."
: $"Unbanned user {ban.UserId} from guild {ban.GuildId} (temp ban {ban.Id}).");
}
catch (Exception ex)
{
Expand All @@ -49,6 +54,28 @@ public async Task Execute(IJobExecutionContext context)

await db.SaveChangesAsync();
}

internal static async Task<bool> CompleteUnbanAsync(
TemporaryBan ban,
Func<Task> removeBanAsync)
{
bool wasAlreadyUnbanned = false;
try
{
await removeBanAsync();
}
catch (HttpException ex) when (
ex.HttpCode == HttpStatusCode.NotFound &&
ex.DiscordCode == DiscordErrorCode.UnknownBan)
{
// A moderator may have removed the ban before this job ran. Discord's
// Unknown Ban response means the desired state is already reached.
wasAlreadyUnbanned = true;
}

ban.UnbannedAt = DateTime.UtcNow;
return wasAlreadyUnbanned;
}
}


42 changes: 42 additions & 0 deletions Morpheus.Tests/TemporaryBansJobTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System.Net;
using Discord;
using Discord.Net;
using Discord.WebSocket;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
Expand All @@ -10,6 +13,45 @@ namespace Morpheus.Tests;

public class TemporaryBansJobTests
{
[Fact]
public async Task CompleteUnbanAsync_WhenBanIsAlreadyMissing_MarksBanCompleted()
{
TemporaryBan ban = new();
HttpException notFound = new(
HttpStatusCode.NotFound,
null!,
DiscordErrorCode.UnknownBan,
"Unknown Ban",
[]);

bool wasAlreadyUnbanned = await TemporaryBansJob.CompleteUnbanAsync(
ban,
() => Task.FromException(notFound));

Assert.True(wasAlreadyUnbanned);
Assert.NotNull(ban.UnbannedAt);
}

[Fact]
public async Task CompleteUnbanAsync_WhenDifferentDiscordResourceIsMissing_LeavesBanPending()
{
TemporaryBan ban = new();
HttpException unknownGuild = new(
HttpStatusCode.NotFound,
null!,
DiscordErrorCode.UnknownGuild,
"Unknown Guild",
[]);

HttpException thrown = await Assert.ThrowsAsync<HttpException>(() =>
TemporaryBansJob.CompleteUnbanAsync(
ban,
() => Task.FromException(unknownGuild)));

Assert.Same(unknownGuild, thrown);
Assert.Null(ban.UnbannedAt);
}

[Fact]
public async Task Execute_WhenGuildIsUnavailable_LeavesBanPendingForRetry()
{
Expand Down