diff --git a/test/Discord.Net.Tests/Discord.Net.Tests.csproj b/test/Discord.Net.Tests/Discord.Net.Tests.csproj
index 2a50610cc..2968fc10e 100644
--- a/test/Discord.Net.Tests/Discord.Net.Tests.csproj
+++ b/test/Discord.Net.Tests/Discord.Net.Tests.csproj
@@ -1,97 +1,27 @@
-
-
+
- Debug
- AnyCPU
- {855D6B1D-847B-42DA-BE6A-23683EA89511}
- Library
- Properties
- Discord.Tests
- Discord.Net.Tests
- v4.6.1
- 512
- {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 10.0
- $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
- $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
- False
- UnitTest
-
+ Exe
+ netcoreapp1.1
+ $(PackageTargetFallback);portable-net45+win8+wp8+wpa81
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
- ..\..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll
- True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ PreserveNewest
+
-
+
+
+
-
- {c6a50d24-cbd3-4e76-852c-4dca60bbd608}
- Discord.Net.Net45
-
+
+
+
+
+
+
-
-
-
-
- False
-
-
- False
-
-
- False
-
-
- False
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/test/Discord.Net.Tests/Net/CacheInfo.cs b/test/Discord.Net.Tests/Net/CacheInfo.cs
new file mode 100644
index 000000000..ed2820b8e
--- /dev/null
+++ b/test/Discord.Net.Tests/Net/CacheInfo.cs
@@ -0,0 +1,12 @@
+using Newtonsoft.Json;
+
+namespace Discord.Net
+{
+ internal class CacheInfo
+ {
+ [JsonProperty("guild_id")]
+ public ulong? GuildId { get; set; }
+ [JsonProperty("version")]
+ public uint Version { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/test/Discord.Net.Tests/Net/CachedRestClient.cs b/test/Discord.Net.Tests/Net/CachedRestClient.cs
new file mode 100644
index 000000000..324510688
--- /dev/null
+++ b/test/Discord.Net.Tests/Net/CachedRestClient.cs
@@ -0,0 +1,120 @@
+using Akavache;
+using Akavache.Sqlite3;
+using Discord.Net.Rest;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Reactive.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Splat;
+using System.Reactive.Concurrency;
+
+namespace Discord.Net
+{
+ internal class CachedRestClient : IRestClient
+ {
+ private readonly Dictionary _headers;
+ private IBlobCache _blobCache;
+ private string _baseUrl;
+ private CancellationTokenSource _cancelTokenSource;
+ private CancellationToken _cancelToken, _parentToken;
+ private bool _isDisposed;
+
+ public CacheInfo Info { get; private set; }
+
+ public CachedRestClient()
+ {
+ _headers = new Dictionary();
+
+ _cancelTokenSource = new CancellationTokenSource();
+ _cancelToken = CancellationToken.None;
+ _parentToken = CancellationToken.None;
+
+ Locator.CurrentMutable.Register(() => Scheduler.Default, typeof(IScheduler), "Taskpool");
+ Locator.CurrentMutable.Register(() => new FilesystemProvider(), typeof(IFilesystemProvider), null);
+ Locator.CurrentMutable.Register(() => new HttpMixin(), typeof(IAkavacheHttpMixin), null);
+ //new Akavache.Sqlite3.Registrations().Register(Locator.CurrentMutable);
+ _blobCache = new SQLitePersistentBlobCache("cache.db");
+ }
+ private void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ _blobCache.Dispose();
+ _isDisposed = true;
+ }
+ }
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ public void SetUrl(string url)
+ {
+ _baseUrl = url;
+ }
+ public void SetHeader(string key, string value)
+ {
+ _headers[key] = value;
+ }
+ public void SetCancelToken(CancellationToken cancelToken)
+ {
+ _parentToken = cancelToken;
+ _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token;
+ }
+
+ public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly)
+ {
+ if (method != "GET")
+ throw new InvalidOperationException("This RestClient only supports GET requests.");
+
+ string uri = Path.Combine(_baseUrl, endpoint);
+ var bytes = await _blobCache.DownloadUrl(uri, _headers);
+ return new RestResponse(HttpStatusCode.OK, _headers, new MemoryStream(bytes));
+ }
+ public Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly)
+ {
+ throw new InvalidOperationException("This RestClient does not support payloads.");
+ }
+ public Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly)
+ {
+ throw new InvalidOperationException("This RestClient does not support multipart requests.");
+ }
+
+ public async Task ClearAsync()
+ {
+ await _blobCache.InvalidateAll();
+ }
+
+ public async Task LoadInfoAsync(ulong guildId)
+ {
+ if (Info != null)
+ return;
+
+ bool needsReset = false;
+ try
+ {
+ Info = await _blobCache.GetObject("info");
+ if (Info.GuildId != guildId)
+ needsReset = true;
+ }
+ catch (KeyNotFoundException)
+ {
+ needsReset = true;
+ }
+ if (needsReset)
+ {
+ Info = new CacheInfo() { GuildId = guildId, Version = 0 };
+ await SaveInfoAsync().ConfigureAwait(false);
+ }
+ }
+ public async Task SaveInfoAsync()
+ {
+ await ClearAsync().ConfigureAwait(false); //Version changed, invalidate cache
+ await _blobCache.InsertObject("info", Info);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Discord.Net.Tests/Net/FilesystemProvider.cs b/test/Discord.Net.Tests/Net/FilesystemProvider.cs
new file mode 100644
index 000000000..efa56ff66
--- /dev/null
+++ b/test/Discord.Net.Tests/Net/FilesystemProvider.cs
@@ -0,0 +1,124 @@
+using Akavache;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reactive;
+using System.Reactive.Concurrency;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Reflection;
+
+namespace Discord
+{
+ public class FilesystemProvider : IFilesystemProvider
+ {
+ public IObservable OpenFileForReadAsync(string path, IScheduler scheduler)
+ {
+ return SafeOpenFileAsync(path, FileMode.Open, FileAccess.Read, FileShare.Read, scheduler);
+ }
+
+ public IObservable OpenFileForWriteAsync(string path, IScheduler scheduler)
+ {
+ return SafeOpenFileAsync(path, FileMode.Create, FileAccess.Write, FileShare.None, scheduler);
+ }
+
+ public IObservable CreateRecursive(string path)
+ {
+ CreateRecursive(new DirectoryInfo(path));
+ return Observable.Return(Unit.Default);
+ }
+
+ public IObservable Delete(string path)
+ {
+ return Observable.Start(() => File.Delete(path), Scheduler.Default);
+ }
+
+ public string GetDefaultRoamingCacheDirectory()
+ {
+ throw new NotSupportedException();
+ }
+
+ public string GetDefaultSecretCacheDirectory()
+ {
+ throw new NotSupportedException();
+ }
+
+ public string GetDefaultLocalMachineCacheDirectory()
+ {
+ throw new NotSupportedException();
+ }
+
+ protected static string GetAssemblyDirectoryName()
+ {
+ var assemblyDirectoryName = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
+ Debug.Assert(assemblyDirectoryName != null, "The directory name of the assembly location is null");
+ return assemblyDirectoryName;
+ }
+
+ private static IObservable SafeOpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share, IScheduler scheduler = null)
+ {
+ scheduler = scheduler ?? Scheduler.Default;
+ var ret = new AsyncSubject();
+
+ Observable.Start(() =>
+ {
+ try
+ {
+ var createModes = new[]
+ {
+ FileMode.Create,
+ FileMode.CreateNew,
+ FileMode.OpenOrCreate,
+ };
+
+
+ // NB: We do this (even though it's incorrect!) because
+ // throwing lots of 1st chance exceptions makes debugging
+ // obnoxious, as well as a bug in VS where it detects
+ // exceptions caught by Observable.Start as Unhandled.
+ if (!createModes.Contains(mode) && !File.Exists(path))
+ {
+ ret.OnError(new FileNotFoundException());
+ return;
+ }
+
+ Observable.Start(() => new FileStream(path, mode, access, share, 4096, false), scheduler).Cast().Subscribe(ret);
+ }
+ catch (Exception ex)
+ {
+ ret.OnError(ex);
+ }
+ }, scheduler);
+
+ return ret;
+ }
+ private static void CreateRecursive(DirectoryInfo info)
+ {
+ SplitFullPath(info).Aggregate((parent, dir) =>
+ {
+ var path = Path.Combine(parent, dir);
+ if (!Directory.Exists(path))
+ Directory.CreateDirectory(path);
+ return path;
+ });
+ }
+
+ private static IEnumerable SplitFullPath(DirectoryInfo info)
+ {
+ var root = Path.GetPathRoot(info.FullName);
+ var components = new List();
+ for (var path = info.FullName; path != root && path != null; path = Path.GetDirectoryName(path))
+ {
+ var filename = Path.GetFileName(path);
+ if (String.IsNullOrEmpty(filename))
+ continue;
+ components.Add(filename);
+ }
+ components.Add(root);
+ components.Reverse();
+ return components;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Discord.Net.Tests/Net/HttpMixin.cs b/test/Discord.Net.Tests/Net/HttpMixin.cs
new file mode 100644
index 000000000..0c7bf2c3d
--- /dev/null
+++ b/test/Discord.Net.Tests/Net/HttpMixin.cs
@@ -0,0 +1,139 @@
+using Akavache;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Text;
+using System.Reactive;
+using System.Reactive.Threading.Tasks;
+
+namespace Discord.Net
+{
+ public class HttpMixin : IAkavacheHttpMixin
+ {
+ ///
+ /// Download data from an HTTP URL and insert the result into the
+ /// cache. If the data is already in the cache, this returns
+ /// a cached value. The URL itself is used as the key.
+ ///
+ /// The URL to download.
+ /// An optional Dictionary containing the HTTP
+ /// request headers.
+ /// Force a web request to always be issued, skipping the cache.
+ /// An optional expiration date.
+ /// The data downloaded from the URL.
+ public IObservable DownloadUrl(IBlobCache This, string url, IDictionary headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null)
+ {
+ return This.DownloadUrl(url, url, headers, fetchAlways, absoluteExpiration);
+ }
+
+ ///
+ /// Download data from an HTTP URL and insert the result into the
+ /// cache. If the data is already in the cache, this returns
+ /// a cached value. An explicit key is provided rather than the URL itself.
+ ///
+ /// The key to store with.
+ /// The URL to download.
+ /// An optional Dictionary containing the HTTP
+ /// request headers.
+ /// Force a web request to always be issued, skipping the cache.
+ /// An optional expiration date.
+ /// The data downloaded from the URL.
+ public IObservable DownloadUrl(IBlobCache This, string key, string url, IDictionary headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null)
+ {
+ var doFetch = MakeWebRequest(new Uri(url), headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration));
+ var fetchAndCache = doFetch.SelectMany(x => This.Insert(key, x, absoluteExpiration).Select(_ => x));
+
+ var ret = default(IObservable);
+ if (!fetchAlways)
+ {
+ ret = This.Get(key).Catch(fetchAndCache);
+ }
+ else
+ {
+ ret = fetchAndCache;
+ }
+
+ var conn = ret.PublishLast();
+ conn.Connect();
+ return conn;
+ }
+
+ IObservable ProcessWebResponse(WebResponse wr, string url, DateTimeOffset? absoluteExpiration)
+ {
+ var hwr = (HttpWebResponse)wr;
+ Debug.Assert(hwr != null, "The Web Response is somehow null but shouldn't be.");
+ if ((int)hwr.StatusCode >= 400)
+ {
+ return Observable.Throw(new WebException(hwr.StatusDescription));
+ }
+
+ var ms = new MemoryStream();
+ using (var responseStream = hwr.GetResponseStream())
+ {
+ Debug.Assert(responseStream != null, "The response stream is somehow null");
+ responseStream.CopyTo(ms);
+ }
+
+ var ret = ms.ToArray();
+ return Observable.Return(ret);
+ }
+
+ static IObservable MakeWebRequest(
+ Uri uri,
+ IDictionary headers = null,
+ string content = null,
+ int retries = 3,
+ TimeSpan? timeout = null)
+ {
+ IObservable request;
+
+ request = Observable.Defer(() =>
+ {
+ var hwr = CreateWebRequest(uri, headers);
+
+ if (content == null)
+ return Observable.FromAsyncPattern(hwr.BeginGetResponse, hwr.EndGetResponse)();
+
+ var buf = Encoding.UTF8.GetBytes(content);
+
+ // NB: You'd think that BeginGetResponse would never block,
+ // seeing as how it's asynchronous. You'd be wrong :-/
+ var ret = new AsyncSubject();
+ Observable.Start(() =>
+ {
+ Observable.FromAsyncPattern(hwr.BeginGetRequestStream, hwr.EndGetRequestStream)()
+ .SelectMany(x => WriteAsyncRx(x, buf, 0, buf.Length))
+ .SelectMany(_ => Observable.FromAsyncPattern(hwr.BeginGetResponse, hwr.EndGetResponse)())
+ .Multicast(ret).Connect();
+ }, BlobCache.TaskpoolScheduler);
+
+ return ret;
+ });
+
+ return request.Timeout(timeout ?? TimeSpan.FromSeconds(15), BlobCache.TaskpoolScheduler).Retry(retries);
+ }
+
+ private static WebRequest CreateWebRequest(Uri uri, IDictionary headers)
+ {
+ var hwr = WebRequest.Create(uri);
+ if (headers != null)
+ {
+ foreach (var x in headers)
+ {
+ hwr.Headers[x.Key] = x.Value;
+ }
+ }
+ return hwr;
+ }
+
+ private static IObservable WriteAsyncRx(Stream stream, byte[] data, int start, int length)
+ {
+ return stream.WriteAsync(data, start, length).ToObservable();
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Discord.Net.Tests/Properties/AssemblyInfo.cs b/test/Discord.Net.Tests/Properties/AssemblyInfo.cs
deleted file mode 100644
index 5b1c7b125..000000000
--- a/test/Discord.Net.Tests/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Discord.Net.Tests")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Discord.Net.Tests")]
-[assembly: AssemblyCopyright("Copyright © 2015")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("855d6b1d-847b-42da-be6a-23683ea89511")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/test/Discord.Net.Tests/TestConfig.cs b/test/Discord.Net.Tests/TestConfig.cs
new file mode 100644
index 000000000..21c2dcb8d
--- /dev/null
+++ b/test/Discord.Net.Tests/TestConfig.cs
@@ -0,0 +1,21 @@
+using Newtonsoft.Json;
+using System.IO;
+
+namespace Discord
+{
+ internal class TestConfig
+ {
+ [JsonProperty("token")]
+ public string Token { get; private set; }
+ [JsonProperty("guild_id")]
+ public ulong GuildId { get; private set; }
+
+ public static TestConfig LoadFile(string path)
+ {
+ using (var stream = new FileStream(path, FileMode.Open))
+ using (var reader = new StreamReader(stream))
+ using (var jsonReader = new JsonTextReader(reader))
+ return new JsonSerializer().Deserialize(jsonReader);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Discord.Net.Tests/Tests.Channels.cs b/test/Discord.Net.Tests/Tests.Channels.cs
new file mode 100644
index 000000000..1e707b329
--- /dev/null
+++ b/test/Discord.Net.Tests/Tests.Channels.cs
@@ -0,0 +1,142 @@
+using Discord.Rest;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Discord
+{
+ public partial class Tests
+ {
+ internal static async Task Migration_CreateTextChannels(DiscordRestClient client, RestGuild guild)
+ {
+ var text1 = await guild.GetDefaultChannelAsync();
+ var text2 = await guild.CreateTextChannelAsync("text2");
+ var text3 = await guild.CreateTextChannelAsync("text3");
+ var text4 = await guild.CreateTextChannelAsync("text4");
+ var text5 = await guild.CreateTextChannelAsync("text5");
+
+ //Modify #general
+ await text1.ModifyAsync(x =>
+ {
+ x.Name = "text1";
+ x.Position = 1;
+ x.Topic = "Topic1";
+ });
+
+ await text2.ModifyAsync(x =>
+ {
+ x.Position = 2;
+ });
+ await text3.ModifyAsync(x =>
+ {
+ x.Topic = "Topic2";
+ });
+ await text4.ModifyAsync(x =>
+ {
+ x.Position = 3;
+ x.Topic = "Topic2";
+ });
+ await text5.ModifyAsync(x =>
+ {
+ });
+
+ CheckTextChannels(guild, text1, text2, text3, text4, text5);
+ }
+ [Fact]
+ public async Task TestTextChannels()
+ {
+ CheckTextChannels(_guild, (await _guild.GetTextChannelsAsync()).ToArray());
+ }
+ private static void CheckTextChannels(RestGuild guild, params RestTextChannel[] textChannels)
+ {
+ Assert.Equal(textChannels.Length, 5);
+ Assert.All(textChannels, x =>
+ {
+ Assert.NotNull(x);
+ Assert.NotEqual(x.Id, 0UL);
+ Assert.True(x.Position >= 0);
+ });
+
+ var text1 = textChannels.Where(x => x.Name == "text1").FirstOrDefault();
+ var text2 = textChannels.Where(x => x.Name == "text2").FirstOrDefault();
+ var text3 = textChannels.Where(x => x.Name == "text3").FirstOrDefault();
+ var text4 = textChannels.Where(x => x.Name == "text4").FirstOrDefault();
+ var text5 = textChannels.Where(x => x.Name == "text5").FirstOrDefault();
+
+ Assert.NotNull(text1);
+ Assert.True(text1.Id == guild.DefaultChannelId);
+ Assert.Equal(text1.Position, 1);
+ Assert.Equal(text1.Topic, "Topic1");
+
+ Assert.NotNull(text2);
+ Assert.Equal(text2.Position, 2);
+ Assert.Null(text2.Topic);
+
+ Assert.NotNull(text3);
+ Assert.Equal(text3.Topic, "Topic2");
+
+ Assert.NotNull(text4);
+ Assert.Equal(text4.Position, 3);
+ Assert.Equal(text4.Topic, "Topic2");
+
+ Assert.NotNull(text5);
+ Assert.Null(text5.Topic);
+ }
+
+ internal static async Task Migration_CreateVoiceChannels(DiscordRestClient client, RestGuild guild)
+ {
+ var voice1 = await guild.CreateVoiceChannelAsync("voice1");
+ var voice2 = await guild.CreateVoiceChannelAsync("voice2");
+ var voice3 = await guild.CreateVoiceChannelAsync("voice3");
+
+ await voice1.ModifyAsync(x =>
+ {
+ x.Bitrate = 96000;
+ x.Position = 1;
+ x.UserLimit = 8;
+ });
+ await voice2.ModifyAsync(x =>
+ {
+ x.Bitrate = 64000;
+ x.Position = 1;
+ x.UserLimit = null;
+ });
+ await voice3.ModifyAsync(x =>
+ {
+ x.Bitrate = 8000;
+ x.Position = 1;
+ x.UserLimit = 16;
+ });
+
+ CheckVoiceChannels(guild, voice1, voice2, voice3);
+ }
+ [Fact]
+ public async Task TestVoiceChannels()
+ {
+ CheckVoiceChannels(_guild, (await _guild.GetVoiceChannelsAsync()).ToArray());
+ }
+ private static void CheckVoiceChannels(RestGuild guild, params RestVoiceChannel[] voiceChannels)
+ {
+ Assert.Equal(voiceChannels.Length, 3);
+
+ var voice1 = voiceChannels.Where(x => x.Name == "voice1").FirstOrDefault();
+ var voice2 = voiceChannels.Where(x => x.Name == "voice2").FirstOrDefault();
+ var voice3 = voiceChannels.Where(x => x.Name == "voice3").FirstOrDefault();
+
+ Assert.NotNull(voice1);
+ Assert.Equal(voice1.Bitrate, 96000);
+ Assert.Equal(voice1.Position, 1);
+ Assert.Equal(voice1.UserLimit, 8);
+
+ Assert.NotNull(voice2);
+ Assert.Equal(voice2.Bitrate, 64000);
+ Assert.Equal(voice2.Position, 1);
+ Assert.Equal(voice2.UserLimit, null);
+
+ Assert.NotNull(voice3);
+ Assert.Equal(voice3.Bitrate, 8000);
+ Assert.Equal(voice3.Position, 1);
+ Assert.Equal(voice3.UserLimit, 16);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Discord.Net.Tests/Tests.Migrations.cs b/test/Discord.Net.Tests/Tests.Migrations.cs
new file mode 100644
index 000000000..e786329cd
--- /dev/null
+++ b/test/Discord.Net.Tests/Tests.Migrations.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Threading.Tasks;
+using Discord.Rest;
+
+namespace Discord
+{
+ public partial class TestsFixture
+ {
+ public const uint MigrationCount = 3;
+
+ public async Task MigrateAsync()
+ {
+ DiscordRestClient client = null;
+ RestGuild guild = null;
+
+ await _cache.LoadInfoAsync(_config.GuildId).ConfigureAwait(false);
+ while (_cache.Info.Version != MigrationCount)
+ {
+ if (client == null)
+ {
+ client = new DiscordRestClient();
+ await client.LoginAsync(TokenType.Bot, _config.Token, false).ConfigureAwait(false);
+ guild = await client.GetGuildAsync(_config.GuildId);
+ }
+
+ uint nextVer = _cache.Info.Version + 1;
+ try
+ {
+ await DoMigrateAsync(client, guild, nextVer).ConfigureAwait(false);
+ _cache.Info.Version = nextVer;
+ await _cache.SaveInfoAsync().ConfigureAwait(false);
+ }
+ catch
+ {
+ await _cache.ClearAsync().ConfigureAwait(false);
+ throw;
+ }
+ }
+ }
+
+ private static Task DoMigrateAsync(DiscordRestClient client, RestGuild guild, uint toVersion)
+ {
+ switch (toVersion)
+ {
+ case 1: return Migration_WipeGuild(client, guild);
+ case 2: return Tests.Migration_CreateTextChannels(client, guild);
+ case 3: return Tests.Migration_CreateVoiceChannels(client, guild);
+ default: throw new InvalidOperationException("Unknown migration: " + toVersion);
+ }
+ }
+
+ private static async Task Migration_WipeGuild(DiscordRestClient client, RestGuild guild)
+ {
+ var textChannels = await guild.GetTextChannelsAsync();
+ var voiceChannels = await guild.GetVoiceChannelsAsync();
+ var roles = guild.Roles;
+
+ foreach (var channel in textChannels)
+ {
+ if (channel.Id != guild.DefaultChannelId)
+ await channel.DeleteAsync();
+ }
+ foreach (var channel in voiceChannels)
+ await channel.DeleteAsync();
+ foreach (var role in roles)
+ {
+ if (role.Id != guild.EveryoneRole.Id)
+ await role.DeleteAsync();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Discord.Net.Tests/Tests.cs b/test/Discord.Net.Tests/Tests.cs
index d8d09cd3d..df156d254 100644
--- a/test/Discord.Net.Tests/Tests.cs
+++ b/test/Discord.Net.Tests/Tests.cs
@@ -1,494 +1,53 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
+using Discord.Net;
+using Discord.Rest;
+using Xunit;
-namespace Discord.Tests
+namespace Discord
{
- //TODO: Tests are massively incomplete and out of date, needing a full rewrite
-
- [TestClass]
- public class Tests
+ public partial class TestsFixture : IDisposable
{
- private const int EventTimeout = 10000; //Max time in milliseconds to wait for an event response from our test actions
-
- private static DiscordSocketClient _hostBot, _targetBot, _observerBot;
- private static Guild _testGuild;
- private static TextChannel _testGuildChannel;
- private static Random _random;
- private static PublicInvite _testGuildInvite;
-
- private static TestContext _context;
-
- private static string _hostToken;
- private static string _observerToken;
- private static string _targetToken;
-
- private static string GetRandomText()
- {
- lock (_random)
- return $"test_{_random.Next()}";
- }
-
- #region Initialization
-
- [ClassInitialize]
- public static void Initialize(TestContext testContext)
- {
- _context = testContext;
-
- _hostToken = Environment.GetEnvironmentVariable("discord-unit-host_token");
- _observerToken = Environment.GetEnvironmentVariable("discord-unit-observer_token");
- _targetToken = Environment.GetEnvironmentVariable("discord-unit-target_token");
- }
-
- [TestMethod]
- [Priority(1)]
- public async Task TestInitialize()
- {
- _context.WriteLine("Initializing.");
-
- _random = new Random();
-
- _hostBot = new DiscordSocketClient(_hostToken);
- _targetBot = new DiscordSocketClient(_targetToken);
- _observerBot = new DiscordSocketClient(_observerToken);
-
- await _hostBot.Login();
-
- await Task.Delay(3000);
-
- //Cleanup existing Guilds
- (await _hostBot.GetGuilds()).Select(x => x.Owner.Id == _hostBot.CurrentUser.Id ? x.Delete() : x.Leave());
-
- //Create new Guild and invite the other bots to it
-
- _testGuild = await _hostBot.CreateGuild("Discord.Net Testing", _hostBot.GetOptimalVoiceRegion());
-
- await Task.Delay(1000);
-
- PublicInvite invite = await _testGuild.CreateInvite(60, 3, false, false);
- _testGuildInvite = invite;
-
- _context.WriteLine($"Host: {_hostBot.CurrentUser.Username} in {(await _hostBot.GetGuilds()).Count()}");
- }
-
- [TestMethod]
- [Priority(2)]
- public async Task TestTokenLogin_Ready()
- {
- AssertEvent(
- "READY never received",
- async () => await _observerBot.Login(),
- x => _observerBot.Connected += x,
- x => _observerBot.Connected -= x,
- null,
- true);
- (await _observerBot.GetGuilds()).Select(x => x.Owner.Id == _observerBot.CurrentUser.Id ? x.Delete() : x.Leave());
- await _observerBot.RestClient.Send(new API.Rest.AcceptInviteRequest(_testGuildInvite.Code));
- }
-
- [TestMethod]
- [Priority(2)]
- public async Task TestReady()
- {
- AssertEvent(
- "READY never received",
- async () => await _targetBot.Login(),
- x => _targetBot.Connected += x,
- x => _targetBot.Connected -= x,
- null,
- true);
-
- (await _targetBot.GetGuilds()).Select(x => x.Owner.Id == _targetBot.CurrentUser.Id ? x.Delete() : x.Leave());
- _testGuildChannel = _testGuild.DefaultChannel;
- }
-
- #endregion
+ private readonly TestConfig _config;
+ private readonly CachedRestClient _cache;
+ internal readonly DiscordRestClient _client;
+ internal readonly RestGuild _guild;
- // Guilds
-
- #region Guild Tests
-
- [TestMethod]
- [Priority(3)]
- public void TestJoinedGuild()
+ public TestsFixture()
{
- AssertEvent(
- "Never Got JoinedGuild",
- async () => await _targetBot.RestClient.Send(new API.Rest.AcceptInviteRequest(_testGuildInvite.Code)),
- x => _targetBot.JoinedGuild += x,
- x => _targetBot.JoinedGuild -= x);
- }
-
- #endregion
-
- #region Channel Tests
+ _cache = new CachedRestClient();
- //Channels
- [TestMethod]
- public void TestCreateTextChannel()
- {
- GuildChannel channel = null;
- string name = GetRandomText();
- AssertEvent(
- "ChannelCreated event never received",
- async () => channel = await _testGuild.CreateTextChannel(name),
- x => _targetBot.ChannelCreated += x,
- x => _targetBot.ChannelCreated -= x,
- (s, e) => e.Channel.Id == channel.Id);
-
- AssertEvent(
- "ChannelDestroyed event never received",
- async () => await channel.Delete(),
- x => _targetBot.ChannelDestroyed += x,
- x => _targetBot.ChannelDestroyed -= x,
- (s, e) => e.Channel.Id == channel.Id);
- }
- [TestMethod]
- public void TestCreateVoiceChannel()
- {
- GuildChannel channel = null;
- string name = GetRandomText();
- AssertEvent(
- "ChannelCreated event never received",
- async () => channel = await _testGuild.CreateVoiceChannel(name),
- x => _targetBot.ChannelCreated += x,
- x => _targetBot.ChannelCreated -= x,
- (s, e) => e.Channel.Id == channel.Id);
-
- AssertEvent(
- "ChannelDestroyed event never received",
- async () => await channel.Delete(),
- x => _targetBot.ChannelDestroyed += x,
- x => _targetBot.ChannelDestroyed -= x,
- (s, e) => e.Channel.Id == channel.Id);
- }
-
- [TestMethod]
- [ExpectedException(typeof(Net.HttpException))]
- public async Task TestCreateChannel_NoName()
- {
- await _testGuild.CreateTextChannel($"");
- }
- [TestMethod]
- public async Task Test_CreateGetChannel()
- {
- var name = GetRandomText();
- var channel = await _testGuild.CreateTextChannel(name);
- var get_channel = _testGuild.GetChannel(channel.Id);
- Assert.AreEqual(channel.Id, get_channel.Id, "ID of Channel and GetChannel were not equal.");
- }
- [TestMethod]
- public void TestSendTyping()
- {
- var channel = _testGuildChannel;
- AssertEvent(
- "UserUpdated event never fired.",
- async () => await channel.TriggerTyping(),
- x => _targetBot.UserIsTyping += x,
- x => _targetBot.UserIsTyping -= x);
- }
- [TestMethod]
- public void TestEditChannel()
- {
- var channel = _testGuildChannel;
- AssertEvent(
- "ChannelUpdated Never Received",
- async () => await channel.Modify(x =>
- {
- x.Name = GetRandomText();
- x.Topic = $"topic - {GetRandomText()}";
- x.Position = 26;
- }),
- x => _targetBot.ChannelUpdated += x,
- x => _targetBot.ChannelUpdated -= x);
- }
- [TestMethod]
- public void TestChannelMention()
- {
- var channel = _testGuildChannel;
- Assert.AreEqual($"<#{channel.Id}>", channel.Mention, "Generated channel mention was not the expected channel mention.");
- }
- [TestMethod]
- public void TestChannelUserCount()
- {
- Assert.AreEqual(3, _testGuildChannel.Users.Count(), "Read an incorrect number of users in a channel");
- }
-
- #endregion
-
- #region Message Tests
-
- //Messages
- [TestMethod]
- public async Task TestMessageEvents()
- {
- string name = GetRandomText();
- var channel = await _testGuild.CreateTextChannel(name);
- _context.WriteLine($"Channel Name: {channel.Name} / {channel.Guild.Name}");
- string text = GetRandomText();
- Message message = null;
- AssertEvent(
- "MessageCreated event never received",
- async () => message = await channel.SendMessage(text),
- x => _targetBot.MessageReceived += x,
- x => _targetBot.MessageReceived -= x,
- (s, e) => e.Message.Text == text);
-
- AssertEvent(
- "MessageUpdated event never received",
- async () => await message.Modify(x =>
- {
- x.Content = text + " updated";
- }),
- x => _targetBot.MessageUpdated += x,
- x => _targetBot.MessageUpdated -= x,
- (s, e) => e.Before.Text == text && e.After.Text == text + " updated");
-
- AssertEvent(
- "MessageDeleted event never received",
- async () => await message.Delete(),
- x => _targetBot.MessageDeleted += x,
- x => _targetBot.MessageDeleted -= x,
- (s, e) => e.Message.Id == message.Id);
- }
- [TestMethod]
- public async Task TestDownloadMessages()
- {
- string name = GetRandomText();
- var channel = await _testGuild.CreateTextChannel(name);
- for (var i = 0; i < 10; i++) await channel.SendMessage(GetRandomText());
- while (channel.Discord.MessageQueue.Count > 0) await Task.Delay(100);
- var messages = await channel.GetMessages(10);
- Assert.AreEqual(10, messages.Count(), "Expected 10 messages in downloaded array, did not see 10.");
- }
- [TestMethod]
- public async Task TestSendTTSMessage()
- {
- var channel = await _testGuild.CreateTextChannel(GetRandomText());
- AssertEvent(
- "MessageCreated event never fired",
- async () => await channel.SendMessage(GetRandomText(), true),
- x => _targetBot.MessageReceived += x,
- x => _targetBot.MessageReceived -= x,
- (s, e) => e.Message.IsTTS);
- }
-
- #endregion
-
- #region User Tests
-
- [TestMethod]
- public async Task TestUserMentions()
- {
- var user = (await _targetBot.GetGuild(_testGuild.Id)).CurrentUser;
- Assert.AreEqual($"<@{user.Id}>", user.Mention);
- }
- [TestMethod]
- public void TestUserEdit()
- {
- var user = _testGuild.GetUser(_targetBot.CurrentUser.Id);
- AssertEvent(
- "UserUpdated never fired",
- async () => await user.Modify(x =>
- {
- x.Deaf = true;
- x.Mute = true;
- }),
- x => _targetBot.UserUpdated += x,
- x => _targetBot.UserUpdated -= x);
- }
- [TestMethod]
- public void TestEditSelf()
- {
- throw new NotImplementedException();
- /*var name = RandomText
- AssertEvent(
- "UserUpdated never fired",
- async () => await _targetBot.CurrentUser.Modify(TargetPassword, name),
- x => _obGuildBot.UserUpdated += x,
- x => _obGuildBot.UserUpdated -= x,
- (s, e) => e.After.Username == name);*/
- }
- [TestMethod]
- public void TestSetStatus()
- {
- AssertEvent(
- "UserUpdated never fired",
- async () => await SetStatus(_targetBot, UserStatus.Idle),
- x => _observerBot.UserUpdated += x,
- x => _observerBot.UserUpdated -= x,
- (s, e) => e.After.Status == UserStatus.Idle);
- }
- private Task SetStatus(DiscordClient _client, UserStatus status)
- {
- throw new NotImplementedException();
- /*_client.SetStatus(status);
- await Task.Delay(50);*/
- }
- [TestMethod]
- public void TestSetGame()
- {
- AssertEvent(
- "UserUpdated never fired",
- async () => await SetGame(_targetBot, "test game"),
- x => _observerBot.UserUpdated += x,
- x => _observerBot.UserUpdated -= x,
- (s, e) => _targetBot.CurrentUser.CurrentGame == "test game");
-
- }
- private Task SetGame(DiscordClient _client, string game)
- {
- throw new NotImplementedException();
- //_client.SetGame(game);
- //await Task.Delay(5);
- }
-
- #endregion
-
- #region Permission Tests
-
- // Permissions
- [TestMethod]
- public async Task Test_AddGet_PermissionsRule()
- {
- var channel = await _testGuild.CreateTextChannel(GetRandomText());
- var user = _testGuild.GetUser(_targetBot.CurrentUser.Id);
- var perms = new OverwritePermissions(sendMessages: PermValue.Deny);
- await channel.UpdatePermissionOverwrite(user, perms);
- var resultPerms = channel.GetPermissionOverwrite(user);
- Assert.IsNotNull(resultPerms, "Perms retrieved from Guild were null.");
- }
- [TestMethod]
- public async Task Test_AddRemove_PermissionsRule()
- {
- var channel = await _testGuild.CreateTextChannel(GetRandomText());
- var user = _testGuild.GetUser(_targetBot.CurrentUser.Id);
- var perms = new OverwritePermissions(sendMessages: PermValue.Deny);
- await channel.UpdatePermissionOverwrite(user, perms);
- await channel.RemovePermissionOverwrite(user);
- await Task.Delay(200);
- Assert.AreEqual(PermValue.Inherit, channel.GetPermissionOverwrite(user)?.SendMessages);
- }
- [TestMethod]
- public async Task Test_Permissions_Event()
- {
- var channel = await _testGuild.CreateTextChannel(GetRandomText());
- var user = _testGuild.GetUser(_targetBot.CurrentUser.Id);
- var perms = new OverwritePermissions(sendMessages: PermValue.Deny);
- AssertEvent
- ("ChannelUpdatedEvent never fired.",
- async () => await channel.UpdatePermissionOverwrite(user, perms),
- x => _targetBot.ChannelUpdated += x,
- x => _targetBot.ChannelUpdated -= x,
- (s, e) => e.Channel == channel && (e.After as GuildChannel).PermissionOverwrites.Count() != (e.Before as GuildChannel).PermissionOverwrites.Count());
- }
- [TestMethod]
- [ExpectedException(typeof(Net.HttpException))]
- public async Task Test_Affect_Permissions_Invalid_Channel()
- {
- var channel = await _testGuild.CreateTextChannel(GetRandomText());
- var user = _testGuild.GetUser(_targetBot.CurrentUser.Id);
- var perms = new OverwritePermissions(sendMessages: PermValue.Deny);
- await channel.Delete();
- await channel.UpdatePermissionOverwrite(user, perms);
- }
-
- #endregion
-
-
- [ClassCleanup]
- public static async Task Cleanup()
- {
- WaitMany(
- (await _hostBot.GetGuilds()).Select(x => x.Owner.Id == _hostBot.CurrentUser.Id ? x.Delete() : x.Leave()),
- (await _targetBot.GetGuilds()).Select(x => x.Owner.Id == _targetBot.CurrentUser.Id ? x.Delete() : x.Leave()),
- (await _observerBot.GetGuilds()).Select(x => x.Owner.Id == _observerBot.CurrentUser.Id ? x.Delete() : x.Leave()));
-
- WaitAll(
- _hostBot.Disconnect(),
- _targetBot.Disconnect(),
- _observerBot.Disconnect());
- }
-
- #region Helpers
-
- // Task Helpers
-
- private static void AssertEvent(string msg, Func action, Action> addEvent, Action> removeEvent, Func