Use IReady event for gateway tests

- Fix manual connect invocations
This commit is contained in:
Jordan Brown
2021-08-23 19:16:59 -04:00
parent a17d6532bc
commit 6abcfe18f2
5 changed files with 32 additions and 45 deletions
@@ -29,5 +29,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// <inheritdoc />
public Task<Result> RespondAsync(IMessageCreate gatewayEvent, CancellationToken ct) => targetResponder.RespondAsync(gatewayEvent, ct);
/// <inheritdoc />
public Task<Result> RespondAsync(IReady gatewayEvent, CancellationToken ct = default) => targetResponder.RespondAsync(gatewayEvent, ct);
}
}
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -18,7 +17,6 @@ using Remora.Discord.Gateway.Extensions;
using Remora.Results;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.System;
@@ -50,11 +48,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// </summary>
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="DiscordProvider"/>.
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="ServiceProvider"/> containing Discord services.
/// </summary>
@@ -80,6 +73,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// </summary>
CancellationTokenSource gatewayCts;
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> for the initial gateway connection event.
/// </summary>
TaskCompletionSource<object> gatewayReadyTcs;
/// <summary>
/// The <see cref="Task"/> representing the lifetime of the client.
/// </summary>
@@ -149,19 +147,16 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// </summary>
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="Provider"/>.</param>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="Provider"/>.</param>
/// <param name="chatBot">The <see cref="ChatBot"/> for the <see cref="Provider"/>.</param>
public DiscordProvider(
IJobManager jobManager,
IAssemblyInformationProvider assemblyInformationProvider,
IAsyncDelayer asyncDelayer,
ILogger<DiscordProvider> logger,
ChatBot chatBot)
: base(jobManager, logger, chatBot)
{
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
mappedChannels = new List<ulong>();
@@ -579,6 +574,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
return Result.FromSuccess();
}
/// <inheritdoc />
public Task<Result> RespondAsync(IReady readyEvent, CancellationToken cancellationToken)
{
gatewayReadyTcs?.TrySetResult(null);
return Task.FromResult(Result.FromSuccess());
}
/// <inheritdoc />
protected override async Task Connect(CancellationToken cancellationToken)
{
@@ -592,20 +594,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var gatewayCancellationToken = gatewayCts.Token;
var gatewayClient = serviceProvider.GetRequiredService<DiscordGatewayClient>();
// reconnects keep happening until we stop or it faults, our auto-reconnector will handle the latter
var gatewayTaskLocal = gatewayClient.RunAsync(gatewayCancellationToken);
// HACK: The gateway connection status isn't public, how 'bout we read it anyway?
GatewayConnectionStatus connectionStatus;
var connectionStatusField = gatewayClient.GetType().GetField("_connectionStatus", BindingFlags.NonPublic | BindingFlags.Instance);
do
gatewayReadyTcs = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => gatewayReadyTcs.TrySetCanceled()))
{
await asyncDelayer.Delay(TimeSpan.FromMilliseconds(250), cancellationToken).ConfigureAwait(false);
connectionStatus = (GatewayConnectionStatus)connectionStatusField.GetValue(gatewayClient);
}
while (!gatewayTaskLocal.IsCompleted && connectionStatus != GatewayConnectionStatus.Connected);
// reconnects keep happening until we stop or it faults, our auto-reconnector will handle the latter
gatewayTask = gatewayClient.RunAsync(gatewayCancellationToken);
await Task.WhenAny(gatewayReadyTcs.Task, gatewayTask).ConfigureAwait(false);
}
gatewayTask = gatewayTaskLocal;
if (gatewayTask.IsCompleted)
{
await DisconnectImpl(cancellationToken).ConfigureAwait(false);
@@ -630,7 +627,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
catch (OperationCanceledException)
{
throw;
if (gatewayTask != null)
await DisconnectImpl(default).ConfigureAwait(false); // DCT: Musn't abort
}
catch (Exception e)
{
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// <summary>
/// Combined interface for the <see cref="IResponder"/> types used by TGS.
/// </summary>
interface IDiscordResponders : IResponder<IMessageCreate>
interface IDiscordResponders : IResponder<IMessageCreate>, IResponder<IReady>
{
}
}
@@ -68,7 +68,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
ChatProvider.Discord => new DiscordProvider(
jobManager,
assemblyInformationProvider,
asyncDelayer,
loggerFactory.CreateLogger<DiscordProvider>(),
settings),
_ => throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid ChatProvider: {0}", settings.Provider)),
@@ -7,7 +7,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.System;
@@ -19,7 +18,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
{
ChatBot testToken1;
IJobManager mockJobManager;
IAsyncDelayer mockDel;
[TestInitialize]
public void Initialize()
@@ -41,10 +39,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
.Setup(x => x.WaitForJobCompletion(It.IsNotNull<Job>(), It.IsAny<User>(), It.IsAny<CancellationToken>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
mockJobManager = mockSetup.Object;
var mockDelSetup = new Mock<IAsyncDelayer>();
mockDelSetup.Setup(x => x.Delay(It.IsAny<TimeSpan>(), It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
mockDel = mockDelSetup.Object;
}
[TestMethod]
@@ -53,14 +47,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
if (testToken1 == null)
Assert.Inconclusive("Required environment variable TGS4_TEST_DISCORD_TOKEN isn't set!");
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(null, null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, null, null, null));
var mockAss = new Mock<IAssemblyInformationProvider>();
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, mockAss.Object, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, mockAss.Object, mockDel, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, mockAss.Object, null, null));
var mockLogger = new Mock<ILogger<DiscordProvider>>();
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, null, null, mockLogger.Object, null));
await new DiscordProvider(mockJobManager, mockAss.Object, mockDel, mockLogger.Object, testToken1).DisposeAsync();
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, null, mockLogger.Object, null));
await new DiscordProvider(mockJobManager, mockAss.Object, mockLogger.Object, testToken1).DisposeAsync();
}
static Task InvokeConnect(IProvider provider, CancellationToken cancellationToken = default) => (Task)provider.GetType().GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(provider, new object[] { cancellationToken });
@@ -69,7 +62,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
public async Task TestConnectWithFakeTokenFails()
{
var mockLogger = new Mock<ILogger<DiscordProvider>>();
await using var provider = new DiscordProvider(mockJobManager, Mock.Of<IAssemblyInformationProvider>(), mockDel, mockLogger.Object, new ChatBot
await using var provider = new DiscordProvider(mockJobManager, Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, new ChatBot
{
ReconnectionInterval = 1,
ConnectionString = "asdf"
@@ -85,17 +78,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
Assert.Inconclusive("Required environment variable TGS4_TEST_DISCORD_TOKEN isn't set!");
var mockLogger = new Mock<ILogger<DiscordProvider>>();
await using var provider = new DiscordProvider(mockJobManager, Mock.Of<IAssemblyInformationProvider>(), mockDel, mockLogger.Object, testToken1);
await using var provider = new DiscordProvider(mockJobManager, Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, testToken1);
Assert.IsFalse(provider.Connected);
await provider.Disconnect(default).ConfigureAwait(false);
Assert.IsFalse(provider.Connected);
await InvokeConnect(provider).ConfigureAwait(false);
Assert.IsTrue(provider.Connected);
await InvokeConnect(provider).ConfigureAwait(false);
Assert.IsTrue(provider.Connected);
await provider.Disconnect(default).ConfigureAwait(false);
Assert.IsFalse(provider.Connected);
await provider.Disconnect(default).ConfigureAwait(false);
Assert.IsFalse(provider.Connected);