diff --git a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj
new file mode 100644
index 0000000..1d7d9b1
--- /dev/null
+++ b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net6.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs b/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs
new file mode 100644
index 0000000..5b38306
--- /dev/null
+++ b/test/Discord.Net.Tests.Integration/DiscordRestApiClientTests.cs
@@ -0,0 +1,53 @@
+using Discord.API;
+using Discord.API.Rest;
+using Discord.Net;
+using Discord.Rest;
+using FluentAssertions;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Discord;
+
+[CollectionDefinition(nameof(DiscordRestApiClientTests), DisableParallelization = true)]
+public class DiscordRestApiClientTests : IClassFixture, IAsyncDisposable
+{
+ private readonly DiscordRestApiClient _apiClient;
+ private readonly IGuild _guild;
+ private readonly ITextChannel _channel;
+
+ public DiscordRestApiClientTests(RestGuildFixture guildFixture)
+ {
+ _guild = guildFixture.Guild;
+ _apiClient = guildFixture.Client.ApiClient;
+ _channel = _guild.CreateTextChannelAsync("testChannel").Result;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await _channel.DeleteAsync();
+ }
+
+ [Fact]
+ public async Task UploadFile_WithMaximumSize_DontThrowsException()
+ {
+ var fileSize = GuildHelper.GetUploadLimit(_guild.PremiumTier);
+ using var stream = new MemoryStream(new byte[fileSize]);
+
+ await _apiClient.UploadFileAsync(_channel.Id, new UploadFileParams(new FileAttachment(stream, "filename")));
+ }
+
+ [Fact]
+ public async Task UploadFile_WithOverSize_ThrowsException()
+ {
+ var fileSize = GuildHelper.GetUploadLimit(_guild.PremiumTier) + 1;
+ using var stream = new MemoryStream(new byte[fileSize]);
+
+ Func upload = async () =>
+ await _apiClient.UploadFileAsync(_channel.Id, new UploadFileParams(new FileAttachment(stream, "filename")));
+
+ await upload.Should().ThrowExactlyAsync()
+ .Where(e => e.DiscordCode == DiscordErrorCode.RequestEntityTooLarge);
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs
new file mode 100644
index 0000000..de7d0ae
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Discord
+{
+ ///
+ /// Tests the behavior of the type and related functions.
+ ///
+ public class ChannelPermissionsTests
+ {
+ ///
+ /// Tests the default value of the constructor.
+ ///
+ [Fact]
+ public void DefaultConstructor()
+ {
+ var permission = new ChannelPermissions();
+ Assert.Equal((ulong)0, permission.RawValue);
+ Assert.Equal(ChannelPermissions.None.RawValue, permission.RawValue);
+ }
+
+ ///
+ /// Tests the behavior of the raw value constructor.
+ ///
+ [Fact]
+ public void RawValueConstructor()
+ {
+ // returns all of the values that will be tested
+ // a Theory cannot be used here, because these values are not all constants
+ IEnumerable GetTestValues()
+ {
+ yield return 0;
+ yield return ChannelPermissions.Category.RawValue;
+ yield return ChannelPermissions.DM.RawValue;
+ yield return ChannelPermissions.Group.RawValue;
+ yield return ChannelPermissions.None.RawValue;
+ yield return ChannelPermissions.Text.RawValue;
+ yield return ChannelPermissions.Voice.RawValue;
+ };
+
+ foreach (var rawValue in GetTestValues())
+ {
+ var p = new ChannelPermissions(rawValue);
+ Assert.Equal(rawValue, p.RawValue);
+ }
+ }
+
+ ///
+ /// Tests the behavior of the constructor for each
+ /// of it's flags.
+ ///
+ [Fact]
+ public void FlagsConstructor()
+ {
+ // util method for asserting that the constructor sets the given flag
+ void AssertFlag(Func cstr, ChannelPermission flag)
+ {
+ var p = cstr();
+ // ensure that this flag is set to true
+ Assert.True(p.Has(flag));
+ // ensure that only this flag is set
+ Assert.Equal((ulong)flag, p.RawValue);
+ }
+
+ AssertFlag(() => new ChannelPermissions(createInstantInvite: true), ChannelPermission.CreateInstantInvite);
+ AssertFlag(() => new ChannelPermissions(manageChannel: true), ChannelPermission.ManageChannels);
+ AssertFlag(() => new ChannelPermissions(addReactions: true), ChannelPermission.AddReactions);
+ AssertFlag(() => new ChannelPermissions(viewChannel: true), ChannelPermission.ViewChannel);
+ AssertFlag(() => new ChannelPermissions(sendMessages: true), ChannelPermission.SendMessages);
+ AssertFlag(() => new ChannelPermissions(sendTTSMessages: true), ChannelPermission.SendTTSMessages);
+ AssertFlag(() => new ChannelPermissions(manageMessages: true), ChannelPermission.ManageMessages);
+ AssertFlag(() => new ChannelPermissions(embedLinks: true), ChannelPermission.EmbedLinks);
+ AssertFlag(() => new ChannelPermissions(attachFiles: true), ChannelPermission.AttachFiles);
+ AssertFlag(() => new ChannelPermissions(readMessageHistory: true), ChannelPermission.ReadMessageHistory);
+ AssertFlag(() => new ChannelPermissions(mentionEveryone: true), ChannelPermission.MentionEveryone);
+ AssertFlag(() => new ChannelPermissions(useExternalEmojis: true), ChannelPermission.UseExternalEmojis);
+ AssertFlag(() => new ChannelPermissions(connect: true), ChannelPermission.Connect);
+ AssertFlag(() => new ChannelPermissions(speak: true), ChannelPermission.Speak);
+ AssertFlag(() => new ChannelPermissions(muteMembers: true), ChannelPermission.MuteMembers);
+ AssertFlag(() => new ChannelPermissions(deafenMembers: true), ChannelPermission.DeafenMembers);
+ AssertFlag(() => new ChannelPermissions(moveMembers: true), ChannelPermission.MoveMembers);
+ AssertFlag(() => new ChannelPermissions(useVoiceActivation: true), ChannelPermission.UseVAD);
+ AssertFlag(() => new ChannelPermissions(prioritySpeaker: true), ChannelPermission.PrioritySpeaker);
+ AssertFlag(() => new ChannelPermissions(stream: true), ChannelPermission.Stream);
+ AssertFlag(() => new ChannelPermissions(manageRoles: true), ChannelPermission.ManageRoles);
+ AssertFlag(() => new ChannelPermissions(manageWebhooks: true), ChannelPermission.ManageWebhooks);
+ AssertFlag(() => new ChannelPermissions(useApplicationCommands: true), ChannelPermission.UseApplicationCommands);
+ AssertFlag(() => new ChannelPermissions(createPrivateThreads: true), ChannelPermission.CreatePrivateThreads);
+ AssertFlag(() => new ChannelPermissions(createPublicThreads: true), ChannelPermission.CreatePublicThreads);
+ AssertFlag(() => new ChannelPermissions(sendMessagesInThreads: true), ChannelPermission.SendMessagesInThreads);
+ AssertFlag(() => new ChannelPermissions(startEmbeddedActivities: true), ChannelPermission.StartEmbeddedActivities);
+ AssertFlag(() => new ChannelPermissions(useSoundboard: true), ChannelPermission.UseSoundboard);
+ AssertFlag(() => new ChannelPermissions(createEvents: true), ChannelPermission.CreateEvents);
+ AssertFlag(() => new ChannelPermissions(sendVoiceMessages: true), ChannelPermission.SendVoiceMessages);
+ AssertFlag(() => new ChannelPermissions(useClydeAI: true), ChannelPermission.UseClydeAI);
+ AssertFlag(() => new ChannelPermissions(setVoiceChannelStatus: true), ChannelPermission.SetVoiceChannelStatus);
+ }
+
+ ///
+ /// Tests the behavior of
+ /// with each of the parameters.
+ ///
+ [Fact]
+ public void Modify()
+ {
+ // asserts that a channel permission flag value can be checked
+ // and that modify can set and unset each flag
+ // and that ToList performs as expected
+ void AssertUtil(ChannelPermission permission,
+ Func has,
+ Func modify)
+ {
+ var perm = new ChannelPermissions();
+ // ensure permission initially false
+ // use both the function and Has to ensure that the GetPermission
+ // function is working
+ Assert.False(has(perm));
+ Assert.False(perm.Has(permission));
+
+ // enable it, and ensure that it gets set
+ perm = modify(perm, true);
+ Assert.True(has(perm));
+ Assert.True(perm.Has(permission));
+
+ // check ToList behavior
+ var list = perm.ToList();
+ Assert.Contains(permission, list);
+ Assert.Single(list);
+
+ // set it false again
+ perm = modify(perm, false);
+ Assert.False(has(perm));
+ Assert.False(perm.Has(permission));
+
+ // ensure that no perms are set now
+ Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue);
+ }
+
+ AssertUtil(ChannelPermission.CreateInstantInvite, x => x.CreateInstantInvite, (p, enable) => p.Modify(createInstantInvite: enable));
+ AssertUtil(ChannelPermission.ManageChannels, x => x.ManageChannel, (p, enable) => p.Modify(manageChannel: enable));
+ AssertUtil(ChannelPermission.AddReactions, x => x.AddReactions, (p, enable) => p.Modify(addReactions: enable));
+ AssertUtil(ChannelPermission.ViewChannel, x => x.ViewChannel, (p, enable) => p.Modify(viewChannel: enable));
+ AssertUtil(ChannelPermission.SendMessages, x => x.SendMessages, (p, enable) => p.Modify(sendMessages: enable));
+ AssertUtil(ChannelPermission.SendTTSMessages, x => x.SendTTSMessages, (p, enable) => p.Modify(sendTTSMessages: enable));
+ AssertUtil(ChannelPermission.ManageMessages, x => x.ManageMessages, (p, enable) => p.Modify(manageMessages: enable));
+ AssertUtil(ChannelPermission.EmbedLinks, x => x.EmbedLinks, (p, enable) => p.Modify(embedLinks: enable));
+ AssertUtil(ChannelPermission.AttachFiles, x => x.AttachFiles, (p, enable) => p.Modify(attachFiles: enable));
+ AssertUtil(ChannelPermission.ReadMessageHistory, x => x.ReadMessageHistory, (p, enable) => p.Modify(readMessageHistory: enable));
+ AssertUtil(ChannelPermission.MentionEveryone, x => x.MentionEveryone, (p, enable) => p.Modify(mentionEveryone: enable));
+ AssertUtil(ChannelPermission.UseExternalEmojis, x => x.UseExternalEmojis, (p, enable) => p.Modify(useExternalEmojis: enable));
+ AssertUtil(ChannelPermission.Connect, x => x.Connect, (p, enable) => p.Modify(connect: enable));
+ AssertUtil(ChannelPermission.Speak, x => x.Speak, (p, enable) => p.Modify(speak: enable));
+ AssertUtil(ChannelPermission.MuteMembers, x => x.MuteMembers, (p, enable) => p.Modify(muteMembers: enable));
+ AssertUtil(ChannelPermission.DeafenMembers, x => x.DeafenMembers, (p, enable) => p.Modify(deafenMembers: enable));
+ AssertUtil(ChannelPermission.MoveMembers, x => x.MoveMembers, (p, enable) => p.Modify(moveMembers: enable));
+ AssertUtil(ChannelPermission.UseVAD, x => x.UseVAD, (p, enable) => p.Modify(useVoiceActivation: enable));
+ AssertUtil(ChannelPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable));
+ AssertUtil(ChannelPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable));
+ AssertUtil(ChannelPermission.PrioritySpeaker, x => x.PrioritySpeaker, (p, enable) => p.Modify(prioritySpeaker: enable));
+ AssertUtil(ChannelPermission.Stream, x => x.Stream, (p, enable) => p.Modify(stream: enable));
+ AssertUtil(ChannelPermission.SendVoiceMessages, x => x.SendVoiceMessages, (p, enable) => p.Modify(sendVoiceMessages: enable));
+ AssertUtil(ChannelPermission.UseClydeAI, x => x.UseClydeAI, (p, enable) => p.Modify(useClydeAI: enable));
+ AssertUtil(ChannelPermission.SetVoiceChannelStatus, x => x.SetVoiceChannelStatus, (p, enable) => p.Modify(setVoiceChannelStatus: enable));
+ }
+
+ ///
+ /// Tests that for a null channel will throw an .
+ ///
+ [Fact]
+ public void ChannelTypeResolution_Null()
+ {
+ Assert.Throws(() =>
+ {
+ ChannelPermissions.All(null);
+ });
+ }
+
+ ///
+ /// Tests that for an will return a value
+ /// equivalent to .
+ ///
+ [Fact]
+ public void ChannelTypeResolution_Text()
+ {
+ Assert.Equal(ChannelPermissions.Text.RawValue, ChannelPermissions.All(new MockedTextChannel()).RawValue);
+ }
+
+ ///
+ /// Tests that for an will return a value
+ /// equivalent to .
+ ///
+ [Fact]
+ public void ChannelTypeResolution_Voice()
+ {
+ Assert.Equal(ChannelPermissions.Voice.RawValue, ChannelPermissions.All(new MockedVoiceChannel()).RawValue);
+ }
+
+ ///
+ /// Tests that for an will return a value
+ /// equivalent to .
+ ///
+ [Fact]
+ public void ChannelTypeResolution_Category()
+ {
+ Assert.Equal(ChannelPermissions.Category.RawValue, ChannelPermissions.All(new MockedCategoryChannel()).RawValue);
+ }
+
+ ///
+ /// Tests that for an will return a value
+ /// equivalent to .
+ ///
+ [Fact]
+ public void ChannelTypeResolution_DM()
+ {
+ Assert.Equal(ChannelPermissions.DM.RawValue, ChannelPermissions.All(new MockedDMChannel()).RawValue);
+ }
+
+ ///
+ /// Tests that for an will return a value
+ /// equivalent to .
+ ///
+ [Fact]
+ public void ChannelTypeResolution_Group()
+ {
+ Assert.Equal(ChannelPermissions.Group.RawValue, ChannelPermissions.All(new MockedGroupChannel()).RawValue);
+ }
+
+ ///
+ /// Tests that for an invalid channel will throw an .
+ ///
+ [Fact]
+ public void ChannelTypeResolution_Invalid()
+ {
+ Assert.Throws(() => ChannelPermissions.All(new MockedInvalidChannel()));
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/ColorTests.cs b/test/Discord.Net.Tests.Unit/ColorTests.cs
new file mode 100644
index 0000000..48a6041
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/ColorTests.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ ///
+ /// Tests for the type.
+ ///
+ public class ColorTests
+ {
+ [Fact]
+ public void Color_New()
+ {
+ Assert.Equal(0u, new Color().RawValue);
+ Assert.Equal(uint.MinValue, new Color(uint.MinValue).RawValue);
+ Assert.Throws(() => new Color(uint.MaxValue));
+ }
+ [Fact]
+ public void Color_Default()
+ {
+ Assert.Equal(0u, Color.Default.RawValue);
+ Assert.Equal(0, Color.Default.R);
+ Assert.Equal(0, Color.Default.G);
+ Assert.Equal(0, Color.Default.B);
+ }
+ [Fact]
+ public void Color_FromRgb_Byte()
+ {
+ Assert.Equal(0xFF0000u, new Color((byte)255, (byte)0, (byte)0).RawValue);
+ Assert.Equal(0x00FF00u, new Color((byte)0, (byte)255, (byte)0).RawValue);
+ Assert.Equal(0x0000FFu, new Color((byte)0, (byte)0, (byte)255).RawValue);
+ Assert.Equal(0xFFFFFFu, new Color((byte)255, (byte)255, (byte)255).RawValue);
+ }
+ [Fact]
+ public void Color_FromRgb_Int()
+ {
+ Assert.Equal(0xFF0000u, new Color(255, 0, 0).RawValue);
+ Assert.Equal(0x00FF00u, new Color(0, 255, 0).RawValue);
+ Assert.Equal(0x0000FFu, new Color(0, 0, 255).RawValue);
+ Assert.Equal(0xFFFFFFu, new Color(255, 255, 255).RawValue);
+ }
+ [Fact]
+ public void Color_FromRgb_Int_OutOfRange()
+ {
+ Assert.Throws("r", () => new Color(-1024, 0, 0));
+ Assert.Throws("r", () => new Color(1024, 0, 0));
+ Assert.Throws("g", () => new Color(0, -1024, 0));
+ Assert.Throws("g", () => new Color(0, 1024, 0));
+ Assert.Throws("b", () => new Color(0, 0, -1024));
+ Assert.Throws("b", () => new Color(0, 0, 1024));
+ Assert.Throws(() => new Color(-1024, -1024, -1024));
+ Assert.Throws(() => new Color(1024, 1024, 1024));
+ }
+ [Fact]
+ public void Color_FromRgb_Float()
+ {
+ Assert.Equal(0xFF0000u, new Color(1.0f, 0, 0).RawValue);
+ Assert.Equal(0x00FF00u, new Color(0, 1.0f, 0).RawValue);
+ Assert.Equal(0x0000FFu, new Color(0, 0, 1.0f).RawValue);
+ Assert.Equal(0xFFFFFFu, new Color(1.0f, 1.0f, 1.0f).RawValue);
+ }
+ [Fact]
+ public void Color_FromRgb_Float_OutOfRange()
+ {
+ Assert.Throws("r", () => new Color(-2.0f, 0, 0));
+ Assert.Throws("r", () => new Color(2.0f, 0, 0));
+ Assert.Throws("g", () => new Color(0, -2.0f, 0));
+ Assert.Throws("g", () => new Color(0, 2.0f, 0));
+ Assert.Throws("b", () => new Color(0, 0, -2.0f));
+ Assert.Throws("b", () => new Color(0, 0, 2.0f));
+ Assert.Throws(() => new Color(-2.0f, -2.0f, -2.0f));
+ Assert.Throws(() => new Color(2.0f, 2.0f, 2.0f));
+ }
+ [Fact]
+ public void Color_Red()
+ {
+ Assert.Equal(0xAF, new Color(0xAF1390).R);
+ }
+ [Fact]
+ public void Color_Green()
+ {
+ Assert.Equal(0x13, new Color(0xAF1390).G);
+ }
+ [Fact]
+ public void Color_Blue()
+ {
+ Assert.Equal(0x90, new Color(0xAF1390).B);
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/CommandBuilderTests.cs b/test/Discord.Net.Tests.Unit/CommandBuilderTests.cs
new file mode 100644
index 0000000..22df7cc
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/CommandBuilderTests.cs
@@ -0,0 +1,55 @@
+using Discord;
+using System;
+using Xunit;
+
+namespace Discord;
+
+public class CommandBuilderTests
+{
+ [Fact]
+ public void BuildSimpleSlashCommand()
+ {
+ var command = new SlashCommandBuilder()
+ .WithName("command")
+ .WithDescription("description")
+ .AddOption(
+ "option1",
+ ApplicationCommandOptionType.String,
+ "option1 description",
+ isRequired: true,
+ choices: new[]
+ {
+ new ApplicationCommandOptionChoiceProperties()
+ {
+ Name = "choice1", Value = "1"
+ }
+ })
+ .AddOptions(new SlashCommandOptionBuilder()
+ .WithName("option2")
+ .WithDescription("option2 description")
+ .WithType(ApplicationCommandOptionType.String)
+ .WithRequired(true)
+ .AddChannelType(ChannelType.Text)
+ .AddChoice("choice1", "1")
+ .AddChoice("choice2", "2"));
+ command.Build();
+ }
+
+ [Fact]
+ public void BuildSubSlashCommand()
+ {
+ var command = new SlashCommandBuilder()
+ .WithName("command").WithDescription("Command desc.")
+ .AddOptions(new SlashCommandOptionBuilder()
+ .WithType(ApplicationCommandOptionType.SubCommand)
+ .WithName("subcommand").WithDescription("Subcommand desc.")
+ .AddOptions(
+ new SlashCommandOptionBuilder()
+ .WithType(ApplicationCommandOptionType.String)
+ .WithName("name1").WithDescription("desc1"),
+ new SlashCommandOptionBuilder()
+ .WithType(ApplicationCommandOptionType.String)
+ .WithName("name2").WithDescription("desc2")));
+ command.Build();
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
index 74abf5c..b16b6b1 100644
--- a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
+++ b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj
@@ -1,10 +1,25 @@
- Exe
net6.0
- enable
- enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
diff --git a/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs b/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs
new file mode 100644
index 0000000..10c0576
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/EmbedBuilderTests.cs
@@ -0,0 +1,426 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Discord
+{
+ ///
+ /// Tests the class.
+ ///
+ public class EmbedBuilderTests
+ {
+ private const string Name = "chrisj";
+ private const string Icon = "https://meowpuffygottem.fun/blob.png";
+ private const string Url = "https://meowpuffygottem.fun/";
+
+ ///
+ /// Tests the behavior of .
+ ///
+ [Fact]
+ public void WithAuthor_Strings()
+ {
+ var builder = new EmbedBuilder();
+ // null by default
+ Assert.Null(builder.Author);
+
+ builder = new EmbedBuilder()
+ .WithAuthor(Name, Icon, Url);
+
+ Assert.NotNull(builder.Author);
+ Assert.Equal(Name, builder.Author.Name);
+ Assert.Equal(Icon, builder.Author.IconUrl);
+ Assert.Equal(Url, builder.Author.Url);
+ }
+
+ ///
+ /// Tests the behavior of
+ ///
+ [Fact]
+ public void WithAuthor_AuthorBuilder()
+ {
+ var author = new EmbedAuthorBuilder()
+ .WithIconUrl(Icon)
+ .WithName(Name)
+ .WithUrl(Url);
+ var builder = new EmbedBuilder()
+ .WithAuthor(author);
+ Assert.NotNull(builder.Author);
+ Assert.Equal(Name, builder.Author.Name);
+ Assert.Equal(Icon, builder.Author.IconUrl);
+ Assert.Equal(Url, builder.Author.Url);
+ }
+
+ ///
+ /// Tests the behavior of
+ ///
+ [Fact]
+ public void WithAuthor_ActionAuthorBuilder()
+ {
+ var builder = new EmbedBuilder()
+ .WithAuthor((author) =>
+ author.WithIconUrl(Icon)
+ .WithName(Name)
+ .WithUrl(Url));
+ Assert.NotNull(builder.Author);
+ Assert.Equal(Name, builder.Author.Name);
+ Assert.Equal(Icon, builder.Author.IconUrl);
+ Assert.Equal(Url, builder.Author.Url);
+ }
+
+ ///
+ /// Tests the behavior of .
+ ///
+ [Fact]
+ public void EmbedAuthorBuilder()
+ {
+ var builder = new EmbedAuthorBuilder()
+ .WithIconUrl(Icon)
+ .WithName(Name)
+ .WithUrl(Url);
+ Assert.Equal(Icon, builder.IconUrl);
+ Assert.Equal(Name, builder.Name);
+ Assert.Equal(Url, builder.Url);
+ }
+
+ ///
+ /// Tests that invalid titles throw an .
+ ///
+ /// The embed title to set.
+ [Theory]
+ // 257 chars
+ [InlineData("jVyLChmA7aBZozXQuZ3VDEcwW6zOq0nteOVYBZi31ny73rpXfSSBXR4Jw6FiplDKQseKskwRMuBZkUewrewqAbkBZpslHirvC5nEzRySoDIdTRnkVvTXZUXg75l3bQCjuuHxDd6DfrY8ihd6yZX1Y0XFeg239YBcYV4TpL9uQ8H3HFYxrWhLlG2PRVjUmiglP5iXkawszNwMVm1SZ5LZT4jkMZHxFegVi7170d16iaPWOovu50aDDHy087XBtLKVa")]
+ // 257 chars of whitespace
+ [InlineData(" ")]
+ public void Title_Invalid(string title)
+ {
+ Assert.Throws(() =>
+ {
+ var builder = new EmbedBuilder
+ {
+ Title = title
+ };
+ });
+ Assert.Throws(() =>
+ {
+ new EmbedBuilder().WithTitle(title);
+ });
+ }
+
+ ///
+ /// Tests that valid titles do not throw any exceptions.
+ ///
+ /// The embed title to set.
+ [Theory]
+ // 256 chars
+ [InlineData("jVyLChmA7aBZozXQuZ3VDEcwW6zOq0nteOVYBZi31ny73rpXfSSBXR4Jw6FiplDKQseKskwRMuBZkUewrewqAbkBZpslHirvC5nEzRySoDIdTRnkVvTXZUXg75l3bQCjuuHxDd6DfrY8ihd6yZX1Y0XFeg239YBcYV4TpL9uQ8H3HFYxrWhLlG2PRVjUmiglP5iXkawszNwMVm1SZ5LZT4jkMZHxFegVi7170d16iaPWOovu50aDDHy087XBtLKV")]
+ public void Tile_Valid(string title)
+ {
+ var builder = new EmbedBuilder
+ {
+ Title = title
+ };
+ new EmbedBuilder().WithTitle(title);
+ }
+
+ ///
+ /// Tests that invalid descriptions throw an .
+ ///
+ [Fact]
+ public void Description_Invalid()
+ {
+ IEnumerable GetInvalid()
+ {
+ yield return new string('a', 4097);
+ }
+ foreach (var description in GetInvalid())
+ {
+ Assert.Throws(() => new EmbedBuilder().WithDescription(description));
+ Assert.Throws(() =>
+ {
+ var b = new EmbedBuilder
+ {
+ Description = description
+ };
+ });
+ }
+ }
+
+ ///
+ /// Tests that valid descriptions do not throw any exceptions.
+ ///
+ [Fact]
+ public void Description_Valid()
+ {
+ IEnumerable GetValid()
+ {
+ yield return string.Empty;
+ yield return null;
+ yield return new string('a', 4096);
+ }
+ foreach (var description in GetValid())
+ {
+ var b = new EmbedBuilder().WithDescription(description);
+ Assert.Equal(description, b.Description);
+
+ b = new EmbedBuilder
+ {
+ Description = description
+ };
+ Assert.Equal(description, b.Description);
+ }
+ }
+
+ ///
+ /// Tests that valid url does not throw any exceptions.
+ ///
+ /// The url to set.
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("https://docs.stillu.cc")]
+ public void Url_Valid(string url)
+ {
+ // does not throw an exception
+ var result = new EmbedBuilder()
+ .WithUrl(url)
+ .WithImageUrl(url)
+ .WithThumbnailUrl(url);
+ Assert.Equal(result.Url, url);
+ Assert.Equal(result.ImageUrl, url);
+ Assert.Equal(result.ThumbnailUrl, url);
+
+ result = new EmbedBuilder
+ {
+ Url = url,
+ ImageUrl = url,
+ ThumbnailUrl = url
+ };
+ Assert.Equal(result.Url, url);
+ Assert.Equal(result.ImageUrl, url);
+ Assert.Equal(result.ThumbnailUrl, url);
+ }
+
+ ///
+ /// Tests the value of the property when there are no fields set.
+ ///
+ [Fact]
+ public void Length_Empty()
+ {
+ var empty = new EmbedBuilder();
+ Assert.Equal(0, empty.Length);
+ }
+
+ ///
+ /// Tests the value of the property when all fields are set.
+ ///
+ [Fact]
+ public void Length()
+ {
+ var e = new EmbedBuilder()
+ .WithAuthor(Name, Icon, Url)
+ .WithColor(Color.Blue)
+ .WithDescription("This is the test description.")
+ .WithFooter("This is the footer", Url)
+ .WithImageUrl(Url)
+ .WithThumbnailUrl(Url)
+ .WithTimestamp(DateTimeOffset.MinValue)
+ .WithTitle("This is the title")
+ .WithUrl(Url)
+ .AddField("Field 1", "Inline", true)
+ .AddField("Field 2", "Not Inline", false);
+ Assert.Equal(100, e.Length);
+ }
+
+ ///
+ /// Tests the behavior of .
+ ///
+ [Fact]
+ public void WithCurrentTimestamp()
+ {
+ var e = new EmbedBuilder()
+ .WithCurrentTimestamp();
+ // ensure within a second of accuracy
+ Assert.Equal(DateTime.UtcNow, e.Timestamp.Value.UtcDateTime, TimeSpan.FromSeconds(1));
+ }
+
+ ///
+ /// Tests the behavior of .
+ ///
+ [Fact]
+ public void WithColor()
+ {
+ // use WithColor
+ var e = new EmbedBuilder().WithColor(Color.Red);
+ Assert.Equal(Color.Red.RawValue, e.Color.Value.RawValue);
+ }
+
+ ///
+ /// Tests the behavior of
+ ///
+ [Fact]
+ public void WithFooter_ActionFooterBuilder()
+ {
+ var e = new EmbedBuilder()
+ .WithFooter(x =>
+ {
+ x.IconUrl = Url;
+ x.Text = Name;
+ });
+ Assert.Equal(Url, e.Footer.IconUrl);
+ Assert.Equal(Name, e.Footer.Text);
+ }
+
+ ///
+ /// Tests the behavior of
+ ///
+ [Fact]
+ public void WithFooter_FooterBuilder()
+ {
+ var footer = new EmbedFooterBuilder()
+ {
+ IconUrl = Url,
+ Text = Name
+ };
+ var e = new EmbedBuilder()
+ .WithFooter(footer);
+ Assert.Equal(Url, e.Footer.IconUrl);
+ Assert.Equal(Name, e.Footer.Text);
+ // use the property
+ e = new EmbedBuilder
+ {
+ Footer = footer
+ };
+ Assert.Equal(Url, e.Footer.IconUrl);
+ Assert.Equal(Name, e.Footer.Text);
+ }
+
+ ///
+ /// Tests the behavior of
+ ///
+ [Fact]
+ public void WithFooter_Strings()
+ {
+ var e = new EmbedBuilder()
+ .WithFooter(Name, Url);
+ Assert.Equal(Url, e.Footer.IconUrl);
+ Assert.Equal(Name, e.Footer.Text);
+ }
+
+ ///
+ /// Tests the behavior of .
+ ///
+ [Fact]
+ public void EmbedFooterBuilder()
+ {
+ var footer = new EmbedFooterBuilder()
+ .WithIconUrl(Url)
+ .WithText(Name);
+ Assert.Equal(Url, footer.IconUrl);
+ Assert.Equal(Name, footer.Text);
+ }
+ ///
+ /// Tests that invalid text throws an .
+ ///
+ [Fact]
+ public void EmbedFooterBuilder_InvalidText()
+ {
+ Assert.Throws(() =>
+ {
+ new EmbedFooterBuilder().WithText(new string('a', 2049));
+ });
+ }
+ [Fact]
+ public void AddField_Strings()
+ {
+ var e = new EmbedBuilder()
+ .AddField("name", "value", true);
+ Assert.Equal("name", e.Fields[0].Name);
+ Assert.Equal("value", e.Fields[0].Value);
+ Assert.True(e.Fields[0].IsInline);
+ }
+ [Fact]
+ public void AddField_EmbedFieldBuilder()
+ {
+ var field = new EmbedFieldBuilder()
+ .WithIsInline(true)
+ .WithValue("value")
+ .WithName("name");
+ var e = new EmbedBuilder()
+ .AddField(field);
+ Assert.Equal("name", e.Fields[0].Name);
+ Assert.Equal("value", e.Fields[0].Value);
+ Assert.True(e.Fields[0].IsInline);
+ }
+ [Fact]
+ public void AddField_ActionEmbedFieldBuilder()
+ {
+ var e = new EmbedBuilder()
+ .AddField(x => x
+ .WithName("name")
+ .WithValue("value")
+ .WithIsInline(true));
+ Assert.Equal("name", e.Fields[0].Name);
+ Assert.Equal("value", e.Fields[0].Value);
+ Assert.True(e.Fields[0].IsInline);
+ }
+ [Fact]
+ public void AddField_TooManyFields()
+ {
+ var e = new EmbedBuilder();
+ for (var i = 0; i < 25; i++)
+ {
+ e = e.AddField("name", "value", false);
+ }
+ Assert.Throws(() =>
+ {
+ e = e.AddField("name", "value", false);
+ });
+ }
+ [Fact]
+ public void EmbedFieldBuilder()
+ {
+ var e = new EmbedFieldBuilder()
+ .WithIsInline(true)
+ .WithName("name")
+ .WithValue("value");
+ Assert.Equal("name", e.Name);
+ Assert.Equal("value", e.Value);
+ Assert.True(e.IsInline);
+ // use the properties
+ e = new EmbedFieldBuilder
+ {
+ IsInline = true,
+ Name = "name",
+ Value = "value"
+ };
+ Assert.Equal("name", e.Name);
+ Assert.Equal("value", e.Value);
+ Assert.True(e.IsInline);
+ }
+ [Theory]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData(null)]
+ // 257 chars
+ [InlineData("jVyLChmA7aBZozXQuZ3VDEcwW6zOq0nteOVYBZi31ny73rpXfSSBXR4Jw6FiplDKQseKskwRMuBZkUewrewqAbkBZpslHirvC5nEzRySoDIdTRnkVvTXZUXg75l3bQCjuuHxDd6DfrY8ihd6yZX1Y0XFeg239YBcYV4TpL9uQ8H3HFYxrWhLlG2PRVjUmiglP5iXkawszNwMVm1SZ5LZT4jkMZHxFegVi7170d16iaPWOovu50aDDHy087XBtLKVa")]
+ // 257 chars of whitespace
+ [InlineData(" ")]
+ public void EmbedFieldBuilder_InvalidName(string name)
+ {
+ Assert.Throws(() => new EmbedFieldBuilder().WithName(name));
+ }
+ [Fact]
+ public void EmbedFieldBuilder_InvalidValue()
+ {
+ IEnumerable GetInvalidValue()
+ {
+ yield return null;
+ yield return string.Empty;
+ yield return " ";
+ yield return new string('a', 1025);
+ };
+ foreach (var v in GetInvalidValue())
+ Assert.Throws(() => new EmbedFieldBuilder().WithValue(v));
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/EmoteTests.cs b/test/Discord.Net.Tests.Unit/EmoteTests.cs
new file mode 100644
index 0000000..a4f4417
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/EmoteTests.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ public class EmoteTests
+ {
+ [Fact]
+ public void Test_Emote_Parse()
+ {
+ Assert.True(Emote.TryParse("<:typingstatus:394207658351263745>", out Emote emote));
+ Assert.NotNull(emote);
+ Assert.Equal("typingstatus", emote.Name);
+ Assert.Equal(394207658351263745UL, emote.Id);
+ Assert.False(emote.Animated);
+ Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1514056829775), emote.CreatedAt);
+ Assert.EndsWith("png", emote.Url);
+ }
+ [Fact]
+ public void Test_Invalid_Emote_Parse()
+ {
+ Assert.False(Emote.TryParse("invalid", out _));
+ Assert.False(Emote.TryParse("<:typingstatus:not_a_number>", out _));
+ Assert.Throws(() => Emote.Parse("invalid"));
+ }
+ [Fact]
+ public void Test_Animated_Emote_Parse()
+ {
+ Assert.True(Emote.TryParse("", out Emote emote));
+ Assert.NotNull(emote);
+ Assert.Equal("typingstatus", emote.Name);
+ Assert.Equal(394207658351263745UL, emote.Id);
+ Assert.True(emote.Animated);
+ Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1514056829775), emote.CreatedAt);
+ Assert.EndsWith("gif", emote.Url);
+ }
+ [Fact]
+ public void Test_Invalid_Amimated_Emote_Parse()
+ {
+ Assert.False(Emote.TryParse("", out _));
+ Assert.False(Emote.TryParse("", out _));
+ Assert.False(Emote.TryParse("", out _));
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/FormatTests.cs b/test/Discord.Net.Tests.Unit/FormatTests.cs
new file mode 100644
index 0000000..c015c7e
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/FormatTests.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ public class FormatTests
+ {
+ [Theory]
+ [InlineData("@everyone", "@everyone")]
+ [InlineData(@"\", @"\\")]
+ [InlineData(@"*text*", @"\*text\*")]
+ [InlineData(@"~text~", @"\~text\~")]
+ [InlineData(@"`text`", @"\`text\`")]
+ [InlineData(@"_text_", @"\_text\_")]
+ [InlineData(@"> text", @"\> text")]
+ public void Sanitize(string input, string expected)
+ {
+ Assert.Equal(expected, Format.Sanitize(input));
+ }
+ [Fact]
+ public void Code()
+ {
+ // no language
+ Assert.Equal("`test`", Format.Code("test"));
+ Assert.Equal("```\nanother\none\n```", Format.Code("another\none"));
+ // language specified
+ Assert.Equal("```cs\ntest\n```", Format.Code("test", "cs"));
+ Assert.Equal("```cs\nanother\none\n```", Format.Code("another\none", "cs"));
+ }
+ [Fact]
+ public void QuoteNullString()
+ {
+ Assert.Null(Format.Quote(null));
+ }
+ [Theory]
+ [InlineData("", "")]
+ [InlineData("\n", "\n")]
+ [InlineData("foo\n\nbar", "> foo\n> \n> bar")]
+ [InlineData("input", "> input")] // single line
+ // should work with CR or CRLF
+ [InlineData("inb4\ngreentext", "> inb4\n> greentext")]
+ [InlineData("inb4\r\ngreentext", "> inb4\r\n> greentext")]
+ public void Quote(string input, string expected)
+ {
+ Assert.Equal(expected, Format.Quote(input));
+ }
+ [Theory]
+ [InlineData(null, null)]
+ [InlineData("", "")]
+ [InlineData("\n", "\n")]
+ [InlineData("foo\n\nbar", ">>> foo\n\nbar")]
+ [InlineData("input", ">>> input")] // single line
+ // should work with CR or CRLF
+ [InlineData("inb4\ngreentext", ">>> inb4\ngreentext")]
+ [InlineData("inb4\r\ngreentext", ">>> inb4\r\ngreentext")]
+ public void BlockQuote(string input, string expected)
+ {
+ Assert.Equal(expected, Format.BlockQuote(input));
+ }
+
+ [Theory]
+ [InlineData("", "")]
+ [InlineData("\n", "\n")]
+ [InlineData("**hi**", "hi")]
+ [InlineData("__uwu__", "uwu")]
+ [InlineData(">>__uwu__", "uwu")]
+ [InlineData("```uwu```", "uwu")]
+ [InlineData("~uwu~", "uwu")]
+ [InlineData("berries __and__ *Cream**, I'm a little lad who loves berries and cream", "berries and Cream, I'm a little lad who loves berries and cream")]
+ public void StripMarkdown(string input, string expected)
+ {
+ var test = Format.StripMarkDown(input);
+ Assert.Equal(expected, test);
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/GuildHelperTests.cs b/test/Discord.Net.Tests.Unit/GuildHelperTests.cs
new file mode 100644
index 0000000..b05e5ff
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/GuildHelperTests.cs
@@ -0,0 +1,23 @@
+using Discord.Rest;
+using NSubstitute;
+using System;
+using Xunit;
+
+namespace Discord;
+
+public class GuildHelperTests
+{
+ [Theory]
+ [InlineData(PremiumTier.None, 25)]
+ [InlineData(PremiumTier.Tier1, 25)]
+ [InlineData(PremiumTier.Tier2, 50)]
+ [InlineData(PremiumTier.Tier3, 100)]
+ public void GetUploadLimit(PremiumTier tier, ulong factor)
+ {
+ var expected = factor * (ulong)Math.Pow(2, 20);
+
+ var actual = GuildHelper.GetUploadLimit(tier);
+
+ Assert.Equal(expected, actual);
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
new file mode 100644
index 0000000..102860c
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ ///
+ /// Tests the behavior of the type and related functions.
+ ///
+ public class GuildPermissionsTests
+ {
+ ///
+ /// Tests the default value of the constructor.
+ ///
+ [Fact]
+ public void DefaultConstructor()
+ {
+ var p = new GuildPermissions();
+ Assert.Equal((ulong)0, p.RawValue);
+ Assert.Equal(GuildPermissions.None.RawValue, p.RawValue);
+ }
+
+ ///
+ /// Tests the behavior of the raw value constructor.
+ ///
+ [Fact]
+ public void RawValueConstructor()
+ {
+ // returns all of the values that will be tested
+ // a Theory cannot be used here, because these values are not all constants
+ IEnumerable GetTestValues()
+ {
+ yield return 0;
+ yield return GuildPermissions.None.RawValue;
+ yield return GuildPermissions.All.RawValue;
+ yield return GuildPermissions.Webhook.RawValue;
+ };
+
+ foreach (var rawValue in GetTestValues())
+ {
+ var p = new GuildPermissions(rawValue);
+ Assert.Equal(rawValue, p.RawValue);
+ }
+ }
+
+ ///
+ /// Tests the behavior of the constructor for each
+ /// of it's flags.
+ ///
+ [Fact]
+ public void FlagsConstructor()
+ {
+ // util method for asserting that the constructor sets the given flag
+ void AssertFlag(Func cstr, GuildPermission flag)
+ {
+ var p = cstr();
+ // ensure flag set to true
+ Assert.True(p.Has(flag));
+ // ensure only this flag is set
+ Assert.Equal((ulong)flag, p.RawValue);
+ }
+
+ AssertFlag(() => new GuildPermissions(createInstantInvite: true), GuildPermission.CreateInstantInvite);
+ AssertFlag(() => new GuildPermissions(kickMembers: true), GuildPermission.KickMembers);
+ AssertFlag(() => new GuildPermissions(banMembers: true), GuildPermission.BanMembers);
+ AssertFlag(() => new GuildPermissions(administrator: true), GuildPermission.Administrator);
+ AssertFlag(() => new GuildPermissions(manageChannels: true), GuildPermission.ManageChannels);
+ AssertFlag(() => new GuildPermissions(manageGuild: true), GuildPermission.ManageGuild);
+ AssertFlag(() => new GuildPermissions(addReactions: true), GuildPermission.AddReactions);
+ AssertFlag(() => new GuildPermissions(viewAuditLog: true), GuildPermission.ViewAuditLog);
+ AssertFlag(() => new GuildPermissions(viewGuildInsights: true), GuildPermission.ViewGuildInsights);
+ AssertFlag(() => new GuildPermissions(viewChannel: true), GuildPermission.ViewChannel);
+ AssertFlag(() => new GuildPermissions(sendMessages: true), GuildPermission.SendMessages);
+ AssertFlag(() => new GuildPermissions(sendTTSMessages: true), GuildPermission.SendTTSMessages);
+ AssertFlag(() => new GuildPermissions(manageMessages: true), GuildPermission.ManageMessages);
+ AssertFlag(() => new GuildPermissions(embedLinks: true), GuildPermission.EmbedLinks);
+ AssertFlag(() => new GuildPermissions(attachFiles: true), GuildPermission.AttachFiles);
+ AssertFlag(() => new GuildPermissions(readMessageHistory: true), GuildPermission.ReadMessageHistory);
+ AssertFlag(() => new GuildPermissions(mentionEveryone: true), GuildPermission.MentionEveryone);
+ AssertFlag(() => new GuildPermissions(useExternalEmojis: true), GuildPermission.UseExternalEmojis);
+ AssertFlag(() => new GuildPermissions(connect: true), GuildPermission.Connect);
+ AssertFlag(() => new GuildPermissions(speak: true), GuildPermission.Speak);
+ AssertFlag(() => new GuildPermissions(muteMembers: true), GuildPermission.MuteMembers);
+ AssertFlag(() => new GuildPermissions(deafenMembers: true), GuildPermission.DeafenMembers);
+ AssertFlag(() => new GuildPermissions(moveMembers: true), GuildPermission.MoveMembers);
+ AssertFlag(() => new GuildPermissions(useVoiceActivation: true), GuildPermission.UseVAD);
+ AssertFlag(() => new GuildPermissions(prioritySpeaker: true), GuildPermission.PrioritySpeaker);
+ AssertFlag(() => new GuildPermissions(stream: true), GuildPermission.Stream);
+ AssertFlag(() => new GuildPermissions(changeNickname: true), GuildPermission.ChangeNickname);
+ AssertFlag(() => new GuildPermissions(manageNicknames: true), GuildPermission.ManageNicknames);
+ AssertFlag(() => new GuildPermissions(manageRoles: true), GuildPermission.ManageRoles);
+ AssertFlag(() => new GuildPermissions(manageWebhooks: true), GuildPermission.ManageWebhooks);
+ AssertFlag(() => new GuildPermissions(manageEmojisAndStickers: true), GuildPermission.ManageEmojisAndStickers);
+ AssertFlag(() => new GuildPermissions(useApplicationCommands: true), GuildPermission.UseApplicationCommands);
+ AssertFlag(() => new GuildPermissions(requestToSpeak: true), GuildPermission.RequestToSpeak);
+ AssertFlag(() => new GuildPermissions(manageEvents: true), GuildPermission.ManageEvents);
+ AssertFlag(() => new GuildPermissions(manageThreads: true), GuildPermission.ManageThreads);
+ AssertFlag(() => new GuildPermissions(createPublicThreads: true), GuildPermission.CreatePublicThreads);
+ AssertFlag(() => new GuildPermissions(createPrivateThreads: true), GuildPermission.CreatePrivateThreads);
+ AssertFlag(() => new GuildPermissions(useExternalStickers: true), GuildPermission.UseExternalStickers);
+ AssertFlag(() => new GuildPermissions(moderateMembers: true), GuildPermission.ModerateMembers);
+ AssertFlag(() => new GuildPermissions(viewMonetizationAnalytics: true), GuildPermission.ViewMonetizationAnalytics);
+ AssertFlag(() => new GuildPermissions(useSoundboard: true), GuildPermission.UseSoundboard);
+ AssertFlag(() => new GuildPermissions(sendVoiceMessages: true), GuildPermission.SendVoiceMessages);
+ AssertFlag(() => new GuildPermissions(useClydeAI: true), GuildPermission.UseClydeAI);
+ AssertFlag(() => new GuildPermissions(createGuildExpressions: true), GuildPermission.CreateGuildExpressions);
+ AssertFlag(() => new GuildPermissions(setVoiceChannelStatus: true), GuildPermission.SetVoiceChannelStatus);
+ }
+
+ ///
+ /// Tests the behavior of
+ /// with each of the parameters.
+ ///
+ [Fact]
+ public void Modify()
+ {
+ // asserts that flag values can be checked
+ // and that flag values can be toggled on and off
+ // and that the behavior of ToList works as expected
+ void AssertUtil(GuildPermission permission,
+ Func has,
+ Func modify)
+ {
+ var perm = new GuildPermissions();
+ // ensure permission initially false
+ // use both the function and Has to ensure that the GetPermission
+ // function is working
+ Assert.False(has(perm));
+ Assert.False(perm.Has(permission));
+
+ // enable it, and ensure that it gets set
+ perm = modify(perm, true);
+ Assert.True(has(perm));
+ Assert.True(perm.Has(permission));
+
+ // check ToList behavior
+ var list = perm.ToList();
+ Assert.Contains(permission, list);
+ Assert.Single(list);
+
+ // set it false again
+ perm = modify(perm, false);
+ Assert.False(has(perm));
+ Assert.False(perm.Has(permission));
+
+ // ensure that no perms are set now
+ Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue);
+ }
+
+ AssertUtil(GuildPermission.CreateInstantInvite, x => x.CreateInstantInvite, (p, enable) => p.Modify(createInstantInvite: enable));
+ AssertUtil(GuildPermission.KickMembers, x => x.KickMembers, (p, enable) => p.Modify(kickMembers: enable));
+ AssertUtil(GuildPermission.BanMembers, x => x.BanMembers, (p, enable) => p.Modify(banMembers: enable));
+ AssertUtil(GuildPermission.Administrator, x => x.Administrator, (p, enable) => p.Modify(administrator: enable));
+ AssertUtil(GuildPermission.ManageChannels, x => x.ManageChannels, (p, enable) => p.Modify(manageChannels: enable));
+ AssertUtil(GuildPermission.ManageGuild, x => x.ManageGuild, (p, enable) => p.Modify(manageGuild: enable));
+ AssertUtil(GuildPermission.AddReactions, x => x.AddReactions, (p, enable) => p.Modify(addReactions: enable));
+ AssertUtil(GuildPermission.ViewAuditLog, x => x.ViewAuditLog, (p, enable) => p.Modify(viewAuditLog: enable));
+ AssertUtil(GuildPermission.ViewGuildInsights, x => x.ViewGuildInsights, (p, enable) => p.Modify(viewGuildInsights: enable));
+ AssertUtil(GuildPermission.ViewChannel, x => x.ViewChannel, (p, enable) => p.Modify(viewChannel: enable));
+ AssertUtil(GuildPermission.SendMessages, x => x.SendMessages, (p, enable) => p.Modify(sendMessages: enable));
+ AssertUtil(GuildPermission.SendTTSMessages, x => x.SendTTSMessages, (p, enable) => p.Modify(sendTTSMessages: enable));
+ AssertUtil(GuildPermission.ManageMessages, x => x.ManageMessages, (p, enable) => p.Modify(manageMessages: enable));
+ AssertUtil(GuildPermission.EmbedLinks, x => x.EmbedLinks, (p, enable) => p.Modify(embedLinks: enable));
+ AssertUtil(GuildPermission.AttachFiles, x => x.AttachFiles, (p, enable) => p.Modify(attachFiles: enable));
+ AssertUtil(GuildPermission.ReadMessageHistory, x => x.ReadMessageHistory, (p, enable) => p.Modify(readMessageHistory: enable));
+ AssertUtil(GuildPermission.MentionEveryone, x => x.MentionEveryone, (p, enable) => p.Modify(mentionEveryone: enable));
+ AssertUtil(GuildPermission.UseExternalEmojis, x => x.UseExternalEmojis, (p, enable) => p.Modify(useExternalEmojis: enable));
+ AssertUtil(GuildPermission.Connect, x => x.Connect, (p, enable) => p.Modify(connect: enable));
+ AssertUtil(GuildPermission.Speak, x => x.Speak, (p, enable) => p.Modify(speak: enable));
+ AssertUtil(GuildPermission.MuteMembers, x => x.MuteMembers, (p, enable) => p.Modify(muteMembers: enable));
+ AssertUtil(GuildPermission.MoveMembers, x => x.MoveMembers, (p, enable) => p.Modify(moveMembers: enable));
+ AssertUtil(GuildPermission.UseVAD, x => x.UseVAD, (p, enable) => p.Modify(useVoiceActivation: enable));
+ AssertUtil(GuildPermission.ChangeNickname, x => x.ChangeNickname, (p, enable) => p.Modify(changeNickname: enable));
+ AssertUtil(GuildPermission.ManageNicknames, x => x.ManageNicknames, (p, enable) => p.Modify(manageNicknames: enable));
+ AssertUtil(GuildPermission.ManageRoles, x => x.ManageRoles, (p, enable) => p.Modify(manageRoles: enable));
+ AssertUtil(GuildPermission.ManageWebhooks, x => x.ManageWebhooks, (p, enable) => p.Modify(manageWebhooks: enable));
+ AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojisAndStickers: enable));
+ AssertUtil(GuildPermission.UseApplicationCommands, x => x.UseApplicationCommands, (p, enable) => p.Modify(useApplicationCommands: enable));
+ AssertUtil(GuildPermission.RequestToSpeak, x => x.RequestToSpeak, (p, enable) => p.Modify(requestToSpeak: enable));
+ AssertUtil(GuildPermission.ManageEvents, x => x.ManageEvents, (p, enable) => p.Modify(manageEvents: enable));
+ AssertUtil(GuildPermission.ManageThreads, x => x.ManageThreads, (p, enable) => p.Modify(manageThreads: enable));
+ AssertUtil(GuildPermission.CreatePublicThreads, x => x.CreatePublicThreads, (p, enable) => p.Modify(createPublicThreads: enable));
+ AssertUtil(GuildPermission.CreatePrivateThreads, x => x.CreatePrivateThreads, (p, enable) => p.Modify(createPrivateThreads: enable));
+ AssertUtil(GuildPermission.UseExternalStickers, x => x.UseExternalStickers, (p, enable) => p.Modify(useExternalStickers: enable));
+ AssertUtil(GuildPermission.ModerateMembers, x => x.ModerateMembers, (p, enable) => p.Modify(moderateMembers: enable));
+ AssertUtil(GuildPermission.ViewMonetizationAnalytics, x => x.ViewMonetizationAnalytics, (p, enable) => p.Modify(viewMonetizationAnalytics: enable));
+ AssertUtil(GuildPermission.UseSoundboard, x => x.UseSoundboard, (p, enable) => p.Modify(useSoundboard: enable));
+ AssertUtil(GuildPermission.SendVoiceMessages, x => x.SendVoiceMessages, (p, enable) => p.Modify(sendVoiceMessages: enable));
+ AssertUtil(GuildPermission.UseClydeAI, x => x.UseClydeAI, (p, enable) => p.Modify(useClydeAI: enable));
+ AssertUtil(GuildPermission.CreateGuildExpressions, x => x.CreateGuildExpressions, (p, enable) => p.Modify(createGuildExpressions: enable));
+ AssertUtil(GuildPermission.SetVoiceChannelStatus, x => x.SetVoiceChannelStatus, (p, enable) => p.Modify(setVoiceChannelStatus: enable));
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs b/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs
new file mode 100644
index 0000000..b59b9f3
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ ///
+ /// Tests the methods provided in .
+ ///
+ public class MentionUtilsTests
+ {
+ ///
+ /// Tests
+ ///
+ [Fact]
+ public void MentionUser()
+ {
+ Assert.Equal("<@123>", MentionUtils.MentionUser(123u));
+ Assert.Equal("<@123>", MentionUtils.MentionUser("123"));
+ }
+ ///
+ /// Tests
+ ///
+ [Fact]
+ public void MentionChannel()
+ {
+ Assert.Equal("<#123>", MentionUtils.MentionChannel(123u));
+ Assert.Equal("<#123>", MentionUtils.MentionChannel("123"));
+ }
+ ///
+ /// Tests
+ ///
+ [Fact]
+ public void MentionRole()
+ {
+ Assert.Equal("<@&123>", MentionUtils.MentionRole(123u));
+ Assert.Equal("<@&123>", MentionUtils.MentionRole("123"));
+ }
+ [Theory]
+ [InlineData("<@!123>", 123)]
+ [InlineData("<@123>", 123)]
+ public void ParseUser_Pass(string user, ulong id)
+ {
+ var parsed = MentionUtils.ParseUser(user);
+ Assert.Equal(id, parsed);
+
+ Assert.True(MentionUtils.TryParseUser(user, out ulong result));
+ Assert.Equal(id, result);
+ }
+ [Theory]
+ [InlineData(" ")]
+ [InlineData("invalid")]
+ [InlineData("<12!3@>")]
+ [InlineData("<123>")]
+ public void ParseUser_Fail(string user)
+ {
+ Assert.Throws(() => MentionUtils.ParseUser(user));
+ Assert.False(MentionUtils.TryParseUser(user, out _));
+ }
+ [Fact]
+ public void ParseUser_Null()
+ {
+ Assert.Throws(() => MentionUtils.ParseUser(null));
+ Assert.Throws(() => MentionUtils.TryParseUser(null, out _));
+ }
+ [Theory]
+ [InlineData("<#123>", 123)]
+ public void ParseChannel_Pass(string channel, ulong id)
+ {
+ var parsed = MentionUtils.ParseChannel(channel);
+ Assert.Equal(id, parsed);
+
+ Assert.True(MentionUtils.TryParseChannel(channel, out ulong result));
+ Assert.Equal(id, result);
+ }
+ [Theory]
+ [InlineData(" ")]
+ [InlineData("invalid")]
+ [InlineData("<12#3>")]
+ [InlineData("<123>")]
+ public void ParseChannel_Fail(string channel)
+ {
+ Assert.Throws(() => MentionUtils.ParseChannel(channel));
+ Assert.False(MentionUtils.TryParseChannel(channel, out _));
+ }
+ [Fact]
+ public void ParseChannel_Null()
+ {
+ Assert.Throws(() => MentionUtils.ParseChannel(null));
+ Assert.Throws(() => MentionUtils.TryParseChannel(null, out _));
+ }
+ [Theory]
+ [InlineData("<@&123>", 123)]
+ public void ParseRole_Pass(string role, ulong id)
+ {
+ var parsed = MentionUtils.ParseRole(role);
+ Assert.Equal(id, parsed);
+
+ Assert.True(MentionUtils.TryParseRole(role, out ulong result));
+ Assert.Equal(id, result);
+ }
+ [Theory]
+ [InlineData(" ")]
+ [InlineData("invalid")]
+ [InlineData("<12@&3>")]
+ [InlineData("<123>")]
+ public void ParseRole_Fail(string role)
+ {
+ Assert.Throws(() => MentionUtils.ParseRole(role));
+ Assert.False(MentionUtils.TryParseRole(role, out _));
+ }
+ [Fact]
+ public void ParseRole_Null()
+ {
+ Assert.Throws(() => MentionUtils.ParseRole(null));
+ Assert.Throws(() => MentionUtils.TryParseRole(null, out _));
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MessageHelperTests.cs b/test/Discord.Net.Tests.Unit/MessageHelperTests.cs
new file mode 100644
index 0000000..42688ed
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/MessageHelperTests.cs
@@ -0,0 +1,118 @@
+using Discord.Rest;
+using Xunit;
+
+namespace Discord
+{
+ ///
+ /// Tests for parsing.
+ ///
+ public class MessageHelperTests
+ {
+ ///
+ /// Tests that no tags are parsed while in code blocks
+ /// or inline code.
+ ///
+ [Theory]
+ [InlineData("`@everyone`")]
+ [InlineData("`<@163184946742034432>`")]
+ [InlineData("```@everyone```")]
+ [InlineData("```cs \n @everyone```")]
+ [InlineData("```cs <@163184946742034432> ```")]
+ [InlineData("``` test ``` ```cs <@163184946742034432> ```")]
+ [InlineData("`<:test:537920404019216384>`")]
+ [InlineData("``` @everyone `")] // discord client handles these weirdly
+ [InlineData("``` @everyone ``")]
+ [InlineData("` @here `")]
+ [InlineData("` @everyone @here <@163184946742034432> <@&163184946742034432> <#163184946742034432> <:test:537920404019216384> `")]
+ public void ParseTagsInCode(string testData)
+ {
+ // don't care that I'm passing in null channels/guilds/users
+ // as they shouldn't be required
+ var result = MessageHelper.ParseTags(testData, null, null, null);
+ Assert.Empty(result);
+ }
+
+ /// Tests parsing tags that surround inline code or a code block.
+ [Theory]
+ [InlineData("`` <@&163184946742034432>")]
+ [InlineData("``` code block 1 ``` ``` code block 2 ``` <@&163184946742034432>")]
+ [InlineData("` code block 1 ``` ` code block 2 ``` <@&163184946742034432>")]
+ [InlineData("<@&163184946742034432> ``` code block 1 ```")]
+ [InlineData("``` code ``` ``` code ``` @here ``` code ``` ``` more ```")]
+ [InlineData("``` code ``` @here ``` more ```")]
+ public void ParseTagsAroundCode(string testData)
+ {
+ // don't care that I'm passing in null channels/guilds/users
+ // as they shouldn't be required
+ var result = MessageHelper.ParseTags(testData, null, null, null);
+ Assert.NotEmpty(result);
+ }
+
+ [Theory]
+ [InlineData(@"\` @everyone \`")]
+ [InlineData(@"\`\`\` @everyone \`\`\`")]
+ [InlineData(@"hey\`\`\`@everyone\`\`\`!!")]
+ public void IgnoreEscapedCodeBlocks(string testData)
+ {
+ var result = MessageHelper.ParseTags(testData, null, null, null);
+ Assert.NotEmpty(result);
+ }
+
+ // cannot test parsing a user, as it uses the ReadOnlyCollection arg.
+ // this could be done if mocked entities are merged in PR #1290
+
+ /// Tests parsing a mention of a role.
+ [Theory]
+ [InlineData("<@&163184946742034432>")]
+ [InlineData("**<@&163184946742034432>**")]
+ [InlineData("__<@&163184946742034432>__")]
+ [InlineData("<><@&163184946742034432>")]
+ public void ParseRole(string roleTag)
+ {
+ var result = MessageHelper.ParseTags(roleTag, null, null, null);
+ Assert.Contains(result, x => x.Type == TagType.RoleMention);
+ }
+
+ /// Tests parsing a channel.
+ [Theory]
+ [InlineData("<#429115823748284417>")]
+ [InlineData("**<#429115823748284417>**")]
+ [InlineData("<><#429115823748284417>")]
+ public void ParseChannel(string channelTag)
+ {
+ var result = MessageHelper.ParseTags(channelTag, null, null, null);
+ Assert.Contains(result, x => x.Type == TagType.ChannelMention);
+ }
+
+ /// Tests parsing an emoji.
+ [Theory]
+ [InlineData("<:test:537920404019216384>")]
+ [InlineData("**<:test:537920404019216384>**")]
+ [InlineData("<><:test:537920404019216384>")]
+ public void ParseEmoji(string emoji)
+ {
+ var result = MessageHelper.ParseTags(emoji, null, null, null);
+ Assert.Contains(result, x => x.Type == TagType.Emoji);
+ }
+
+ /// Tests parsing a mention of @everyone.
+ [Theory]
+ [InlineData("@everyone")]
+ [InlineData("**@everyone**")]
+ public void ParseEveryone(string everyone)
+ {
+ var result = MessageHelper.ParseTags(everyone, null, null, null);
+ Assert.Contains(result, x => x.Type == TagType.EveryoneMention);
+ }
+
+ /// Tests parsing a mention of @here.
+ [Theory]
+ [InlineData("@here")]
+ [InlineData("**@here**")]
+ public void ParseHere(string here)
+ {
+ var result = MessageHelper.ParseTags(here, null, null, null);
+ Assert.Contains(result, x => x.Type == TagType.HereMention);
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedCategoryChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedCategoryChannel.cs
new file mode 100644
index 0000000..5588bd6
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedCategoryChannel.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ internal sealed class MockedCategoryChannel : ICategoryChannel
+ {
+ public int Position => throw new NotImplementedException();
+
+ public IGuild Guild => throw new NotImplementedException();
+
+ public ulong GuildId => throw new NotImplementedException();
+
+ public IReadOnlyCollection PermissionOverwrites => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public DateTimeOffset CreatedAt => throw new NotImplementedException();
+
+ public ulong Id => throw new NotImplementedException();
+
+ public ChannelFlags Flags => throw new NotImplementedException();
+
+ public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public OverwritePermissions? GetPermissionOverwrite(IRole role)
+ {
+ throw new NotImplementedException();
+ }
+
+ public OverwritePermissions? GetPermissionOverwrite(IUser user)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ModifyAsync(Action func, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs
new file mode 100644
index 0000000..2a7f806
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedDMChannel.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ internal sealed class MockedDMChannel : IDMChannel
+ {
+ public IUser Recipient => throw new NotImplementedException();
+
+ public IReadOnlyCollection Recipients => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public DateTimeOffset CreatedAt => throw new NotImplementedException();
+
+ public ulong Id => throw new NotImplementedException();
+
+ public Task CloseAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IDisposable EnterTypingState(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task TriggerTypingAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs
new file mode 100644
index 0000000..8d91172
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs
@@ -0,0 +1,119 @@
+using Discord.Audio;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ internal sealed class MockedGroupChannel : IGroupChannel
+ {
+ public IReadOnlyCollection Recipients => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public DateTimeOffset CreatedAt => throw new NotImplementedException();
+
+ public ulong Id => throw new NotImplementedException();
+
+ public string RTCRegion => throw new NotImplementedException();
+
+ public Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false, bool disconnect = true)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DisconnectAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ModifyAsync(Action func, RequestOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IDisposable EnterTypingState(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task LeaveAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task TriggerTypingAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedInvalidChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedInvalidChannel.cs
new file mode 100644
index 0000000..362eeb9
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedInvalidChannel.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents a channel that is of an unrecognized type.
+ ///
+ internal sealed class MockedInvalidChannel : IChannel
+ {
+ public string Name => throw new NotImplementedException();
+
+ public DateTimeOffset CreatedAt => throw new NotImplementedException();
+
+ public ulong Id => throw new NotImplementedException();
+
+ public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
new file mode 100644
index 0000000..a9abe2e
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedTextChannel.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ internal sealed class MockedTextChannel : ITextChannel
+ {
+ public bool IsNsfw => throw new NotImplementedException();
+
+ public int DefaultSlowModeInterval => throw new NotImplementedException();
+
+ public ThreadArchiveDuration DefaultArchiveDuration => throw new NotImplementedException();
+
+ public string Topic => throw new NotImplementedException();
+
+ public int SlowModeInterval => throw new NotImplementedException();
+
+ public string Mention => throw new NotImplementedException();
+
+ public ulong? CategoryId => throw new NotImplementedException();
+
+ public int Position => throw new NotImplementedException();
+
+ public IGuild Guild => throw new NotImplementedException();
+
+ public ulong GuildId => throw new NotImplementedException();
+
+ public IReadOnlyCollection PermissionOverwrites => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public DateTimeOffset CreatedAt => throw new NotImplementedException();
+
+ public ulong Id => throw new NotImplementedException();
+
+ public ChannelFlags Flags => throw new NotImplementedException();
+
+ public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+ public Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
+ public Task CreateInviteToStreamAsync(IUser user, int? maxAge, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null)
+ => throw new NotImplementedException();
+
+ public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteMessageAsync(IMessage message, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IDisposable EnterTypingState(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetInvitesAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public OverwritePermissions? GetPermissionOverwrite(IRole role)
+ {
+ throw new NotImplementedException();
+ }
+
+ public OverwritePermissions? GetPermissionOverwrite(IUser user)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetPinnedMessagesAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetWebhooksAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ModifyAsync(Action func, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ModifyAsync(Action func, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SyncPermissionsAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task TriggerTypingAsync(RequestOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent component = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) => throw new NotImplementedException();
+ public Task> GetActiveThreadsAsync(RequestOptions options = null) => throw new NotImplementedException();
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
new file mode 100644
index 0000000..c2e34af
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedVoiceChannel.cs
@@ -0,0 +1,100 @@
+using Discord.Audio;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ internal sealed class MockedVoiceChannel : IVoiceChannel
+ {
+ public int DefaultSlowModeInterval => throw new NotImplementedException();
+
+ public int Bitrate => throw new NotImplementedException();
+
+ public int? UserLimit => throw new NotImplementedException();
+ public string Topic { get; }
+ public int SlowModeInterval { get; }
+ public ThreadArchiveDuration DefaultArchiveDuration { get; }
+ public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => throw new NotImplementedException();
+
+ public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+
+ public Task CreateThreadAsync(string name, ThreadType type = ThreadType.PublicThread, ThreadArchiveDuration autoArchiveDuration = ThreadArchiveDuration.OneDay, IMessage message = null, bool? invitable = null, int? slowmode = null, RequestOptions options = null) => throw new NotImplementedException();
+
+ public Task> GetActiveThreadsAsync(RequestOptions options = null) => throw new NotImplementedException();
+
+ public ulong? CategoryId => throw new NotImplementedException();
+
+ public int Position => throw new NotImplementedException();
+
+ public IGuild Guild => throw new NotImplementedException();
+
+ public ulong GuildId => throw new NotImplementedException();
+
+ public IReadOnlyCollection PermissionOverwrites => throw new NotImplementedException();
+
+ public string RTCRegion => throw new NotImplementedException();
+
+ public string Name => throw new NotImplementedException();
+
+ public DateTimeOffset CreatedAt => throw new NotImplementedException();
+
+ public ulong Id => throw new NotImplementedException();
+
+ public string Mention => throw new NotImplementedException();
+
+ public ChannelFlags Flags => throw new NotImplementedException();
+
+ public VideoQualityMode VideoQualityMode => throw new NotImplementedException();
+ public bool IsNsfw { get; }
+
+ public Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException();
+ public Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ConnectAsync(bool selfDeaf = false, bool selfMute = false, bool external = false, bool disconnect = true) => throw new NotImplementedException();
+ public Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task CreateInviteToApplicationAsync(ulong applicationId, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task CreateInviteToApplicationAsync(DefaultApplications application, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task CreateInviteToStreamAsync(IUser user, int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => throw new NotImplementedException();
+ public Task DisconnectAsync() => throw new NotImplementedException();
+ public IDisposable EnterTypingState(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public Task> GetInvitesAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = 100, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public OverwritePermissions? GetPermissionOverwrite(IRole role) => throw new NotImplementedException();
+ public OverwritePermissions? GetPermissionOverwrite(IUser user) => throw new NotImplementedException();
+ public Task> GetPinnedMessagesAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task SetStatusAsync(string status, RequestOptions options = null) => throw new NotImplementedException();
+
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyAsync(Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task ModifyMessageAsync(ulong messageId, Action func, RequestOptions options = null) => throw new NotImplementedException();
+ public Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) => throw new NotImplementedException();
+ public Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) => throw new NotImplementedException();
+ public Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFileAsync(FileAttachment attachment, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendFilesAsync(IEnumerable attachments, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null, MessageComponent components = null, ISticker[] stickers = null, Embed[] embeds = null, MessageFlags flags = MessageFlags.None) => throw new NotImplementedException();
+ public Task SyncPermissionsAsync(RequestOptions options = null) => throw new NotImplementedException();
+ public Task TriggerTypingAsync(RequestOptions options = null) => throw new NotImplementedException();
+ Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => throw new NotImplementedException();
+ IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => throw new NotImplementedException();
+ public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) => throw new NotImplementedException();
+
+ public Task GetWebhookAsync(ulong id, RequestOptions options = null) => throw new NotImplementedException();
+
+ public Task> GetWebhooksAsync(RequestOptions options = null) => throw new NotImplementedException();
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/Program.cs b/test/Discord.Net.Tests.Unit/Program.cs
deleted file mode 100644
index 3751555..0000000
--- a/test/Discord.Net.Tests.Unit/Program.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-// See https://aka.ms/new-console-template for more information
-Console.WriteLine("Hello, World!");
diff --git a/test/Discord.Net.Tests.Unit/SnowflakeUtilsTests.cs b/test/Discord.Net.Tests.Unit/SnowflakeUtilsTests.cs
new file mode 100644
index 0000000..f7cbf92
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/SnowflakeUtilsTests.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ public class SnowflakeUtilsTests
+ {
+ [Fact]
+ public void FromSnowflake()
+ {
+ // snowflake from a userid
+ var id = 163184946742034432u;
+ Assert.Equal(new DateTime(2016, 3, 26, 7, 18, 43), SnowflakeUtils.FromSnowflake(id).UtcDateTime, TimeSpan.FromSeconds(1));
+ }
+ [Fact]
+ public void ToSnowflake()
+ {
+ // most significant digits should match, but least significant digits cannot be determined from here
+ Assert.Equal(163184946184192000u, SnowflakeUtils.ToSnowflake(new DateTimeOffset(2016, 3, 26, 7, 18, 43, TimeSpan.Zero)));
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/TimeSpanTypeReaderTests.cs b/test/Discord.Net.Tests.Unit/TimeSpanTypeReaderTests.cs
new file mode 100644
index 0000000..f93579a
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/TimeSpanTypeReaderTests.cs
@@ -0,0 +1,71 @@
+using Discord.Commands;
+using System;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Discord
+{
+ public class TimeSpanTypeReaderTests
+ {
+ [Theory]
+ [InlineData("4d3h2m1s", false)] // tests format "%d'd'%h'h'%m'm'%s's'"
+ [InlineData("4d3h2m", false)] // tests format "%d'd'%h'h'%m'm'"
+ [InlineData("4d3h1s", false)] // tests format "%d'd'%h'h'%s's'"
+ [InlineData("4d3h", false)] // tests format "%d'd'%h'h'"
+ [InlineData("4d2m1s", false)] // tests format "%d'd'%m'm'%s's'"
+ [InlineData("4d2m", false)] // tests format "%d'd'%m'm'"
+ [InlineData("4d1s", false)] // tests format "%d'd'%s's'"
+ [InlineData("4d", false)] // tests format "%d'd'"
+ [InlineData("3h2m1s", false)] // tests format "%h'h'%m'm'%s's'"
+ [InlineData("3h2m", false)] // tests format "%h'h'%m'm'"
+ [InlineData("3h1s", false)] // tests format "%h'h'%s's'"
+ [InlineData("3h", false)] // tests format "%h'h'"
+ [InlineData("2m1s", false)] // tests format "%m'm'%s's'"
+ [InlineData("2m", false)] // tests format "%m'm'"
+ [InlineData("1s", false)] // tests format "%s's'"
+ // Negatives
+ [InlineData("-4d3h2m1s", true)] // tests format "-%d'd'%h'h'%m'm'%s's'"
+ [InlineData("-4d3h2m", true)] // tests format "-%d'd'%h'h'%m'm'"
+ [InlineData("-4d3h1s", true)] // tests format "-%d'd'%h'h'%s's'"
+ [InlineData("-4d3h", true)] // tests format "-%d'd'%h'h'"
+ [InlineData("-4d2m1s", true)] // tests format "-%d'd'%m'm'%s's'"
+ [InlineData("-4d2m", true)] // tests format "-%d'd'%m'm'"
+ [InlineData("-4d1s", true)] // tests format "-%d'd'%s's'"
+ [InlineData("-4d", true)] // tests format "-%d'd'"
+ [InlineData("-3h2m1s", true)] // tests format "-%h'h'%m'm'%s's'"
+ [InlineData("-3h2m", true)] // tests format "-%h'h'%m'm'"
+ [InlineData("-3h1s", true)] // tests format "-%h'h'%s's'"
+ [InlineData("-3h", true)] // tests format "-%h'h'"
+ [InlineData("-2m1s", true)] // tests format "-%m'm'%s's'"
+ [InlineData("-2m", true)] // tests format "-%m'm'"
+ [InlineData("-1s", true)] // tests format "-%s's'"
+ public async Task TestTimeSpanParse(string input, bool isNegative)
+ {
+ var reader = new TimeSpanTypeReader();
+ var result = await reader.ReadAsync(null, input, null);
+ Assert.True(result.IsSuccess);
+
+ var actual = (TimeSpan)result.BestMatch;
+ Assert.True(actual != TimeSpan.Zero);
+
+ if (isNegative)
+ {
+ Assert.True(actual < TimeSpan.Zero);
+
+ Assert.True(actual.Seconds == 0 || actual.Seconds == -1);
+ Assert.True(actual.Minutes == 0 || actual.Minutes == -2);
+ Assert.True(actual.Hours == 0 || actual.Hours == -3);
+ Assert.True(actual.Days == 0 || actual.Days == -4);
+ }
+ else
+ {
+ Assert.True(actual > TimeSpan.Zero);
+
+ Assert.True(actual.Seconds == 0 || actual.Seconds == 1);
+ Assert.True(actual.Minutes == 0 || actual.Minutes == 2);
+ Assert.True(actual.Hours == 0 || actual.Hours == 3);
+ Assert.True(actual.Days == 0 || actual.Days == 4);
+ }
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/TokenUtilsTests.cs b/test/Discord.Net.Tests.Unit/TokenUtilsTests.cs
new file mode 100644
index 0000000..4306fa9
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/TokenUtilsTests.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ ///
+ /// Tests for the methods.
+ ///
+ public class TokenUtilsTests
+ {
+ ///
+ /// Tests the usage of
+ /// to see that when a null, empty or whitespace-only string is passed as the token,
+ /// it will throw an ArgumentNullException.
+ ///
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")] // string.Empty isn't a constant type
+ [InlineData(" ")]
+ [InlineData(" ")]
+ [InlineData("\t")]
+ public void NullOrWhitespaceToken(string token)
+ {
+ // an ArgumentNullException should be thrown, regardless of the TokenType
+ Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bearer, token));
+ Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token));
+ Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Webhook, token));
+ }
+
+ ///
+ /// Tests the behavior of
+ /// to see that valid Webhook tokens do not throw Exceptions.
+ ///
+ ///
+ [Theory]
+ [InlineData("123123123")]
+ // bot token
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
+ // bearer token taken from discord docs
+ [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
+ // client secret
+ [InlineData("937it3ow87i4ery69876wqire")]
+ public void WebhookTokenDoesNotThrowExceptions(string token)
+ {
+ TokenUtils.ValidateToken(TokenType.Webhook, token);
+ }
+
+ // No tests for invalid webhook token behavior, because there is nothing there yet.
+
+ ///
+ /// Tests the behavior of
+ /// to see that valid Webhook tokens do not throw Exceptions.
+ ///
+ ///
+ [Theory]
+ [InlineData("123123123")]
+ // bot token
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
+ // bearer token taken from discord docs
+ [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
+ // client secret
+ [InlineData("937it3ow87i4ery69876wqire")]
+ public void BearerTokenDoesNotThrowExceptions(string token)
+ {
+ TokenUtils.ValidateToken(TokenType.Bearer, token);
+ }
+
+ // No tests for invalid bearer token behavior, because there is nothing there yet.
+
+ ///
+ /// Tests the behavior of
+ /// to see that valid Bot tokens do not throw Exceptions.
+ /// Valid Bot tokens can be strings of length 58 or above.
+ ///
+ [Theory]
+ // missing a single character from the end, 58 char. still should be valid
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")]
+ // 59 char token
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")]
+ public void BotTokenDoesNotThrowExceptions(string token)
+ {
+ // This example token is pulled from the Discord Docs
+ // https://discord.com/developers/docs/reference#authentication-example-bot-token-authorization-header
+ // should not throw any exception
+ TokenUtils.ValidateToken(TokenType.Bot, token);
+ }
+
+ ///
+ /// Tests the usage of with
+ /// a Bot token that is invalid.
+ ///
+ [Theory]
+ [InlineData("This is invalid")]
+ // bearer token
+ [InlineData("6qrZcUqja7812RVdnEKjpzOL4CvHBFG")]
+ // client secret
+ [InlineData("937it3ow87i4ery69876wqire")]
+ // 57 char bot token
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kK")]
+ // ends with invalid characters
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7k ")]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7k\n")]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7k\t")]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7k\r\n")]
+ // starts with invalid characters
+ [InlineData(" MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7k")]
+ [InlineData("\nMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7k")]
+ [InlineData("\tMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7k")]
+ [InlineData("\r\nMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7k")]
+ [InlineData("This is an invalid token, but it passes the check for string length.")]
+ // valid token, but passed in twice
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWsMTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")]
+ public void BotTokenInvalidThrowsArgumentException(string token)
+ {
+ Assert.Throws(() => TokenUtils.ValidateToken(TokenType.Bot, token));
+ }
+
+ ///
+ /// Tests the behavior of
+ /// to see that an is thrown when an invalid
+ /// is supplied as a parameter.
+ ///
+ ///
+ /// The type is treated as an invalid .
+ ///
+ [Theory]
+ // out of range TokenType
+ [InlineData(-1)]
+ [InlineData(4)]
+ [InlineData(7)]
+ public void UnrecognizedTokenType(int type)
+ {
+ Assert.Throws(() =>
+ TokenUtils.ValidateToken((TokenType)type, "MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs"));
+ }
+
+ ///
+ /// Checks the method for expected output.
+ ///
+ /// The Bot Token to test.
+ /// The expected result.
+ [Theory]
+ // this method only checks the first part of the JWT
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4..", true)]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kK", true)]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4. this part is invalid. this part is also invalid", true)]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.", false)]
+ [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4", false)]
+ [InlineData("NDI4NDc3OTQ0MDA5MTk1NTIw.xxxx.xxxxx", true)]
+ // should not throw an unexpected exception
+ [InlineData("", false)]
+ [InlineData(null, false)]
+ public void CheckBotTokenValidity(string token, bool expected)
+ {
+ Assert.Equal(expected, TokenUtils.CheckBotTokenValidity(token));
+ }
+
+ [Theory]
+ // cannot pass a ulong? as a param in InlineData, so have to have a separate param
+ // indicating if a value is null
+ [InlineData("NDI4NDc3OTQ0MDA5MTk1NTIw", false, 428477944009195520)]
+ // should return null w/o throwing other exceptions
+ [InlineData("", true, 0)]
+ [InlineData(" ", true, 0)]
+ [InlineData(null, true, 0)]
+ [InlineData("these chars aren't allowed @U#)*@#!)*", true, 0)]
+ public void DecodeBase64UserId(string encodedUserId, bool isNull, ulong expectedUserId)
+ {
+ var result = TokenUtils.DecodeBase64UserId(encodedUserId);
+ if (isNull)
+ Assert.Null(result);
+ else
+ Assert.Equal(expectedUserId, result);
+ }
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/TypeReaderTests.cs b/test/Discord.Net.Tests.Unit/TypeReaderTests.cs
new file mode 100644
index 0000000..59eb313
--- /dev/null
+++ b/test/Discord.Net.Tests.Unit/TypeReaderTests.cs
@@ -0,0 +1,142 @@
+using Discord.Commands;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Discord
+{
+ public sealed class TypeReaderTests
+ {
+ [Fact]
+ public async Task TestNamedArgumentReader()
+ {
+ using (var commands = new CommandService())
+ {
+ var module = await commands.AddModuleAsync(null);
+
+ Assert.NotNull(module);
+ Assert.NotEmpty(module.Commands);
+
+ var cmd = module.Commands[0];
+ Assert.NotNull(cmd);
+ Assert.NotEmpty(cmd.Parameters);
+
+ var param = cmd.Parameters[0];
+ Assert.NotNull(param);
+ Assert.True(param.IsRemainder);
+
+ var result = await param.ParseAsync(null, "bar: hello foo: 42");
+ Assert.True(result.IsSuccess);
+
+ var m = result.BestMatch as ArgumentType;
+ Assert.NotNull(m);
+ Assert.Equal(expected: 42, actual: m.Foo);
+ Assert.Equal(expected: "hello", actual: m.Bar);
+ }
+ }
+
+ [Fact]
+ public async Task TestQuotedArgumentValue()
+ {
+ using (var commands = new CommandService())
+ {
+ var module = await commands.AddModuleAsync(null);
+
+ Assert.NotNull(module);
+ Assert.NotEmpty(module.Commands);
+
+ var cmd = module.Commands[0];
+ Assert.NotNull(cmd);
+ Assert.NotEmpty(cmd.Parameters);
+
+ var param = cmd.Parameters[0];
+ Assert.NotNull(param);
+ Assert.True(param.IsRemainder);
+
+ var result = await param.ParseAsync(null, "foo: 42 bar: 《hello》");
+ Assert.True(result.IsSuccess);
+
+ var m = result.BestMatch as ArgumentType;
+ Assert.NotNull(m);
+ Assert.Equal(expected: 42, actual: m.Foo);
+ Assert.Equal(expected: "hello", actual: m.Bar);
+ }
+ }
+
+ [Fact]
+ public async Task TestNonPatternInput()
+ {
+ using (var commands = new CommandService())
+ {
+ var module = await commands.AddModuleAsync(null);
+
+ Assert.NotNull(module);
+ Assert.NotEmpty(module.Commands);
+
+ var cmd = module.Commands[0];
+ Assert.NotNull(cmd);
+ Assert.NotEmpty(cmd.Parameters);
+
+ var param = cmd.Parameters[0];
+ Assert.NotNull(param);
+ Assert.True(param.IsRemainder);
+
+ var result = await param.ParseAsync(null, "foobar");
+ Assert.False(result.IsSuccess);
+ Assert.Equal(expected: CommandError.Exception, actual: result.Error);
+ }
+ }
+
+ [Fact]
+ public async Task TestMultiple()
+ {
+ using (var commands = new CommandService())
+ {
+ var module = await commands.AddModuleAsync(null);
+
+ Assert.NotNull(module);
+ Assert.NotEmpty(module.Commands);
+
+ var cmd = module.Commands[0];
+ Assert.NotNull(cmd);
+ Assert.NotEmpty(cmd.Parameters);
+
+ var param = cmd.Parameters[0];
+ Assert.NotNull(param);
+ Assert.True(param.IsRemainder);
+
+ var result = await param.ParseAsync(null, "manyints: \"1, 2, 3, 4, 5, 6, 7\"");
+ Assert.True(result.IsSuccess);
+
+ var m = result.BestMatch as ArgumentType;
+ Assert.NotNull(m);
+ Assert.Equal(expected: new int[] { 1, 2, 3, 4, 5, 6, 7 }, actual: m.ManyInts);
+ }
+ }
+ }
+
+ [NamedArgumentType]
+ public sealed class ArgumentType
+ {
+ public int Foo { get; set; }
+
+ [OverrideTypeReader(typeof(CustomTypeReader))]
+ public string Bar { get; set; }
+
+ public IEnumerable ManyInts { get; set; }
+ }
+
+ public sealed class CustomTypeReader : TypeReader
+ {
+ public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services)
+ => Task.FromResult(TypeReaderResult.FromSuccess(input));
+ }
+
+ public sealed class TestModule : ModuleBase
+ {
+ [Command("test")]
+ public Task TestCommand(ArgumentType arg) => Task.Delay(0);
+ }
+}
diff --git a/test/Discord.Net.Tests/Tests.DiscordWebhookClient.cs b/test/Discord.Net.Tests/Tests.DiscordWebhookClient.cs
new file mode 100644
index 0000000..52c3900
--- /dev/null
+++ b/test/Discord.Net.Tests/Tests.DiscordWebhookClient.cs
@@ -0,0 +1,60 @@
+using Discord.Webhook;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace Discord
+{
+ ///
+ /// Tests the function.
+ ///
+ public class DiscordWebhookClientTests
+ {
+ [Theory]
+ [InlineData("https://discord.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
+ 123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
+ // ptb, canary, etc will have slightly different urls
+ [InlineData("https://ptb.discord.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
+ 123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
+ [InlineData("https://canary.discord.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
+ 123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
+ // don't care about https
+ [InlineData("http://canary.discord.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
+ 123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
+ // this is the minimum that the regex cares about
+ [InlineData("discord.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK",
+ 123412347732897802, "_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
+ public void TestWebhook_Valid(string webhookurl, ulong expectedId, string expectedToken)
+ {
+ DiscordWebhookClient.ParseWebhookUrl(webhookurl, out ulong id, out string token);
+
+ Assert.Equal(expectedId, id);
+ Assert.Equal(expectedToken, token);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData(null)]
+ public void TestWebhook_Null(string webhookurl)
+ {
+ Assert.Throws(() =>
+ {
+ DiscordWebhookClient.ParseWebhookUrl(webhookurl, out ulong id, out string token);
+ });
+ }
+
+ [Theory]
+ [InlineData("123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK")]
+ // trailing slash
+ [InlineData("https://discord.com/api/webhooks/123412347732897802/_abcde123456789-ABCDEFGHIJKLMNOP12345678-abcdefghijklmnopABCDEFGHIJK/")]
+ public void TestWebhook_Invalid(string webhookurl)
+ {
+ Assert.Throws(() =>
+ {
+ DiscordWebhookClient.ParseWebhookUrl(webhookurl, out ulong id, out string token);
+ });
+ }
+ }
+}