refactor: reorganize solution for better multi-project support/management
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
[*.cs]
|
||||
dotnet_diagnostic.CA1515.severity = none
|
||||
dotnet_diagnostic.CA1707.severity = none
|
||||
dotnet_diagnostic.CA2201.severity = none
|
||||
dotnet_diagnostic.CA2007.severity = none
|
||||
dotnet_diagnostic.CA1303.severity = none
|
||||
dotnet_diagnostic.CA1031.severity = none
|
||||
dotnet_diagnostic.CA1034.severity = none
|
||||
dotnet_diagnostic.CA1054.severity = none
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project>
|
||||
|
||||
<Import
|
||||
Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<Using Include="FluentAssertions" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
|
||||
<PackageVersion Include="FluentAssertions" Version="7.2.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.5" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageVersion Include="moq" Version="4.20.72" />
|
||||
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,37 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.msbuild">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="moq" />
|
||||
<PackageReference Include="RichardSzalay.MockHttp" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\StevesBot.Webhook\StevesBot.Webhook.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CollectCoverage>true</CollectCoverage>
|
||||
<CoverletOutput>./TestResults/Coverage/</CoverletOutput>
|
||||
<CoverletOutputFormat>cobertura</CoverletOutputFormat>
|
||||
<Include>[StevesBot.Webhook]*</Include>
|
||||
<ExcludeByFile>**/Program.cs,**/Worker.cs</ExcludeByFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="GenerateHtmlCoverageReport" AfterTargets="GenerateCoverageResultAfterTest">
|
||||
<Exec Command="reportgenerator -reports:./TestResults/Coverage/*.xml -targetdir:./TestResults/Coverage/Report/ -reporttypes:Html_Dark" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace StevesBot.Webhook.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using WebSocket = System.Net.WebSockets.WebSocket;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Integration.Infrastructure;
|
||||
|
||||
public sealed class TestWebSocketServer : IAsyncLifetime, IDisposable
|
||||
{
|
||||
private readonly IWebHost _host;
|
||||
private readonly CancellationTokenSource _echoCts = new();
|
||||
private Uri? _webSocketUri;
|
||||
|
||||
public Uri Uri
|
||||
{
|
||||
get
|
||||
{
|
||||
_webSocketUri ??= GetWebSocketUri();
|
||||
return _webSocketUri;
|
||||
}
|
||||
}
|
||||
|
||||
public TestWebSocketServer()
|
||||
{
|
||||
_host = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseWebSockets();
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
if (context.Request.Path == "/ws" && context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
|
||||
await Echo(webSocket, _echoCts.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
}
|
||||
});
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseUrls("http://[::1]:0")
|
||||
.Build();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await _host.StartAsync();
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await _echoCts.CancelAsync();
|
||||
await _host.StopAsync();
|
||||
|
||||
_echoCts.Dispose();
|
||||
_host.Dispose();
|
||||
}
|
||||
|
||||
private Uri GetWebSocketUri()
|
||||
{
|
||||
var address = _host.ServerFeatures.GetRequiredFeature<IServerAddressesFeature>().Addresses.First();
|
||||
var webSocketAddress = address.Replace("http://", "ws://", StringComparison.OrdinalIgnoreCase);
|
||||
return new Uri(webSocketAddress + "/ws");
|
||||
}
|
||||
|
||||
private static async Task Echo(WebSocket webSocket, CancellationToken cancellationToken)
|
||||
{
|
||||
var buffer = new byte[1024 * 4];
|
||||
|
||||
try
|
||||
{
|
||||
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
|
||||
|
||||
while (result.CloseStatus.HasValue is false && cancellationToken.IsCancellationRequested is false)
|
||||
{
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken);
|
||||
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
|
||||
}
|
||||
|
||||
if (result.CloseStatus.HasValue && cancellationToken.IsCancellationRequested is false)
|
||||
{
|
||||
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
|
||||
{
|
||||
Console.WriteLine($"{nameof(TestWebSocketServer)}: Client connection closed prematurely.");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine($"{nameof(TestWebSocketServer)}: Echo operation cancelled (server shutting down).");
|
||||
|
||||
if (webSocket.State is WebSocketState.Open or WebSocketState.CloseReceived)
|
||||
{
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "Server shutting down", CancellationToken.None);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{nameof(TestWebSocketServer)}: Error during echo: {ex.Message}");
|
||||
|
||||
if (webSocket.State is WebSocketState.Open or WebSocketState.CloseReceived)
|
||||
{
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, "Server error", CancellationToken.None);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
webSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_host.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using WebSocket = StevesBot.Worker.WebSockets.WebSocket;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Integration;
|
||||
|
||||
public sealed class WebSocketTests : IClassFixture<TestWebSocketServer>, IDisposable
|
||||
{
|
||||
private readonly WebSocket _client = new();
|
||||
private readonly TestWebSocketServer _server;
|
||||
|
||||
public WebSocketTests(TestWebSocketServer server)
|
||||
{
|
||||
_server = server;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WebSocket_WhenUsed_ItShouldProperlyConnectSendMsgReceiveMsgAndClose()
|
||||
{
|
||||
await _client.ConnectAsync(_server.Uri, CancellationToken.None);
|
||||
|
||||
var message = "Hello, WebSocket!";
|
||||
var buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(message));
|
||||
await _client.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
|
||||
var receivedBuffer = new ArraySegment<byte>(new byte[buffer.Count]);
|
||||
var receivedMessage = await _client.ReceiveAsync(receivedBuffer, CancellationToken.None);
|
||||
|
||||
receivedMessage.MessageType.Should().Be(WebSocketMessageType.Text);
|
||||
receivedMessage.EndOfMessage.Should().BeTrue();
|
||||
receivedMessage.Count.Should().Be(buffer.Count);
|
||||
|
||||
var receivedString = Encoding.UTF8.GetString([.. receivedBuffer], 0, receivedMessage.Count);
|
||||
receivedString.Should().Be(message);
|
||||
|
||||
await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Test complete", CancellationToken.None);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.msbuild">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="moq" />
|
||||
<PackageReference Include="RichardSzalay.MockHttp" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CollectCoverage>true</CollectCoverage>
|
||||
<CoverletOutput>./TestResults/Coverage/</CoverletOutput>
|
||||
<CoverletOutputFormat>cobertura</CoverletOutputFormat>
|
||||
<Include>[StevesBot.Worker]*</Include>
|
||||
<ExcludeByFile>**/Program.cs,**/Worker.cs</ExcludeByFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="GenerateHtmlCoverageReport" AfterTargets="GenerateCoverageResultAfterTest">
|
||||
<Exec
|
||||
Command="reportgenerator -reports:./TestResults/Coverage/*.xml -targetdir:./TestResults/Coverage/Report/ -reporttypes:Html_Dark" />
|
||||
</Target>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\StevesBot.Worker\StevesBot.Worker.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class ActivityTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldCreateAnInstance()
|
||||
{
|
||||
var activity = new Activity();
|
||||
|
||||
activity.Name.Should().Be(string.Empty);
|
||||
activity.Type.Should().Be(ActivityType.Custom);
|
||||
activity.State.Should().BeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class ActivityTypeTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public void ActivityType_WhenCalled_ItShouldReturnExpectedResult(int activityType, int expected)
|
||||
{
|
||||
activityType.Should().Be(expected);
|
||||
}
|
||||
|
||||
public static TheoryData<int, int> TestData => new()
|
||||
{
|
||||
{
|
||||
ActivityType.Custom,
|
||||
4
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class AsyncLockTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task LockAsync_WhenCalled_ItShouldReturnsNonNullRelease()
|
||||
{
|
||||
using var asyncLock = new AsyncLock();
|
||||
var releaser = await asyncLock.LockAsync();
|
||||
|
||||
releaser.Should().NotBeNull();
|
||||
releaser.Should().BeAssignableTo<IDisposable>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockAsync_WhenLockIsAcquired_ItShouldForceSubsequentCallsToWait()
|
||||
{
|
||||
using var asyncLock = new AsyncLock();
|
||||
var lockOneAcquired = new TaskCompletionSource<bool>();
|
||||
var lockTwoAttempted = new TaskCompletionSource<bool>();
|
||||
var lockTwoAcquired = new TaskCompletionSource<bool>();
|
||||
|
||||
async Task FirstLockAction()
|
||||
{
|
||||
using (await asyncLock.LockAsync())
|
||||
{
|
||||
lockOneAcquired.SetResult(true);
|
||||
await lockTwoAttempted.Task;
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
async Task SecondLockAction()
|
||||
{
|
||||
await lockOneAcquired.Task;
|
||||
lockTwoAttempted.SetResult(true);
|
||||
using (await asyncLock.LockAsync())
|
||||
{
|
||||
lockTwoAcquired.SetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
var taskOne = FirstLockAction();
|
||||
var taskTwo = SecondLockAction();
|
||||
|
||||
await Task.WhenAll(taskOne, Task.WhenAny(taskTwo, Task.Delay(500)));
|
||||
|
||||
lockOneAcquired.Task.IsCompleted.Should().BeTrue();
|
||||
lockTwoAttempted.Task.IsCompleted.Should().BeTrue();
|
||||
lockTwoAcquired.Task.IsCompleted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockAsync_WhenReleaserIsDisposed_ItShouldAllowAnotherLockAcquisition()
|
||||
{
|
||||
using var asyncLock = new AsyncLock();
|
||||
|
||||
var releaser = await asyncLock.LockAsync();
|
||||
releaser.Dispose();
|
||||
|
||||
IDisposable? newReleaser = null;
|
||||
var act = async () => newReleaser = await asyncLock.LockAsync();
|
||||
|
||||
await act.Should().NotThrowAsync();
|
||||
newReleaser.Should().NotBeNull();
|
||||
newReleaser.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockAsync_WhenCalledWithCancellationTokenAndTokenIsCancelled_ItShouldThrowOperationCanceledException()
|
||||
{
|
||||
using var asyncLock = new AsyncLock();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
var initialReleaser = await asyncLock.LockAsync();
|
||||
|
||||
async Task<IDisposable> Act()
|
||||
{
|
||||
return await asyncLock.LockAsync(cts.Token);
|
||||
}
|
||||
|
||||
var lockTask = Act();
|
||||
await Task.Delay(100);
|
||||
await cts.CancelAsync();
|
||||
|
||||
var act = () => lockTask;
|
||||
await act.Should().ThrowAsync<OperationCanceledException>();
|
||||
|
||||
initialReleaser.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockAsync_WhenCalledWithAlreadyCancelledToken_ItShouldThrowOperationCanceledException()
|
||||
{
|
||||
using var asyncLock = new AsyncLock();
|
||||
using var cts = new CancellationTokenSource();
|
||||
await cts.CancelAsync();
|
||||
|
||||
var act = async () => await asyncLock.LockAsync(cts.Token);
|
||||
|
||||
await act.Should().ThrowAsync<OperationCanceledException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Dispose_WhenCalled_ItShouldDisposeSemaphore()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
|
||||
asyncLock.Dispose();
|
||||
|
||||
var act = () => asyncLock.LockAsync();
|
||||
|
||||
await act.Should().ThrowAsync<ObjectDisposedException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WhenCalledMultipleTimes_ItShouldNotThrow()
|
||||
{
|
||||
using var asyncLock = new AsyncLock();
|
||||
|
||||
var act = () =>
|
||||
{
|
||||
asyncLock.Dispose();
|
||||
asyncLock.Dispose();
|
||||
};
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockAsync_WhenCalledAfterDispose_ItShouldThrowObjectDisposedException()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
asyncLock.Dispose();
|
||||
|
||||
var act = () => asyncLock.LockAsync();
|
||||
|
||||
await act.Should().ThrowAsync<ObjectDisposedException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockAsync_WhenCalled_ItShouldOnlyAllowConcurrentAccessToOneThread()
|
||||
{
|
||||
using var asyncLock = new AsyncLock();
|
||||
var concurrentAccessCount = 0;
|
||||
var maxConcurrentAccessCount = 0;
|
||||
const int numberOfTasks = 10;
|
||||
var tasks = new Task[numberOfTasks];
|
||||
|
||||
for (var i = 0; i < numberOfTasks; i++)
|
||||
{
|
||||
tasks[i] = Task.Run(async () =>
|
||||
{
|
||||
using (await asyncLock.LockAsync())
|
||||
{
|
||||
Interlocked.Increment(ref concurrentAccessCount);
|
||||
maxConcurrentAccessCount = Math.Max(maxConcurrentAccessCount, concurrentAccessCount);
|
||||
await Task.Delay(20);
|
||||
Interlocked.Decrement(ref concurrentAccessCount);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
maxConcurrentAccessCount.Should().Be(1);
|
||||
concurrentAccessCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LockAsync_WhenCalled_ItShouldPreventRaceConditionsInHighContentionScenario()
|
||||
{
|
||||
using var asyncLock = new AsyncLock();
|
||||
var sharedResource = 0;
|
||||
const int numberOfIterations = 10;
|
||||
const int numberOfTasks = 10;
|
||||
var tasks = new Task[numberOfTasks];
|
||||
|
||||
for (var i = 0; i < numberOfTasks; i++)
|
||||
{
|
||||
tasks[i] = Task.Run(async () =>
|
||||
{
|
||||
for (var j = 0; j < numberOfIterations; j++)
|
||||
{
|
||||
using (await asyncLock.LockAsync())
|
||||
{
|
||||
var temp = sharedResource;
|
||||
await Task.Delay(1);
|
||||
sharedResource = temp + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
sharedResource.Should().Be(numberOfTasks * numberOfIterations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class CreateMessageRequestTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var content = "content";
|
||||
var messageReference = new DiscordMessageReference(
|
||||
1,
|
||||
"message_id",
|
||||
"channel_id",
|
||||
"guild_id",
|
||||
false
|
||||
);
|
||||
|
||||
var result = new CreateMessageRequest(content, messageReference);
|
||||
|
||||
result.Content.Should().Be(content);
|
||||
result.MessageReference.Should().BeSameAs(messageReference);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordClientOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithoutParameters_ItShouldCreateInstance()
|
||||
{
|
||||
var options = new DiscordClientOptions();
|
||||
|
||||
options.Should().NotBeNull();
|
||||
options.Should().BeOfType<DiscordClientOptions>();
|
||||
options.ApiUrl.Should().Be(string.Empty);
|
||||
options.AppToken.Should().Be(string.Empty);
|
||||
options.Intents.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithParameters_ItShouldCreateInstance()
|
||||
{
|
||||
var apiUrl = "https://api.example.com";
|
||||
var appToken = "test-token";
|
||||
var intents = 123;
|
||||
|
||||
var options = new DiscordClientOptions
|
||||
{
|
||||
ApiUrl = apiUrl,
|
||||
AppToken = appToken,
|
||||
Intents = intents
|
||||
};
|
||||
|
||||
options.Should().NotBeNull();
|
||||
options.Should().BeOfType<DiscordClientOptions>();
|
||||
options.ApiUrl.Should().Be(apiUrl);
|
||||
options.AppToken.Should().Be(appToken);
|
||||
options.Intents.Should().Be(intents);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordCloseCodesTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(4004, false)]
|
||||
[InlineData(4010, false)]
|
||||
[InlineData(4011, false)]
|
||||
[InlineData(4012, false)]
|
||||
[InlineData(4013, false)]
|
||||
[InlineData(4014, false)]
|
||||
[InlineData(null, true)]
|
||||
[InlineData(1000, true)]
|
||||
public void IsReconnectable_WhenCalledWithCloseCode_ItShouldReturnExpectedResult(int? closeCode, bool expected)
|
||||
{
|
||||
var result = DiscordCloseCodes.IsReconnectable(closeCode);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordEventConverterTests
|
||||
{
|
||||
private readonly JsonSerializerOptions _options = new()
|
||||
{
|
||||
ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
Converters =
|
||||
{
|
||||
new DiscordEventConverter()
|
||||
}
|
||||
};
|
||||
private readonly Type _discordEventType = typeof(DiscordEvent);
|
||||
private readonly DiscordEventConverter _converter = new();
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public void Read_WhenCalledWithOpCode_ItShouldReturnDiscordEvent(object data, Type expectedType)
|
||||
{
|
||||
var result = Read(data);
|
||||
|
||||
result.Should().BeOfType(expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Write_WhenCalledWithDiscordEvent_ItShouldReturnJson()
|
||||
{
|
||||
var discordEvent = new DiscordEvent
|
||||
{
|
||||
OpCode = DiscordOpCodes.Dispatch,
|
||||
Sequence = null,
|
||||
Type = null,
|
||||
Data = null
|
||||
};
|
||||
|
||||
var result = JsonSerializer.Serialize(discordEvent, _options);
|
||||
|
||||
var expectedJson = /*lang=json,strict*/ "{\"op\":0,\"s\":null,\"t\":null,\"d\":null}";
|
||||
|
||||
result.Should().Be(expectedJson);
|
||||
}
|
||||
|
||||
private DiscordEvent? Read(object data)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(data);
|
||||
var utf8Json = Encoding.UTF8.GetBytes(json);
|
||||
var reader = new Utf8JsonReader(utf8Json);
|
||||
return _converter.Read(ref reader, _discordEventType, _options);
|
||||
}
|
||||
|
||||
public static TheoryData<object, Type> TestData => new()
|
||||
{
|
||||
{
|
||||
new
|
||||
{
|
||||
op = DiscordOpCodes.Hello,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = null as object,
|
||||
},
|
||||
typeof(HelloDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = DiscordOpCodes.Dispatch,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = null as object
|
||||
},
|
||||
typeof(DispatchDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = DiscordOpCodes.Dispatch,
|
||||
s = null as int?,
|
||||
t = DiscordEventTypes.Ready,
|
||||
d = null as object
|
||||
},
|
||||
typeof(ReadyDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = DiscordOpCodes.Dispatch,
|
||||
s = null as int?,
|
||||
t = DiscordEventTypes.MessageCreate,
|
||||
d = null as object
|
||||
},
|
||||
typeof(MessageCreateDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = DiscordOpCodes.HeartbeatAck,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = null as object
|
||||
},
|
||||
typeof(HeartbeatAckDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = -1,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = null as object
|
||||
},
|
||||
typeof(DiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = 1,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = null as object
|
||||
},
|
||||
typeof(HeartbeatDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = 7,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = null as object
|
||||
},
|
||||
typeof(ReconnectDiscordEvent)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
op = 9,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = false
|
||||
},
|
||||
typeof(InvalidSessionDiscordEvent)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldCreateInstance()
|
||||
{
|
||||
var e = new DiscordEvent();
|
||||
|
||||
e.Should().NotBeNull();
|
||||
e.Should().BeOfType<DiscordEvent>();
|
||||
e.OpCode.Should().Be(0);
|
||||
e.Sequence.Should().BeNull();
|
||||
e.Type.Should().BeNull();
|
||||
e.Data.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithParameters_ItShouldCreateInstance()
|
||||
{
|
||||
var opCode = 1;
|
||||
var sequence = 2;
|
||||
var type = "test";
|
||||
var data = new object();
|
||||
|
||||
var e = new DiscordEvent
|
||||
{
|
||||
OpCode = opCode,
|
||||
Sequence = sequence,
|
||||
Type = type,
|
||||
Data = data,
|
||||
};
|
||||
|
||||
e.Should().NotBeNull();
|
||||
e.Should().BeOfType<DiscordEvent>();
|
||||
e.OpCode.Should().Be(opCode);
|
||||
e.Sequence.Should().Be(sequence);
|
||||
e.Type.Should().Be(type);
|
||||
e.Data.Should().BeSameAs(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordEventTypesTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public void Event_WhenCalled_ItShouldReturnCorrectValue(string eventType, string expectedValue)
|
||||
{
|
||||
eventType.Should().Be(expectedValue);
|
||||
}
|
||||
|
||||
public static TheoryData<string, string> TestData => new()
|
||||
{
|
||||
{ DiscordEventTypes.Ready, "READY" },
|
||||
{ DiscordEventTypes.GuildMemberAdd, "GUILD_MEMBER_ADD" },
|
||||
{ DiscordEventTypes.MessageCreate, "MESSAGE_CREATE" },
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(IsValidEventNameTestData))]
|
||||
public void IsValidEventName_WhenCalled_ItShouldReturnCorrectValue(string? eventName, bool expected)
|
||||
{
|
||||
var result = DiscordEventTypes.IsValidEvent(eventName!);
|
||||
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
||||
public static TheoryData<string?, bool> IsValidEventNameTestData => new()
|
||||
{
|
||||
{ " ", false },
|
||||
{ "", false },
|
||||
{ null, false },
|
||||
{ "I MADE IT UP", false },
|
||||
{ DiscordEventTypes.Ready, true },
|
||||
{ DiscordEventTypes.GuildMemberAdd, true },
|
||||
{ DiscordEventTypes.MessageCreate, true },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordGatewayClientExceptionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithoutParameters_ItShouldCreateInstance()
|
||||
{
|
||||
var exception = new DiscordGatewayClientException();
|
||||
|
||||
exception.Should().NotBeNull();
|
||||
exception.Should().BeOfType<DiscordGatewayClientException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithMessage_ItShouldCreateInstance()
|
||||
{
|
||||
var message = "Test message";
|
||||
var exception = new DiscordGatewayClientException(message);
|
||||
|
||||
exception.Should().NotBeNull();
|
||||
exception.Should().BeOfType<DiscordGatewayClientException>();
|
||||
exception.Message.Should().Be(message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithMessageAndInnerException_ItShouldCreateInstance()
|
||||
{
|
||||
var message = "Test message";
|
||||
var innerException = new Exception("Inner exception");
|
||||
var exception = new DiscordGatewayClientException(message, innerException);
|
||||
|
||||
exception.Should().NotBeNull();
|
||||
exception.Should().BeOfType<DiscordGatewayClientException>();
|
||||
exception.Message.Should().Be(message);
|
||||
exception.InnerException.Should().Be(innerException);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordIntentsTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public void Intents_ShouldHaveCorrectValue(long intent, long expectedValue)
|
||||
{
|
||||
intent.Should().Be(expectedValue);
|
||||
}
|
||||
|
||||
public static TheoryData<long, long> TestData => new()
|
||||
{
|
||||
{ DiscordIntents.Guilds, 1 },
|
||||
{ DiscordIntents.GuildMembers, 2 },
|
||||
{ DiscordIntents.GuildModeration, 4 },
|
||||
{ DiscordIntents.GuildExpressions, 8 },
|
||||
{ DiscordIntents.GuildIntegrations, 16 },
|
||||
{ DiscordIntents.GuildWebhooks, 32 },
|
||||
{ DiscordIntents.GuildInvites, 64 },
|
||||
{ DiscordIntents.GuildVoiceStates, 128 },
|
||||
{ DiscordIntents.GuildPresences, 256 },
|
||||
{ DiscordIntents.GuildMessages, 512 },
|
||||
{ DiscordIntents.GuildMessageReactions, 1024 },
|
||||
{ DiscordIntents.GuildMessageTyping, 2048 },
|
||||
{ DiscordIntents.DirectMessages, 4096 },
|
||||
{ DiscordIntents.DirectMessageReactions, 8192 },
|
||||
{ DiscordIntents.DirectMessageTyping, 16384 },
|
||||
{ DiscordIntents.MessageContent, 32768 },
|
||||
{ DiscordIntents.GuildScheduledEvents, 65536 },
|
||||
{ DiscordIntents.AutoModerationConfiguration, 1048576 },
|
||||
{ DiscordIntents.AutoModerationExecution, 2097152 },
|
||||
{ DiscordIntents.GuildMessagePolls, 16777216 },
|
||||
{ DiscordIntents.DirectMessagePolls, 33554432 },
|
||||
{ DiscordIntents.All, 53608447 }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordMessageReferenceTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var type = 1;
|
||||
var messageId = "message_id";
|
||||
var channelId = "channel_id";
|
||||
var guildId = "guild_id";
|
||||
var failIfNotExists = true;
|
||||
|
||||
var result = new DiscordMessageReference(
|
||||
type,
|
||||
messageId,
|
||||
channelId,
|
||||
guildId,
|
||||
failIfNotExists
|
||||
);
|
||||
|
||||
result.Type.Should().Be(type);
|
||||
result.MessageId.Should().Be(messageId);
|
||||
result.ChannelId.Should().Be(channelId);
|
||||
result.GuildId.Should().Be(guildId);
|
||||
result.FailIfNotExists.Should().Be(failIfNotExists);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordMessageReferenceTypesTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public void Type_WhenCalled_ItShouldReturnExpectedValue(int type, int expected)
|
||||
{
|
||||
type.Should().Be(expected);
|
||||
}
|
||||
|
||||
public static TheoryData<int, int> TestData => new()
|
||||
{
|
||||
{ DiscordMessageReferenceTypes.Default, 0 },
|
||||
{ DiscordMessageReferenceTypes.Forward, 1 },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordOpCodesTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public void OpCode_ShouldHaveCorrectValue(int opCode, int expectedValue)
|
||||
{
|
||||
opCode.Should().Be(expectedValue);
|
||||
}
|
||||
|
||||
public static TheoryData<int, int> TestData => new()
|
||||
{
|
||||
{ DiscordOpCodes.Dispatch, 0 },
|
||||
{ DiscordOpCodes.Heartbeat, 1 },
|
||||
{ DiscordOpCodes.Hello, 10 },
|
||||
{ DiscordOpCodes.HeartbeatAck, 11 }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class DiscordRestClientExceptionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithNoParameters_ItShouldCreateInstance()
|
||||
{
|
||||
var exception = new DiscordRestClientException();
|
||||
|
||||
exception.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithMessage_ItShouldCreateInstance()
|
||||
{
|
||||
var message = "Test message";
|
||||
var exception = new DiscordRestClientException(message);
|
||||
|
||||
exception.Should().NotBeNull();
|
||||
exception.Message.Should().Be(message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithMessageAndInnerException_ItShouldCreateInstance()
|
||||
{
|
||||
var message = "Test message";
|
||||
var innerException = new Exception("Inner exception");
|
||||
var exception = new DiscordRestClientException(message, innerException);
|
||||
|
||||
exception.Should().NotBeNull();
|
||||
exception.Message.Should().Be(message);
|
||||
exception.InnerException.Should().Be(innerException);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public sealed class DiscordRestClientTests : IDisposable
|
||||
{
|
||||
private const string BaseUrl = "https://discord.com/api/v10";
|
||||
private static string GatewayEndpoint => $"{BaseUrl}/gateway";
|
||||
private static string ChannelMessagesEndpoint => $"{BaseUrl}/channels/*/messages";
|
||||
|
||||
private readonly Mock<ILogger<DiscordRestClient>> _mockLogger = new();
|
||||
private readonly MockHttpMessageHandler _mockHttpMessageHandler;
|
||||
private readonly DiscordRestClient _discordRestClient;
|
||||
|
||||
public DiscordRestClientTests()
|
||||
{
|
||||
_mockHttpMessageHandler = new MockHttpMessageHandler();
|
||||
var httpClient = _mockHttpMessageHandler.ToHttpClient();
|
||||
httpClient.BaseAddress = new Uri("https://discord.com/api/v10/");
|
||||
_discordRestClient = new DiscordRestClient(_mockLogger.Object, httpClient);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenLoggerIsNull_ItShouldThrowAnException()
|
||||
{
|
||||
var act = () => new DiscordRestClient(null!, _mockHttpMessageHandler.ToHttpClient());
|
||||
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenHttpClientIsNull_ItShouldThrowAnException()
|
||||
{
|
||||
var act = () => new DiscordRestClient(_mockLogger.Object, null!);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetGatewayUrlAsync_WhenRequestFails_ItShouldThrowException()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.When(GatewayEndpoint)
|
||||
.Respond(HttpStatusCode.InternalServerError);
|
||||
|
||||
var act = async () => await _discordRestClient.GetGatewayUrlAsync(CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<DiscordRestClientException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetGatewayUrlAsync_WhenResponseIsNull_ItShouldThrowException()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.When(GatewayEndpoint)
|
||||
.Respond(HttpStatusCode.OK, "application/json", "null");
|
||||
|
||||
var act = async () => await _discordRestClient.GetGatewayUrlAsync(CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<DiscordRestClientException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetGatewayUrlAsync_WhenRequestIsSuccessful_ItShouldReturnGatewayUrl()
|
||||
{
|
||||
var expectedUrl = "test";
|
||||
var jsonResponse = $@"{{
|
||||
""url"": ""{expectedUrl}""
|
||||
}}";
|
||||
|
||||
_mockHttpMessageHandler
|
||||
.When(GatewayEndpoint)
|
||||
.Respond(HttpStatusCode.OK, "application/json", jsonResponse);
|
||||
|
||||
var result = await _discordRestClient.GetGatewayUrlAsync(CancellationToken.None);
|
||||
|
||||
result.Should().Be(expectedUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenRequestFails_ItShouldThrowAnException()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.When(ChannelMessagesEndpoint)
|
||||
.Respond(HttpStatusCode.InternalServerError);
|
||||
|
||||
var request = new CreateMessageRequest(
|
||||
"content",
|
||||
new(DiscordMessageReferenceTypes.Default, "message_id", "channel_id", "guild_id", false)
|
||||
);
|
||||
|
||||
var act = async () => await _discordRestClient.CreateMessageAsync("channel_id", request);
|
||||
|
||||
await act.Should().ThrowAsync<DiscordRestClientException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageyAsync_WhenResponseIsNull_ItShouldThrowAnException()
|
||||
{
|
||||
_mockHttpMessageHandler
|
||||
.When(ChannelMessagesEndpoint)
|
||||
.Respond(HttpStatusCode.OK, "application/json", "null");
|
||||
|
||||
var request = new CreateMessageRequest(
|
||||
"content",
|
||||
new(DiscordMessageReferenceTypes.Default, "message_id", "channel_id", "guild_id", false)
|
||||
);
|
||||
|
||||
var act = async () => await _discordRestClient.CreateMessageAsync("channel_id", request);
|
||||
|
||||
await act.Should().ThrowAsync<DiscordRestClientException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateMessageAsync_WhenRequestSucceeds_ItShouldReturnMessage()
|
||||
{
|
||||
var message = new DiscordMessage();
|
||||
var messageResponse = JsonSerializer.Serialize(message);
|
||||
|
||||
_mockHttpMessageHandler
|
||||
.When(ChannelMessagesEndpoint)
|
||||
.Respond(HttpStatusCode.OK, "application/json", messageResponse);
|
||||
|
||||
var request = new CreateMessageRequest(
|
||||
"content",
|
||||
new(DiscordMessageReferenceTypes.Default, "message_id", "channel_id", "guild_id", false)
|
||||
);
|
||||
|
||||
var result = await _discordRestClient.CreateMessageAsync("channel_id", request);
|
||||
|
||||
result.Should().BeEquivalentTo(message);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mockHttpMessageHandler.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class ExtensionsTests
|
||||
{
|
||||
private readonly DiscordClientOptions _discordClientOptions = new()
|
||||
{
|
||||
ApiUrl = "https://test.com",
|
||||
AppToken = "test_token",
|
||||
};
|
||||
private readonly Mock<IDiscordRestClient> _mockDiscordRestClient = new();
|
||||
private readonly Mock<ILogger<DiscordGatewayClient>> _mockLogger = new();
|
||||
private readonly Mock<IWebSocketFactory> _mockWebSocketFactory = new();
|
||||
private readonly Mock<TimeProvider> _mockTimeProvider = new();
|
||||
private readonly Mock<IServiceScopeFactory> _mockScopeFactory = new();
|
||||
|
||||
private readonly ServiceCollection _services = new();
|
||||
|
||||
public ExtensionsTests()
|
||||
{
|
||||
_services.AddSingleton(_discordClientOptions);
|
||||
_services.AddSingleton(_mockLogger.Object);
|
||||
_services.AddSingleton(_mockWebSocketFactory.Object);
|
||||
_services.AddSingleton(_mockTimeProvider.Object);
|
||||
_services.AddSingleton(_mockScopeFactory.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDiscordRestClient_WhenCalled_ItShouldAddDiscordRestClient()
|
||||
{
|
||||
_services.AddDiscordRestClient();
|
||||
|
||||
var act = () => _services
|
||||
.BuildServiceProvider()
|
||||
.GetRequiredService<IDiscordRestClient>();
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDiscordGatewayClient_WhenCalled_ItShouldAddDiscordGatewayClient()
|
||||
{
|
||||
_services.AddSingleton(_mockDiscordRestClient.Object);
|
||||
|
||||
_services.AddDiscordGatewayClient();
|
||||
|
||||
var act = () => _services
|
||||
.BuildServiceProvider()
|
||||
.GetRequiredService<IDiscordGatewayClient>();
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDiscordGatewayClient_WhenCalledWithConfigureAction_ItShouldAddDiscordGatewayClientAndConfigureIt()
|
||||
{
|
||||
var mockAction = new Mock<Action<IDiscordGatewayClient>>();
|
||||
|
||||
_services.AddSingleton(_mockDiscordRestClient.Object);
|
||||
|
||||
_services.AddDiscordGatewayClient(mockAction.Object);
|
||||
|
||||
var act = () => _services
|
||||
.BuildServiceProvider()
|
||||
.GetRequiredService<IDiscordGatewayClient>();
|
||||
|
||||
act.Should().NotThrow();
|
||||
|
||||
mockAction.Invocations.Count.Should().Be(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class HeartbeatAckDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var result = new HeartbeatAckDiscordEvent();
|
||||
|
||||
result.OpCode.Should().Be(DiscordOpCodes.HeartbeatAck);
|
||||
result.Sequence.Should().BeNull();
|
||||
result.Type.Should().BeNull();
|
||||
result.Data.Should().BeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class HeartbeatDiscordEventTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(1)]
|
||||
public void Constructor_WhenCalled_ItShouldInitializeProperties(int? sequence)
|
||||
{
|
||||
var heartbeatEvent = new HeartbeatDiscordEvent(sequence);
|
||||
|
||||
heartbeatEvent.OpCode.Should().Be(1);
|
||||
heartbeatEvent.Sequence.Should().Be(sequence);
|
||||
heartbeatEvent.Type.Should().BeNull();
|
||||
heartbeatEvent.Data.Should().BeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class HelloDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||
{
|
||||
var helloEvent = new HelloDiscordEvent();
|
||||
var helloData = new HelloData();
|
||||
|
||||
helloEvent.OpCode.Should().Be(DiscordOpCodes.Hello);
|
||||
helloEvent.Sequence.Should().BeNull();
|
||||
helloEvent.Type.Should().BeNull();
|
||||
helloEvent.Data.Should().BeEquivalentTo(helloData);
|
||||
helloData.HeartbeatInterval.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithParameters_ItShouldInitializeProperties()
|
||||
{
|
||||
var opCode = 1;
|
||||
var sequence = 2;
|
||||
var type = "type";
|
||||
var heartbeatInterval = 1000;
|
||||
|
||||
|
||||
var helloData = new HelloData
|
||||
{
|
||||
HeartbeatInterval = heartbeatInterval
|
||||
};
|
||||
|
||||
var helloEvent = new HelloDiscordEvent
|
||||
{
|
||||
OpCode = opCode,
|
||||
Sequence = sequence,
|
||||
Type = type,
|
||||
Data = helloData
|
||||
};
|
||||
|
||||
helloEvent.OpCode.Should().Be(opCode);
|
||||
helloEvent.Sequence.Should().Be(sequence);
|
||||
helloEvent.Type.Should().Be(type);
|
||||
helloEvent.Data.Should().BeSameAs(helloData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_WhenCalled_ItShouldReturnHelloDiscordEvent()
|
||||
{
|
||||
var data = new
|
||||
{
|
||||
op = 0,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = new
|
||||
{
|
||||
heartbeat_interval = 1000
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(data);
|
||||
|
||||
var helloEvent = JsonSerializer.Deserialize<HelloDiscordEvent>(json);
|
||||
|
||||
helloEvent.Should().NotBeNull();
|
||||
helloEvent!.OpCode.Should().Be(data.op);
|
||||
helloEvent.Sequence.Should().Be(data.s);
|
||||
helloEvent.Type.Should().Be(data.t);
|
||||
helloEvent.Data.Should().NotBeNull();
|
||||
helloEvent.Data!.HeartbeatInterval.Should().Be(data.d.heartbeat_interval);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using StevesBot.Worker.Discord.Gateway.Events.Data;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class IdentifyDataTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnInstance()
|
||||
{
|
||||
var result = new IdentifyData();
|
||||
|
||||
result.Token.Should().Be(string.Empty);
|
||||
result.Properties.Should().BeEquivalentTo(new IdentifyProperties());
|
||||
result.Presence.Should().BeEquivalentTo(new UpdatePresenceData());
|
||||
result.Intents.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithParameters_ItShouldReturnInstance()
|
||||
{
|
||||
var token = "test_token";
|
||||
var properties = new IdentifyProperties
|
||||
{
|
||||
Os = "test_os",
|
||||
Browser = "test_browser",
|
||||
Device = "test_device",
|
||||
};
|
||||
|
||||
var presence = new UpdatePresenceData
|
||||
{
|
||||
Status = "test_status",
|
||||
Activities = [],
|
||||
};
|
||||
|
||||
var intents = 123456789;
|
||||
|
||||
var result = new IdentifyData
|
||||
{
|
||||
Token = token,
|
||||
Properties = properties,
|
||||
Presence = presence,
|
||||
Intents = intents
|
||||
};
|
||||
|
||||
result.Token.Should().Be(token);
|
||||
result.Properties.Should().BeSameAs(properties);
|
||||
result.Presence.Should().BeSameAs(presence);
|
||||
result.Intents.Should().Be(intents);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class IdentifyDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnInstance()
|
||||
{
|
||||
var token = "test_token";
|
||||
var intents = 123456789;
|
||||
var presence = new UpdatePresenceData
|
||||
{
|
||||
Status = "online",
|
||||
Activities = [],
|
||||
};
|
||||
|
||||
var result = new IdentifyDiscordEvent(token, intents, presence);
|
||||
|
||||
result.OpCode.Should().Be(DiscordOpCodes.Identify);
|
||||
result.Data.Token.Should().Be(token);
|
||||
result.Data.Intents.Should().Be(intents);
|
||||
result.Data.Presence.Should().BeSameAs(presence);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class InvalidSessionDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var result = new InvalidSessionDiscordEvent();
|
||||
|
||||
result.OpCode.Should().Be(DiscordOpCodes.InvalidSession);
|
||||
result.Type.Should().BeNull();
|
||||
result.Sequence.Should().BeNull();
|
||||
result.Data.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class LockReleaserTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldCreateInstance()
|
||||
{
|
||||
using var semaphore = new SemaphoreSlim(1);
|
||||
using var lockReleaser = new LockReleaser(semaphore);
|
||||
|
||||
lockReleaser.Should().NotBeNull();
|
||||
lockReleaser.Should().BeOfType<LockReleaser>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledAndSemaphoreIsNull_ItShouldThrowArgumentNullException()
|
||||
{
|
||||
var act = static () => new LockReleaser(null!);
|
||||
|
||||
act.Should().Throw<ArgumentNullException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WhenCalled_ItShouldReleaseSemaphore()
|
||||
{
|
||||
using var semaphore = new SemaphoreSlim(0, 1);
|
||||
using var lockReleaser = new LockReleaser(semaphore);
|
||||
|
||||
lockReleaser.Dispose();
|
||||
|
||||
semaphore.CurrentCount.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WhenCalledMultipleTimes_ItShouldReleaseSemaphoreOnlyOnce()
|
||||
{
|
||||
using var semaphore = new SemaphoreSlim(0, 1);
|
||||
using var lockReleaser = new LockReleaser(semaphore);
|
||||
|
||||
lockReleaser.Dispose();
|
||||
lockReleaser.Dispose();
|
||||
|
||||
semaphore.CurrentCount.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispose_WhenCalledAndSemaphoreIsAlreadyDisposed_ItShouldNotThrowException()
|
||||
{
|
||||
var act = () =>
|
||||
{
|
||||
var semaphore = new SemaphoreSlim(0, 1);
|
||||
var lockReleaser = new LockReleaser(semaphore);
|
||||
|
||||
semaphore.Dispose();
|
||||
lockReleaser.Dispose();
|
||||
};
|
||||
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class MessageCreateDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var result = new MessageCreateDiscordEvent();
|
||||
|
||||
result.OpCode.Should().Be(0);
|
||||
result.Type.Should().Be(DiscordEventTypes.MessageCreate);
|
||||
result.Sequence.Should().BeNull();
|
||||
result.Data.Should().BeEquivalentTo(new DiscordMessage());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 0, true)]
|
||||
[InlineData(1, 0, false)]
|
||||
public void IsMessageType_WhenCalled_ItShouldReturnCorrectResult(int messageType, int givenType, bool expectedResult)
|
||||
{
|
||||
var e = new MessageCreateDiscordEvent()
|
||||
{
|
||||
Data = new() { Type = messageType }
|
||||
};
|
||||
|
||||
var result = e.IsMessageType(givenType);
|
||||
|
||||
result.Should().Be(expectedResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class PresenceStatusTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public void PresenceStatus_WhenCalled_ItShouldReturnExpectedResult(string status, string expected)
|
||||
{
|
||||
status.Should().Be(expected);
|
||||
}
|
||||
|
||||
public static TheoryData<string, string> TestData => new()
|
||||
{
|
||||
{
|
||||
PresenceStatus.Online,
|
||||
"online"
|
||||
},
|
||||
{
|
||||
PresenceStatus.Idle,
|
||||
"idle"
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class ReadyDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldInitializeProperties()
|
||||
{
|
||||
var readyEvent = new ReadyDiscordEvent();
|
||||
var readyData = new ReadyData();
|
||||
|
||||
readyEvent.OpCode.Should().Be(0);
|
||||
readyEvent.Sequence.Should().BeNull();
|
||||
readyEvent.Type.Should().Be(DiscordEventTypes.Ready);
|
||||
readyEvent.Data.Should().BeEquivalentTo(readyData);
|
||||
readyData.Version.Should().Be(0);
|
||||
readyData.SessionId.Should().Be(string.Empty);
|
||||
readyData.ResumeGatewayUrl.Should().Be(string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledWithParameters_ItShouldInitializeProperties()
|
||||
{
|
||||
var readyData = new ReadyData
|
||||
{
|
||||
Version = 1,
|
||||
SessionId = "session_id",
|
||||
ResumeGatewayUrl = "resume_gateway_url"
|
||||
};
|
||||
|
||||
var readyEvent = new ReadyDiscordEvent
|
||||
{
|
||||
OpCode = 1,
|
||||
Sequence = 2,
|
||||
Type = "type",
|
||||
Data = readyData
|
||||
};
|
||||
|
||||
readyEvent.OpCode.Should().Be(1);
|
||||
readyEvent.Sequence.Should().Be(2);
|
||||
readyEvent.Type.Should().Be("type");
|
||||
readyEvent.Data.Should().BeSameAs(readyData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialize_WhenCalled_ItShouldReturnReadyDiscordEvent()
|
||||
{
|
||||
var data = new
|
||||
{
|
||||
op = 0,
|
||||
s = null as int?,
|
||||
t = null as string,
|
||||
d = new
|
||||
{
|
||||
v = 1,
|
||||
session_id = "session_id",
|
||||
resume_gateway_url = "resume_gateway_url"
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(data);
|
||||
|
||||
var readyEvent = JsonSerializer.Deserialize<ReadyDiscordEvent>(json);
|
||||
|
||||
readyEvent.Should().NotBeNull();
|
||||
readyEvent!.OpCode.Should().Be(data.op);
|
||||
readyEvent.Sequence.Should().Be(data.s);
|
||||
readyEvent.Type.Should().Be(data.t);
|
||||
readyEvent.Data.Version.Should().Be(data.d.v);
|
||||
readyEvent.Data.SessionId.Should().Be(data.d.session_id);
|
||||
readyEvent.Data.ResumeGatewayUrl.Should().Be(data.d.resume_gateway_url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class ReconnectDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var result = new ReconnectDiscordEvent();
|
||||
|
||||
result.OpCode.Should().Be(DiscordOpCodes.Reconnect);
|
||||
result.Sequence.Should().BeNull();
|
||||
result.Type.Should().BeNull();
|
||||
result.Data.Should().BeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class ResumeDataTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var result = new ResumeData();
|
||||
|
||||
result.Token.Should().BeEmpty();
|
||||
result.SessionId.Should().BeEmpty();
|
||||
result.Sequence.Should().Be(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class ResumeDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnAnInstance()
|
||||
{
|
||||
var token = "test_token";
|
||||
var sessionId = "session_id";
|
||||
var sequence = 1;
|
||||
|
||||
var result = new ResumeDiscordEvent(token, sessionId, sequence);
|
||||
|
||||
result.OpCode.Should().Be(DiscordOpCodes.Resume);
|
||||
result.Type.Should().BeNull();
|
||||
result.Sequence.Should().BeNull();
|
||||
result.Data.Should().BeEquivalentTo(new ResumeData()
|
||||
{
|
||||
Token = token,
|
||||
SessionId = sessionId,
|
||||
Sequence = sequence,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class UpdatePresenceDataTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldCreateAnInstance()
|
||||
{
|
||||
var updatePresenceData = new UpdatePresenceData();
|
||||
|
||||
updatePresenceData.Since.Should().BeNull();
|
||||
updatePresenceData.Activities.Should().BeEmpty();
|
||||
updatePresenceData.Status.Should().Be(PresenceStatus.Online);
|
||||
updatePresenceData.Afk.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WhenCalledAndPropertiesAreInitialized_ItShouldCreateAnInstance()
|
||||
{
|
||||
var since = 1;
|
||||
var activities = new List<Activity>();
|
||||
var status = "some_made_up_status";
|
||||
var afk = true;
|
||||
|
||||
var updatePresenceData = new UpdatePresenceData()
|
||||
{
|
||||
Since = since,
|
||||
Activities = activities,
|
||||
Status = status,
|
||||
Afk = afk,
|
||||
};
|
||||
|
||||
updatePresenceData.Since.Should().Be(since);
|
||||
updatePresenceData.Activities.Should().BeSameAs(activities);
|
||||
updatePresenceData.Status.Should().Be(status);
|
||||
updatePresenceData.Afk.Should().Be(afk);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class UpdatePresenceDiscordEventTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WhenCalled_ItShouldReturnInstance()
|
||||
{
|
||||
var activities = new List<Activity>();
|
||||
var since = 1234567890L;
|
||||
|
||||
var e = new UpdatePresenceDiscordEvent(since, activities, PresenceStatus.Online, false);
|
||||
|
||||
e.Should().NotBeNull();
|
||||
e.Data.Should().NotBeNull();
|
||||
e.Data.Since.Should().Be(since);
|
||||
e.Data.Activities.Should().BeSameAs(activities);
|
||||
e.Data.Status.Should().Be(PresenceStatus.Online);
|
||||
e.Data.Afk.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using WebSocket = StevesBot.Worker.WebSockets.WebSocket;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class WebSocketFactoryTests
|
||||
{
|
||||
private readonly WebSocketFactory _webSocketFactory = new();
|
||||
|
||||
[Fact]
|
||||
public void Create_WhenCalled_ItShouldReturnNewWebSocketInstance()
|
||||
{
|
||||
var result = _webSocketFactory.Create();
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.Should().BeOfType<WebSocket>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using WebSocket = StevesBot.Worker.WebSockets.WebSocket;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class WebSocketTests
|
||||
{
|
||||
[Fact]
|
||||
public void State_WhenCalled_ItShouldReturnWebSocketState()
|
||||
{
|
||||
using var webSocket = new WebSocket();
|
||||
var state = webSocket.State;
|
||||
|
||||
state.Should().Be(WebSocketState.None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using StevesBot.Worker.Handlers;
|
||||
|
||||
namespace StevesBot.Worker.Tests.Unit;
|
||||
|
||||
public class WelcomeMessageHandlerTests
|
||||
{
|
||||
private readonly Mock<IDiscordRestClient> _mockDiscordRestClient = new();
|
||||
private readonly Mock<ILogger<IDiscordGatewayClient>> _mockLogger = new();
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public WelcomeMessageHandlerTests()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddSingleton(_mockDiscordRestClient.Object);
|
||||
serviceCollection.AddSingleton(_mockLogger.Object);
|
||||
|
||||
_serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenEventIsNotMessageCreateEvent_ItShouldNotCreateMessage()
|
||||
{
|
||||
await WelcomeMessageHandler.HandleAsync(new DiscordEvent(), _serviceProvider);
|
||||
|
||||
_mockDiscordRestClient
|
||||
.Verify(
|
||||
static c => c.CreateMessageAsync(It.IsAny<string>(), It.IsAny<CreateMessageRequest>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenMessageCreateEventIsNotAUserJoinMessage_ItShouldNotCreateMessage()
|
||||
{
|
||||
var @event = new MessageCreateDiscordEvent()
|
||||
{
|
||||
Data = new()
|
||||
{
|
||||
Type = -1,
|
||||
},
|
||||
};
|
||||
|
||||
await WelcomeMessageHandler.HandleAsync(@event, _serviceProvider);
|
||||
|
||||
_mockDiscordRestClient
|
||||
.Verify(
|
||||
static c => c.CreateMessageAsync(It.IsAny<string>(), It.IsAny<CreateMessageRequest>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_WhenUserJoinMessageCreateEventReceived_ItShouldCreateWelcomeMessage()
|
||||
{
|
||||
_mockDiscordRestClient
|
||||
.Setup(static c => c.CreateMessageAsync(It.IsAny<string>(), It.IsAny<CreateMessageRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new DiscordMessage());
|
||||
|
||||
var @event = new MessageCreateDiscordEvent()
|
||||
{
|
||||
Data = new()
|
||||
{
|
||||
Type = DiscordMessageTypes.UserJoin,
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
ChannelId = Guid.NewGuid().ToString(),
|
||||
GuildId = Guid.NewGuid().ToString(),
|
||||
Author = new()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await WelcomeMessageHandler.HandleAsync(@event, _serviceProvider);
|
||||
|
||||
_mockDiscordRestClient
|
||||
.Verify(
|
||||
static c => c.CreateMessageAsync(It.IsAny<string>(), It.IsAny<CreateMessageRequest>(), It.IsAny<CancellationToken>()),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
global using System.Net;
|
||||
global using System.Net.WebSockets;
|
||||
global using System.Text;
|
||||
global using System.Text.Json;
|
||||
global using System.Text.Json.Serialization;
|
||||
|
||||
global using Microsoft.AspNetCore.Builder;
|
||||
global using Microsoft.AspNetCore.Hosting;
|
||||
global using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
global using Microsoft.AspNetCore.Http.Features;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Hosting;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
|
||||
global using Moq;
|
||||
|
||||
global using RichardSzalay.MockHttp;
|
||||
|
||||
global using StevesBot.Worker.Discord;
|
||||
global using StevesBot.Worker.Discord.Gateway;
|
||||
global using StevesBot.Worker.Discord.Gateway.Events;
|
||||
global using StevesBot.Worker.Discord.Gateway.Events.Data;
|
||||
global using StevesBot.Worker.Discord.Rest;
|
||||
global using StevesBot.Worker.Discord.Rest.Requests;
|
||||
global using StevesBot.Worker.Discord.Shared;
|
||||
global using StevesBot.Worker.Tests.Integration.Infrastructure;
|
||||
global using StevesBot.Worker.Threading;
|
||||
global using StevesBot.Worker.WebSockets;
|
||||
Reference in New Issue
Block a user