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)
|
typeof(ReadyDiscordEvent)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
op = DiscordOpCodes.Dispatch,
|
||||||
|
s = null as int?,
|
||||||
|
t = DiscordEventTypes.MessageCreate,
|
||||||
|
d = null as object
|
||||||
|
},
|
||||||
|
typeof(MessageCreateDiscordEvent)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
@@ -100,6 +110,36 @@ public class DiscordEventConverterTests
|
|||||||
d = null as object
|
d = null as object
|
||||||
},
|
},
|
||||||
typeof(DiscordEvent)
|
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;
|
namespace StevesBot.Worker.Tests.Unit;
|
||||||
|
|
||||||
public sealed class DiscordRestClientTests : IDisposable
|
public sealed class DiscordRestClientTests : IDisposable
|
||||||
{
|
{
|
||||||
private const string BaseUrl = "https://discord.com/api/v10";
|
private const string BaseUrl = "https://discord.com/api/v10";
|
||||||
private static string GatewayEndpoint => $"{BaseUrl}/gateway";
|
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 MockHttpMessageHandler _mockHttpMessageHandler;
|
||||||
private readonly DiscordRestClient _discordRestClient;
|
private readonly DiscordRestClient _discordRestClient;
|
||||||
|
|
||||||
@@ -14,7 +17,23 @@ public sealed class DiscordRestClientTests : IDisposable
|
|||||||
_mockHttpMessageHandler = new MockHttpMessageHandler();
|
_mockHttpMessageHandler = new MockHttpMessageHandler();
|
||||||
var httpClient = _mockHttpMessageHandler.ToHttpClient();
|
var httpClient = _mockHttpMessageHandler.ToHttpClient();
|
||||||
httpClient.BaseAddress = new Uri("https://discord.com/api/v10/");
|
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]
|
[Fact]
|
||||||
@@ -58,6 +77,60 @@ public sealed class DiscordRestClientTests : IDisposable
|
|||||||
result.Should().Be(expectedUrl);
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_mockHttpMessageHandler.Dispose();
|
_mockHttpMessageHandler.Dispose();
|
||||||
|
|||||||
@@ -42,4 +42,19 @@ public class LockReleaserTests
|
|||||||
|
|
||||||
semaphore.CurrentCount.Should().Be(1);
|
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;
|
||||||
global using StevesBot.Worker.Discord.Gateway.Events.Data;
|
global using StevesBot.Worker.Discord.Gateway.Events.Data;
|
||||||
global using StevesBot.Worker.Discord.Rest;
|
global using StevesBot.Worker.Discord.Rest;
|
||||||
|
global using StevesBot.Worker.Discord.Rest.Requests;
|
||||||
global using StevesBot.Worker.Discord.Shared;
|
global using StevesBot.Worker.Discord.Shared;
|
||||||
global using StevesBot.Worker.Tests.Integration.Infrastructure;
|
global using StevesBot.Worker.Tests.Integration.Infrastructure;
|
||||||
global using StevesBot.Worker.Threading;
|
global using StevesBot.Worker.Threading;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ internal sealed class DiscordRestClient : IDiscordRestClient
|
|||||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
_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 gatewayEndpoint = new Uri("gateway", UriKind.Relative);
|
||||||
var response = await _httpClient.GetAsync(gatewayEndpoint, cancellationToken);
|
var response = await _httpClient.GetAsync(gatewayEndpoint, cancellationToken);
|
||||||
@@ -36,7 +36,7 @@ internal sealed class DiscordRestClient : IDiscordRestClient
|
|||||||
return gatewayResponse.Url;
|
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 channelEndpoint = new Uri($"channels/{channelId}/messages", UriKind.Relative);
|
||||||
var response = await _httpClient.PostAsJsonAsync(channelEndpoint, request, cancellationToken);
|
var response = await _httpClient.PostAsJsonAsync(channelEndpoint, request, cancellationToken);
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ namespace StevesBot.Worker.Discord.Rest;
|
|||||||
|
|
||||||
internal interface IDiscordRestClient
|
internal interface IDiscordRestClient
|
||||||
{
|
{
|
||||||
Task<string> GetGatewayUrlAsync(CancellationToken cancellationToken);
|
Task<string> GetGatewayUrlAsync(CancellationToken cancellationToken = default);
|
||||||
Task<DiscordMessage> CreateMessageAsync(string channelId, CreateMessageRequest request, CancellationToken cancellationToken);
|
Task<DiscordMessage> CreateMessageAsync(string channelId, CreateMessageRequest request, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@ namespace StevesBot.Worker.Discord.Rest.Requests;
|
|||||||
|
|
||||||
internal sealed record CreateMessageRequest(
|
internal sealed record CreateMessageRequest(
|
||||||
[property: JsonPropertyName("content")] string Content,
|
[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;
|
namespace StevesBot.Worker.Discord.Shared;
|
||||||
|
|
||||||
internal sealed record MessageReference(
|
internal sealed record DiscordMessageReference(
|
||||||
[property: JsonPropertyName("type")] int Type,
|
[property: JsonPropertyName("type")] int Type,
|
||||||
[property: JsonPropertyName("message_id")] string MessageId,
|
[property: JsonPropertyName("message_id")] string MessageId,
|
||||||
[property: JsonPropertyName("channel_id")] string ChannelId,
|
[property: JsonPropertyName("channel_id")] string ChannelId,
|
||||||
[property: JsonPropertyName("guild_id")] string GuildId,
|
[property: JsonPropertyName("guild_id")] string GuildId,
|
||||||
[property: JsonPropertyName("fail_if_not_exists")] bool FailIfNotExists
|
[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(
|
public static async Task HandleAsync(
|
||||||
DiscordEvent discordEvent,
|
DiscordEvent discordEvent,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
CancellationToken cancellationToken
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var discordRestClient = serviceProvider.GetRequiredService<IDiscordRestClient>();
|
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)
|
if (discordEvent is not MessageCreateDiscordEvent mcde || mcde.IsMessageType(DiscordMessageTypes.UserJoin) == false)
|
||||||
{
|
{
|
||||||
@@ -23,7 +23,7 @@ internal static class WelcomeMessageHandler
|
|||||||
var request = new CreateMessageRequest(
|
var request = new CreateMessageRequest(
|
||||||
Content: welcomeMessage,
|
Content: welcomeMessage,
|
||||||
MessageReference: new(
|
MessageReference: new(
|
||||||
Type: MessageReferenceTypes.Default,
|
Type: DiscordMessageReferenceTypes.Default,
|
||||||
MessageId: mcde.Data.Id,
|
MessageId: mcde.Data.Id,
|
||||||
ChannelId: mcde.Data.ChannelId,
|
ChannelId: mcde.Data.ChannelId,
|
||||||
GuildId: mcde.Data.GuildId,
|
GuildId: mcde.Data.GuildId,
|
||||||
|
|||||||
Reference in New Issue
Block a user