-
Notifications
You must be signed in to change notification settings - Fork 1
feat: make DiscordChannelAdapter testable and add unit tests #387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
30b51c0
8a1f945
2a0c0a2
8ea346b
b7a42d9
46862a7
353bbf1
7e70d9a
be6b021
6726355
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<ITextChannel>(); | ||
| 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<ITextChannel>(); | ||
| IDisposable typingState = GetSubstitute<IDisposable>(); | ||
| channel.EnterTypingState(Arg.Any<RequestOptions>()).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<ITextChannel>(); | ||
| IUserMessage message = GetSubstitute<IUserMessage>(); | ||
|
|
||
| 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); | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test sets up the mock with Add a await channel
.Received(1)
.SendMessageAsync(
text: string.Empty,
tts: false,
embed: embed,
options: Arg.Any<RequestOptions>(),
allowedMentions: Arg.Any<AllowedMentions>(),
messageReference: Arg.Any<MessageReference>(),
components: Arg.Any<MessageComponent>(),
stickers: Arg.Any<ISticker[]>(),
embeds: Arg.Any<Embed[]>(),
flags: Arg.Any<MessageFlags>(),
poll: Arg.Any<PollProperties>()
); |
||
|
|
||
| await channel | ||
| .Received(1) | ||
| .SendMessageAsync( | ||
| text: string.Empty, | ||
| isTTS: Arg.Any<bool>(), | ||
| embed: embed, | ||
| options: Arg.Any<RequestOptions>(), | ||
| allowedMentions: Arg.Any<AllowedMentions>(), | ||
| messageReference: Arg.Any<MessageReference>(), | ||
| components: Arg.Any<MessageComponent>(), | ||
| stickers: Arg.Any<ISticker[]>(), | ||
| embeds: Arg.Any<Embed[]>(), | ||
| flags: Arg.Any<MessageFlags>(), | ||
| poll: Arg.Any<PollProperties>() | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The correct fix is to revert to [assembly: InternalsVisibleTo("BuildBot.Discord.Tests")]
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverted — |
||
| { | ||
| 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); | ||
| } | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in a37cac5 — switched to |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GatewayIntents.MessageContentis absent from the intents registered inDiscordSocketClientAdapter(onlyGuilds | GuildMessageTyping | GuildMessagesare set). Discord's gateway strips message content when this intent is not enabled, somsg.CleanContentalways returns""in production.The test stubs
message.CleanContent.Returns("clean content")and passes, but the actual production path silently returns an empty string. The test gives false confidence thatCleanContentcontains useful data.Consider either:
GatewayIntents.MessageContentto the registered intents inDiscordSocketClientAdapter, orCleanContentis expected to be empty in production due to the missing intent.