diff --git a/CHANGELOG.md b/CHANGELOG.md index d39e792e..780e2628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Please ADD ALL Changes to the UNRELEASED SECTION and not a specific release - SnsMessage: Token property was never populated from the constructor argument - Added null guards for botMessageChannel and botReleaseMessageChannel parameters in BotService constructor - Pass ILogger to HealthCheckClient.ExecuteAsync to match new API in Credfeto.Docker.HealthCheck.Http.Client 0.0.72.928 +- Made DiscordChannelAdapter testable by accepting ITextChannel instead of the sealed SocketTextChannel, and added unit tests ### Changed - Dependencies - Updated NSubstitute.Analyzers.CSharp to 1.0.17 - Switched to use minimal APIs diff --git a/src/BuildBot.Discord.Tests/BuildBot.Discord.Tests.csproj b/src/BuildBot.Discord.Tests/BuildBot.Discord.Tests.csproj index a05fb59c..b687ed47 100644 --- a/src/BuildBot.Discord.Tests/BuildBot.Discord.Tests.csproj +++ b/src/BuildBot.Discord.Tests/BuildBot.Discord.Tests.csproj @@ -70,4 +70,4 @@ - \ No newline at end of file + diff --git a/src/BuildBot.Discord.Tests/Services/DiscordChannelAdapterTests.cs b/src/BuildBot.Discord.Tests/Services/DiscordChannelAdapterTests.cs new file mode 100644 index 00000000..34ed239f --- /dev/null +++ b/src/BuildBot.Discord.Tests/Services/DiscordChannelAdapterTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading.Tasks; +using BuildBot.Discord.Services; +using Discord; +using FunFair.Test.Common; +using NSubstitute; +using Xunit; + +namespace BuildBot.Discord.Tests.Services; + +public sealed class DiscordChannelAdapterTests : TestBase +{ + [Fact] + public void Name_ReturnsChannelName() + { + ITextChannel channel = GetSubstitute(); + channel.Name.Returns("test-channel"); + + DiscordChannelAdapter adapter = new(channel); + + Assert.Equal(expected: "test-channel", actual: adapter.Name); + } + + [Fact] + public void EnterTypingState_ReturnsTypingStateFromChannel() + { + ITextChannel channel = GetSubstitute(); + IDisposable typingState = GetSubstitute(); + channel.EnterTypingState(Arg.Any()).Returns(typingState); + + DiscordChannelAdapter adapter = new(channel); + + IDisposable result = adapter.EnterTypingState(); + + Assert.Same(expected: typingState, actual: result); + } + + [Fact] + public async Task SendMessageAsync_ReturnsChannelNameAndCleanContent() + { + ITextChannel channel = GetSubstitute(); + IUserMessage message = GetSubstitute(); + + channel.Name.Returns("sent-channel"); + + // In production CleanContent is always "" because the adapter sends text: string.Empty (embed-only). + message.CleanContent.Returns("clean content"); + + channel.SendMessageAsync(text: string.Empty, embed: default).ReturnsForAnyArgs(Task.FromResult(message)); + + DiscordChannelAdapter adapter = new(channel); + + Embed embed = new EmbedBuilder().Build(); + (string sentToChannel, string messageContent) = await adapter.SendMessageAsync(embed); + + Assert.Equal(expected: "sent-channel", actual: sentToChannel); + Assert.Equal(expected: "clean content", actual: messageContent); + + await channel + .Received(1) + .SendMessageAsync( + text: string.Empty, + isTTS: Arg.Any(), + embed: embed, + options: Arg.Any(), + allowedMentions: Arg.Any(), + messageReference: Arg.Any(), + components: Arg.Any(), + stickers: Arg.Any(), + embeds: Arg.Any(), + flags: Arg.Any(), + poll: Arg.Any() + ); + } +} diff --git a/src/BuildBot.Discord/Services/DiscordChannelAdapter.cs b/src/BuildBot.Discord/Services/DiscordChannelAdapter.cs index cf64a6cd..a9e0fd99 100644 --- a/src/BuildBot.Discord/Services/DiscordChannelAdapter.cs +++ b/src/BuildBot.Discord/Services/DiscordChannelAdapter.cs @@ -1,18 +1,16 @@ using System; using System.Threading.Tasks; using Discord; -using Discord.Rest; -using Discord.WebSocket; namespace BuildBot.Discord.Services; -internal sealed class DiscordChannelAdapter : IDiscordChannel +public sealed class DiscordChannelAdapter : IDiscordChannel { - private readonly SocketTextChannel _channel; + private readonly ITextChannel _channel; - public DiscordChannelAdapter(SocketTextChannel channel) + public DiscordChannelAdapter(ITextChannel channel) { - this._channel = channel; + this._channel = channel ?? throw new ArgumentNullException(nameof(channel)); } public string Name => this._channel.Name; @@ -24,8 +22,8 @@ public IDisposable EnterTypingState() public async Task<(string SentToChannel, string MessageContent)> SendMessageAsync(Embed embed) { - RestUserMessage msg = await this._channel.SendMessageAsync(text: string.Empty, embed: embed); + IUserMessage msg = await this._channel.SendMessageAsync(text: string.Empty, embed: embed); - return (msg.Channel.Name, msg.CleanContent); + return (this._channel.Name, msg.CleanContent); } }