| @@ -1,97 +1,27 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
| <Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0"> | |||||
| <PropertyGroup> | <PropertyGroup> | ||||
| <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||||
| <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||||
| <ProjectGuid>{855D6B1D-847B-42DA-BE6A-23683EA89511}</ProjectGuid> | |||||
| <OutputType>Library</OutputType> | |||||
| <AppDesignerFolder>Properties</AppDesignerFolder> | |||||
| <RootNamespace>Discord.Tests</RootNamespace> | |||||
| <AssemblyName>Discord.Net.Tests</AssemblyName> | |||||
| <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | |||||
| <FileAlignment>512</FileAlignment> | |||||
| <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> | |||||
| <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> | |||||
| <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||||
| <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath> | |||||
| <IsCodedUITest>False</IsCodedUITest> | |||||
| <TestProjectType>UnitTest</TestProjectType> | |||||
| <TargetFrameworkProfile /> | |||||
| <OutputType>Exe</OutputType> | |||||
| <TargetFramework>netcoreapp1.1</TargetFramework> | |||||
| <PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81</PackageTargetFallback> | |||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |||||
| <DebugSymbols>true</DebugSymbols> | |||||
| <DebugType>full</DebugType> | |||||
| <Optimize>false</Optimize> | |||||
| <OutputPath>bin\Debug\</OutputPath> | |||||
| <DefineConstants>DEBUG;TRACE</DefineConstants> | |||||
| <ErrorReport>prompt</ErrorReport> | |||||
| <WarningLevel>4</WarningLevel> | |||||
| </PropertyGroup> | |||||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |||||
| <DebugType>pdbonly</DebugType> | |||||
| <Optimize>true</Optimize> | |||||
| <OutputPath>bin\Release\</OutputPath> | |||||
| <DefineConstants>TRACE</DefineConstants> | |||||
| <ErrorReport>prompt</ErrorReport> | |||||
| <WarningLevel>4</WarningLevel> | |||||
| </PropertyGroup> | |||||
| <ItemGroup> | |||||
| <Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | |||||
| <HintPath>..\..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath> | |||||
| <Private>True</Private> | |||||
| </Reference> | |||||
| <Reference Include="System" /> | |||||
| </ItemGroup> | |||||
| <Choose> | |||||
| <When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'"> | |||||
| <ItemGroup> | |||||
| <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> | |||||
| </ItemGroup> | |||||
| </When> | |||||
| <Otherwise> | |||||
| <ItemGroup> | |||||
| <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" /> | |||||
| </ItemGroup> | |||||
| </Otherwise> | |||||
| </Choose> | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <Compile Include="Tests.cs" /> | |||||
| <Compile Include="Properties\AssemblyInfo.cs" /> | |||||
| <Compile Include="**\*.cs" /> | |||||
| <EmbeddedResource Include="**\*.resx" /> | |||||
| <Content Include="*.dll"> | |||||
| <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |||||
| </Content> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <None Include="packages.config" /> | |||||
| <ProjectReference Include="../../src/Discord.Net.Commands/Discord.Net.Commands.csproj" /> | |||||
| <ProjectReference Include="../../src/Discord.Net.Core/Discord.Net.Core.csproj" /> | |||||
| <ProjectReference Include="../../src/Discord.Net.Rest/Discord.Net.Rest.csproj" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\..\src\Discord.Net\Discord.Net.Net45.csproj"> | |||||
| <Project>{c6a50d24-cbd3-4e76-852c-4dca60bbd608}</Project> | |||||
| <Name>Discord.Net.Net45</Name> | |||||
| </ProjectReference> | |||||
| <PackageReference Include="Akavache" Version="5.0.0" /> | |||||
| <PackageReference Include="Microsoft.NETCore.App" Version="1.1.0" /> | |||||
| <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20161123-03" /> | |||||
| <PackageReference Include="Newtonsoft.Json" Version="9.0.1" /> | |||||
| <PackageReference Include="xunit" Version="2.2.0-beta4-build3444" /> | |||||
| <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta4-build1194" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <Choose> | |||||
| <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> | |||||
| <ItemGroup> | |||||
| <Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
| <Private>False</Private> | |||||
| </Reference> | |||||
| <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
| <Private>False</Private> | |||||
| </Reference> | |||||
| <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
| <Private>False</Private> | |||||
| </Reference> | |||||
| <Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
| <Private>False</Private> | |||||
| </Reference> | |||||
| </ItemGroup> | |||||
| </When> | |||||
| </Choose> | |||||
| <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> | |||||
| <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||||
| <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||||
| Other similar extension points exist, see Microsoft.Common.targets. | |||||
| <Target Name="BeforeBuild"> | |||||
| </Target> | |||||
| <Target Name="AfterBuild"> | |||||
| </Target> | |||||
| --> | |||||
| </Project> | |||||
| </Project> | |||||
| @@ -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; } | |||||
| } | |||||
| } | |||||
| @@ -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<string, string> _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<string, string>(); | |||||
| _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<RestResponse> 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<RestResponse> SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly) | |||||
| { | |||||
| throw new InvalidOperationException("This RestClient does not support payloads."); | |||||
| } | |||||
| public Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> 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<CacheInfo>("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<CacheInfo>("info", Info); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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<Stream> OpenFileForReadAsync(string path, IScheduler scheduler) | |||||
| { | |||||
| return SafeOpenFileAsync(path, FileMode.Open, FileAccess.Read, FileShare.Read, scheduler); | |||||
| } | |||||
| public IObservable<Stream> OpenFileForWriteAsync(string path, IScheduler scheduler) | |||||
| { | |||||
| return SafeOpenFileAsync(path, FileMode.Create, FileAccess.Write, FileShare.None, scheduler); | |||||
| } | |||||
| public IObservable<Unit> CreateRecursive(string path) | |||||
| { | |||||
| CreateRecursive(new DirectoryInfo(path)); | |||||
| return Observable.Return(Unit.Default); | |||||
| } | |||||
| public IObservable<Unit> 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<Stream> SafeOpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share, IScheduler scheduler = null) | |||||
| { | |||||
| scheduler = scheduler ?? Scheduler.Default; | |||||
| var ret = new AsyncSubject<Stream>(); | |||||
| 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<Stream>().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<string> SplitFullPath(DirectoryInfo info) | |||||
| { | |||||
| var root = Path.GetPathRoot(info.FullName); | |||||
| var components = new List<string>(); | |||||
| 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; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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 | |||||
| { | |||||
| /// <summary> | |||||
| /// 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. | |||||
| /// </summary> | |||||
| /// <param name="url">The URL to download.</param> | |||||
| /// <param name="headers">An optional Dictionary containing the HTTP | |||||
| /// request headers.</param> | |||||
| /// <param name="fetchAlways">Force a web request to always be issued, skipping the cache.</param> | |||||
| /// <param name="absoluteExpiration">An optional expiration date.</param> | |||||
| /// <returns>The data downloaded from the URL.</returns> | |||||
| public IObservable<byte[]> DownloadUrl(IBlobCache This, string url, IDictionary<string, string> headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) | |||||
| { | |||||
| return This.DownloadUrl(url, url, headers, fetchAlways, absoluteExpiration); | |||||
| } | |||||
| /// <summary> | |||||
| /// 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. | |||||
| /// </summary> | |||||
| /// <param name="key">The key to store with.</param> | |||||
| /// <param name="url">The URL to download.</param> | |||||
| /// <param name="headers">An optional Dictionary containing the HTTP | |||||
| /// request headers.</param> | |||||
| /// <param name="fetchAlways">Force a web request to always be issued, skipping the cache.</param> | |||||
| /// <param name="absoluteExpiration">An optional expiration date.</param> | |||||
| /// <returns>The data downloaded from the URL.</returns> | |||||
| public IObservable<byte[]> DownloadUrl(IBlobCache This, string key, string url, IDictionary<string, string> 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<byte[]>); | |||||
| if (!fetchAlways) | |||||
| { | |||||
| ret = This.Get(key).Catch(fetchAndCache); | |||||
| } | |||||
| else | |||||
| { | |||||
| ret = fetchAndCache; | |||||
| } | |||||
| var conn = ret.PublishLast(); | |||||
| conn.Connect(); | |||||
| return conn; | |||||
| } | |||||
| IObservable<byte[]> 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<byte[]>(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<WebResponse> MakeWebRequest( | |||||
| Uri uri, | |||||
| IDictionary<string, string> headers = null, | |||||
| string content = null, | |||||
| int retries = 3, | |||||
| TimeSpan? timeout = null) | |||||
| { | |||||
| IObservable<WebResponse> request; | |||||
| request = Observable.Defer(() => | |||||
| { | |||||
| var hwr = CreateWebRequest(uri, headers); | |||||
| if (content == null) | |||||
| return Observable.FromAsyncPattern<WebResponse>(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<WebResponse>(); | |||||
| Observable.Start(() => | |||||
| { | |||||
| Observable.FromAsyncPattern<Stream>(hwr.BeginGetRequestStream, hwr.EndGetRequestStream)() | |||||
| .SelectMany(x => WriteAsyncRx(x, buf, 0, buf.Length)) | |||||
| .SelectMany(_ => Observable.FromAsyncPattern<WebResponse>(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<string, string> 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<Unit> WriteAsyncRx(Stream stream, byte[] data, int start, int length) | |||||
| { | |||||
| return stream.WriteAsync(data, start, length).ToObservable(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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")] | |||||
| @@ -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<TestConfig>(jsonReader); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,494 +1,53 @@ | |||||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | |||||
| using System; | 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<GuildEventArgs>( | |||||
| "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<ChannelEventArgs>( | |||||
| "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<ChannelEventArgs>( | |||||
| "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<ChannelEventArgs>( | |||||
| "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<ChannelEventArgs>( | |||||
| "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<TypingEventArgs>( | |||||
| "UserUpdated event never fired.", | |||||
| async () => await channel.TriggerTyping(), | |||||
| x => _targetBot.UserIsTyping += x, | |||||
| x => _targetBot.UserIsTyping -= x); | |||||
| } | |||||
| [TestMethod] | |||||
| public void TestEditChannel() | |||||
| { | |||||
| var channel = _testGuildChannel; | |||||
| AssertEvent<ChannelUpdatedEventArgs>( | |||||
| "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<MessageEventArgs>( | |||||
| "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<MessageUpdatedEventArgs>( | |||||
| "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<MessageEventArgs>( | |||||
| "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<MessageEventArgs>( | |||||
| "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<UserUpdatedEventArgs>( | |||||
| "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<UserUpdatedEventArgs>( | |||||
| "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<UserUpdatedEventArgs>( | |||||
| "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<UserUpdatedEventArgs>( | |||||
| "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<ChannelUpdatedEventArgs> | |||||
| ("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<TArgs>(string msg, Func<Task> action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test = null) | |||||
| { | |||||
| AssertEvent(msg, action, addEvent, removeEvent, test, true); | |||||
| } | |||||
| private static void AssertNoEvent<TArgs>(string msg, Func<Task> action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test = null) | |||||
| { | |||||
| AssertEvent(msg, action, addEvent, removeEvent, test, false); | |||||
| } | |||||
| private static void AssertEvent<TArgs>(string msg, Func<Task> action, Action<EventHandler<TArgs>> addEvent, Action<EventHandler<TArgs>> removeEvent, Func<object, TArgs, bool> test, bool assertTrue) | |||||
| { | |||||
| ManualResetEventSlim trigger = new ManualResetEventSlim(false); | |||||
| bool result = false; | |||||
| EventHandler<TArgs> handler = (s, e) => | |||||
| _config = TestConfig.LoadFile("./config.json"); | |||||
| var config = new DiscordRestConfig | |||||
| { | { | ||||
| if (test != null) | |||||
| RestClientProvider = url => | |||||
| { | { | ||||
| result |= test(s, e); | |||||
| trigger.Set(); | |||||
| _cache.SetUrl(url); | |||||
| return _cache; | |||||
| } | } | ||||
| else | |||||
| result = true; | |||||
| }; | }; | ||||
| _client = new DiscordRestClient(config); | |||||
| _client.LoginAsync(TokenType.Bot, _config.Token).Wait(); | |||||
| addEvent(handler); | |||||
| var task = action(); | |||||
| trigger.Wait(EventTimeout); | |||||
| task.Wait(); | |||||
| removeEvent(handler); | |||||
| Assert.AreEqual(assertTrue, result, msg); | |||||
| MigrateAsync().Wait(); | |||||
| _guild = _client.GetGuildAsync(_config.GuildId).Result; | |||||
| } | } | ||||
| private static void AssertEvent(string msg, Func<Task> action, Action<EventHandler> addEvent, Action<EventHandler> removeEvent, Func<object, bool> test, bool assertTrue) | |||||
| public void Dispose() | |||||
| { | { | ||||
| ManualResetEventSlim trigger = new ManualResetEventSlim(false); | |||||
| bool result = false; | |||||
| EventHandler handler = (s, e) => | |||||
| { | |||||
| if (test != null) | |||||
| { | |||||
| result |= test(s); | |||||
| trigger.Set(); | |||||
| } | |||||
| else | |||||
| result = true; | |||||
| }; | |||||
| addEvent(handler); | |||||
| var task = action(); | |||||
| trigger.Wait(EventTimeout); | |||||
| task.Wait(); | |||||
| removeEvent(handler); | |||||
| Assert.AreEqual(assertTrue, result, msg); | |||||
| _client.Dispose(); | |||||
| _cache.Dispose(); | |||||
| } | } | ||||
| } | |||||
| private static void WaitAll(params Task[] tasks) | |||||
| { | |||||
| Task.WaitAll(tasks); | |||||
| } | |||||
| private static void WaitAll(IEnumerable<Task> tasks) | |||||
| { | |||||
| Task.WaitAll(tasks.ToArray()); | |||||
| } | |||||
| private static void WaitMany(params IEnumerable<Task>[] tasks) | |||||
| public partial class Tests : IClassFixture<TestsFixture> | |||||
| { | |||||
| private DiscordRestClient _client; | |||||
| private RestGuild _guild; | |||||
| public Tests(TestsFixture fixture) | |||||
| { | { | ||||
| Task.WaitAll(tasks.Where(x => x != null).SelectMany(x => x).ToArray()); | |||||
| _client = fixture._client; | |||||
| _guild = fixture._guild; | |||||
| } | } | ||||
| #endregion | |||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -1,14 +0,0 @@ | |||||
| { | |||||
| "user1": { | |||||
| "email": "user1@example.com", | |||||
| "password": "password123" | |||||
| }, | |||||
| "user2": { | |||||
| "email": "user2@example.com", | |||||
| "password": "password456" | |||||
| }, | |||||
| "user3": { | |||||
| "email": "user3@example.com", | |||||
| "password": "password789" | |||||
| } | |||||
| } | |||||
| @@ -1,4 +0,0 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <packages> | |||||
| <package id="Newtonsoft.Json" version="8.0.2" targetFramework="net45" /> | |||||
| </packages> | |||||