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); + }); + } + } +}