tests: add unit tests for CreateMessageRequest, DiscordMessageReference, and DiscordRestClient
refactor: update DiscordRestClient and IDiscordRestClient to use default CancellationToken refactor: rename MessageReference to DiscordMessageReference and add DiscordMessageReferenceTypes chore: update WelcomeMessageHandler to use new DiscordMessageReference
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class CreateMessageRequestTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var content = "content";
|
||||
var messageReference = new DiscordMessageReference(
|
||||
1,
|
||||
"message_id",
|
||||
"channel_id",
|
||||
"guild_id",
|
||||
false
|
||||
);
|
||||
|
||||
var result = new CreateMessageRequest(content, messageReference);
|
||||
|
||||
result.Content.Should().Be(content);
|
||||
result.MessageReference.Should().BeSameAs(messageReference);
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,16 @@ public class DiscordEventConverterTests
|
||||
},
|
||||
typeof(ReadyDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = DiscordOpCodes.Dispatch,
|
||||
s = null as int?,
|
||||
t = DiscordEventTypes.MessageCreate,
|
||||
d = null as object
|
||||
},
|
||||
typeof(MessageCreateDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
@@ -100,6 +110,36 @@ public class DiscordEventConverterTests
|
||||
d = null as object
|
||||
},
|
||||
typeof(DiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = 1,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = null as object
|
||||
},
|
||||
typeof(HeartbeatDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = 7,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = null as object
|
||||
},
|
||||
typeof(ReconnectDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = 9,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = false
|
||||
},
|
||||
typeof(InvalidSessionDiscordEvent)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordMessageReferenceTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var type = 1;
|
||||
var messageId = "message_id";
|
||||
var channelId = "channel_id";
|
||||
var guildId = "guild_id";
|
||||
var failIfNotExists = true;
|
||||
|
||||
var result = new DiscordMessageReference(
|
||||
type,
|
||||
messageId,
|
||||
channelId,
|
||||
guildId,
|
||||
failIfNotExists
|
||||
);
|
||||
|
||||
result.Type.Should().Be(type);
|
||||
result.MessageId.Should().Be(messageId);
|
||||
result.ChannelId.Should().Be(channelId);
|
||||
result.GuildId.Should().Be(guildId);
|
||||
result.FailIfNotExists.Should().Be(failIfNotExists);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordMessageReferenceTypesTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public void Type_WhenCalled_ItShouldReturnExpectedValue(int type, int expected)
|
||||
{
|
||||
type.Should().Be(expected);
|
||||
}
|
||||
|
||||
public static TheoryData<int, int> TestData => new()
|
||||
{
|
||||
{ DiscordMessageReferenceTypes.Default, 0 },
|
||||
{ DiscordMessageReferenceTypes.Forward, 1 },
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public sealed class DiscordRestClientTests : IDisposable
|
||||
{
|
||||
private const string BaseUrl = "https://discord.com/api/v10";
|
||||
private static string GatewayEndpoint => $"{BaseUrl}/gateway";
|
||||
private static string ChannelMessagesEndpoint => $"{BaseUrl}/channels/*/messages";
|
||||
|
||||
private readonly Mock<ILogger<DiscordRestClient>> _loggerMock = new();
|
||||
private readonly Mock<ILogger<DiscordRestClient>> _mockLogger = new();
|
||||
private readonly MockHttpMessageHandler _mockHttpMessageHandler;
|
||||
private readonly DiscordRestClient _discordRestClient;
|
||||
|
||||
@@ -14,7 +17,23 @@ public sealed class DiscordRestClientTests : IDisposable
|
||||
_mockHttpMessageHandler = new MockHttpMessageHandler();
|
||||
var httpClient = _mockHttpMessageHandler.ToHttpClient();
|
||||
httpClient.BaseAddress = new Uri("https://discord.com/api/v10/");
|
||||
_discordRestClient = new DiscordRestClient(_loggerMock.Object, httpClient);
|
||||
_discordRestClient = new DiscordRestClient(_mockLogger.Object, httpClient);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenLoggerIsNull_ItShouldThrowAnException()
|
||||
{
|
||||
var act = () => new DiscordRestClient(null!, _mockHttpMessageHandler.ToHttpClient());
|
||||
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenHttpClientIsNull_ItShouldThrowAnException()
|
||||
{
|
||||
var act = () => new DiscordRestClient(_mockLogger.Object, null!);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -58,6 +77,60 @@ public sealed class DiscordRestClientTests : IDisposable
|
||||
result.Should().Be(expectedUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenRequestFails_ItShouldThrowAnException()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.When(ChannelMessagesEndpoint)
|
||||
.Respond(HttpStatusCode.InternalServerError);
|
||||
|
||||
var request = new CreateMessageRequest(
|
||||
"content",
|
||||
new(DiscordMessageReferenceTypes.Default, "message_id", "channel_id", "guild_id", false)
|
||||
);
|
||||
|
||||
var act = async () => await _discordRestClient.CreateMessageAsync("channel_id", request);
|
||||
|
||||
await act.Should().ThrowAsync<DiscordRestClientException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageyAsync_WhenResponseIsNull_ItShouldThrowAnException()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.When(ChannelMessagesEndpoint)
|
||||
.Respond(HttpStatusCode.OK, "application/json", "null");
|
||||
|
||||
var request = new CreateMessageRequest(
|
||||
"content",
|
||||
new(DiscordMessageReferenceTypes.Default, "message_id", "channel_id", "guild_id", false)
|
||||
);
|
||||
|
||||
var act = async () => await _discordRestClient.CreateMessageAsync("channel_id", request);
|
||||
|
||||
await act.Should().ThrowAsync<DiscordRestClientException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenRequestSucceeds_ItShouldReturnMessage()
|
||||
{
|
||||
var message = new DiscordMessage();
|
||||
var messageResponse = JsonSerializer.Serialize(message);
|
||||
|
||||
_mockHttpMessageHandler
|
||||
.When(ChannelMessagesEndpoint)
|
||||
.Respond(HttpStatusCode.OK, "application/json", messageResponse);
|
||||
|
||||
var request = new CreateMessageRequest(
|
||||
"content",
|
||||
new(DiscordMessageReferenceTypes.Default, "message_id", "channel_id", "guild_id", false)
|
||||
);
|
||||
|
||||
var result = await _discordRestClient.CreateMessageAsync("channel_id", request);
|
||||
|
||||
result.Should().BeEquivalentTo(message);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mockHttpMessageHandler.Dispose();
|
||||
|
||||
@@ -42,4 +42,19 @@ public class LockReleaserTests
|
||||
|
||||
semaphore.CurrentCount.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WhenCalledAndSemaphoreIsAlreadyDisposed_ItShouldNotThrowException()
|
||||
{
|
||||
var act = () =>
|
||||
{
|
||||
var semaphore = new SemaphoreSlim(0, 1);
|
||||
var lockReleaser = new LockReleaser(semaphore);
|
||||
|
||||
semaphore.Dispose();
|
||||
lockReleaser.Dispose();
|
||||
};
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using StevesBot.Worker.Handlers;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class WelcomeMessageHandlerTests
|
||||
{
|
||||
private readonly Mock<IDiscordRestClient> _mockDiscordRestClient = new();
|
||||
private readonly Mock<ILogger<IDiscordGatewayClient>> _mockLogger = new();
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public WelcomeMessageHandlerTests()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddSingleton(_mockDiscordRestClient.Object);
|
||||
serviceCollection.AddSingleton(_mockLogger.Object);
|
||||
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenEventIsNotMessageCreateEvent_ItShouldNotCreateMessage()
|
||||
{
|
||||
await WelcomeMessageHandler.HandleAsync(new DiscordEvent(), _serviceProvider);
|
||||
|
||||
_mockDiscordRestClient
|
||||
.Verify(
|
||||
static c => c.CreateMessageAsync(It.IsAny<string>(), It.IsAny<CreateMessageRequest>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ global using StevesBot.Worker.Discord.Gateway;
|
||||
global using StevesBot.Worker.Discord.Gateway.Events;
|
||||
global using StevesBot.Worker.Discord.Gateway.Events.Data;
|
||||
global using StevesBot.Worker.Discord.Rest;
|
||||
global using StevesBot.Worker.Discord.Rest.Requests;
|
||||
global using StevesBot.Worker.Discord.Shared;
|
||||
global using StevesBot.Worker.Tests.Integration.Infrastructure;
|
||||
global using StevesBot.Worker.Threading;
|
||||
|
||||
@@ -14,7 +14,7 @@ internal sealed class DiscordRestClient : IDiscordRestClient
|
||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
}
|
||||
|
||||
public async Task<string> GetGatewayUrlAsync(CancellationToken cancellationToken)
|
||||
public async Task<string> GetGatewayUrlAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var gatewayEndpoint = new Uri("gateway", UriKind.Relative);
|
||||
var response = await _httpClient.GetAsync(gatewayEndpoint, cancellationToken);
|
||||
@@ -36,7 +36,7 @@ internal sealed class DiscordRestClient : IDiscordRestClient
|
||||
return gatewayResponse.Url;
|
||||
}
|
||||
|
||||
public async Task<DiscordMessage> CreateMessageAsync(string channelId, CreateMessageRequest request, CancellationToken cancellationToken)
|
||||
public async Task<DiscordMessage> CreateMessageAsync(string channelId, CreateMessageRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var channelEndpoint = new Uri($"channels/{channelId}/messages", UriKind.Relative);
|
||||
var response = await _httpClient.PostAsJsonAsync(channelEndpoint, request, cancellationToken);
|
||||
|
||||
@@ -2,6 +2,6 @@ namespace StevesBot.Worker.Discord.Rest;
|
||||
|
||||
internal interface IDiscordRestClient
|
||||
{
|
||||
Task<string> GetGatewayUrlAsync(CancellationToken cancellationToken);
|
||||
Task<DiscordMessage> CreateMessageAsync(string channelId, CreateMessageRequest request, CancellationToken cancellationToken);
|
||||
Task<string> GetGatewayUrlAsync(CancellationToken cancellationToken = default);
|
||||
Task<DiscordMessage> CreateMessageAsync(string channelId, CreateMessageRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -2,5 +2,5 @@ namespace StevesBot.Worker.Discord.Rest.Requests;
|
||||
|
||||
internal sealed record CreateMessageRequest(
|
||||
[property: JsonPropertyName("content")] string Content,
|
||||
[property: JsonPropertyName("message_reference")] MessageReference? MessageReference
|
||||
[property: JsonPropertyName("message_reference")] DiscordMessageReference? MessageReference
|
||||
);
|
||||
@@ -1,15 +1,9 @@
|
||||
namespace StevesBot.Worker.Discord.Shared;
|
||||
|
||||
internal sealed record MessageReference(
|
||||
internal sealed record DiscordMessageReference(
|
||||
[property: JsonPropertyName("type")] int Type,
|
||||
[property: JsonPropertyName("message_id")] string MessageId,
|
||||
[property: JsonPropertyName("channel_id")] string ChannelId,
|
||||
[property: JsonPropertyName("guild_id")] string GuildId,
|
||||
[property: JsonPropertyName("fail_if_not_exists")] bool FailIfNotExists
|
||||
);
|
||||
|
||||
internal static class MessageReferenceTypes
|
||||
{
|
||||
public const int Default = 0;
|
||||
public const int Forward = 1;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace StevesBot.Worker.Discord.Shared;
|
||||
|
||||
internal static class DiscordMessageReferenceTypes
|
||||
{
|
||||
public const int Default = 0;
|
||||
public const int Forward = 1;
|
||||
}
|
||||
@@ -6,11 +6,11 @@ internal static class WelcomeMessageHandler
|
||||
public static async Task HandleAsync(
|
||||
DiscordEvent discordEvent,
|
||||
IServiceProvider serviceProvider,
|
||||
CancellationToken cancellationToken
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var discordRestClient = serviceProvider.GetRequiredService<IDiscordRestClient>();
|
||||
var logger = serviceProvider.GetRequiredService<ILogger<DiscordGatewayClient>>();
|
||||
var logger = serviceProvider.GetRequiredService<ILogger<IDiscordGatewayClient>>();
|
||||
|
||||
if (discordEvent is not MessageCreateDiscordEvent mcde || mcde.IsMessageType(DiscordMessageTypes.UserJoin) == false)
|
||||
{
|
||||
@@ -23,7 +23,7 @@ internal static class WelcomeMessageHandler
|
||||
var request = new CreateMessageRequest(
|
||||
Content: welcomeMessage,
|
||||
MessageReference: new(
|
||||
Type: MessageReferenceTypes.Default,
|
||||
Type: DiscordMessageReferenceTypes.Default,
|
||||
MessageId: mcde.Data.Id,
|
||||
ChannelId: mcde.Data.ChannelId,
|
||||
GuildId: mcde.Data.GuildId,
|
||||
|
||||
Reference in New Issue
Block a user