Merge pull request #787 from tgstation/780-MockRuntimes

Putting the code on a week long kale diet
This commit is contained in:
Jordan Brown
2018-10-15 15:34:27 -04:00
committed by GitHub
28 changed files with 431 additions and 131 deletions
@@ -39,6 +39,11 @@ namespace Tgstation.Server.Host.Components.Chat
/// </summary>
readonly IRestartRegistration restartRegistration;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="Chat"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="Chat"/>
/// </summary>
@@ -109,18 +114,20 @@ namespace Tgstation.Server.Host.Components.Chat
/// </summary>
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="commandFactory">The value of <see cref="commandFactory"/></param>
/// <param name="serverControl">The <see cref="IServerControl"/> to populate <see cref="restartRegistration"/> with</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="initialChatBots">The <see cref="IEnumerable{T}"/> used to populate <see cref="initialChatBots"/></param>
public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, ILoggerFactory loggerFactory, ILogger<Chat> logger, IEnumerable<Models.ChatBot> initialChatBots)
public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory, ILogger<Chat> logger, IEnumerable<Models.ChatBot> initialChatBots)
{
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
if (serverControl == null)
throw new ArgumentNullException(nameof(serverControl));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.initialChatBots = initialChatBots?.ToList() ?? throw new ArgumentNullException(nameof(initialChatBots));
@@ -344,7 +351,7 @@ namespace Tgstation.Server.Host.Components.Chat
if (messageTasks.Count == 0)
{
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
continue;
}
@@ -25,20 +25,27 @@ namespace Tgstation.Server.Host.Components.Chat
/// </summary>
readonly IServerControl serverControl;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="ChatFactory"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// Construct a <see cref="ChatFactory"/>
/// </summary>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
/// <param name="providerFactory">The value of <see cref="providerFactory"/></param>
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
public ChatFactory(ILoggerFactory loggerFactory, IProviderFactory providerFactory, IServerControl serverControl)
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
public ChatFactory(IProviderFactory providerFactory, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
/// <inheritdoc />
public IChat CreateChat(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable<Models.ChatBot> initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, serverControl, loggerFactory, loggerFactory.CreateLogger<Chat>(), initialChatBots);
public IChat CreateChat(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable<Models.ChatBot> initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, serverControl, asyncDelayer, loggerFactory, loggerFactory.CreateLogger<Chat>(), initialChatBots);
}
}
@@ -11,24 +11,31 @@ namespace Tgstation.Server.Host.Components.Chat
/// <inheritdoc />
sealed class ProviderFactory : IProviderFactory
{
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="ProviderFactory"/>
/// </summary>
readonly ILoggerFactory loggerFactory;
/// <summary>
/// The <see cref="IApplication"/> for the <see cref="ProviderFactory"/>
/// </summary>
readonly IApplication application;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="ProviderFactory"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="ProviderFactory"/>
/// </summary>
readonly ILoggerFactory loggerFactory;
/// <summary>
/// Construct a <see cref="ProviderFactory"/>
/// </summary>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
/// <param name="application">The value of <see cref="application"/></param>
public ProviderFactory(ILoggerFactory loggerFactory, IApplication application)
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
public ProviderFactory(IApplication application, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.application = application ?? throw new ArgumentNullException(nameof(application));
}
@@ -44,7 +51,7 @@ namespace Tgstation.Server.Host.Components.Chat
{
case ChatProvider.Irc:
var ircBuilder = (IrcConnectionStringBuilder)builder;
return new IrcProvider(loggerFactory.CreateLogger<IrcProvider>(), application, ircBuilder.Address, ircBuilder.Port.Value, ircBuilder.Nickname, ircBuilder.Password, ircBuilder.PasswordType, ircBuilder.UseSsl.Value);
return new IrcProvider(application, asyncDelayer, loggerFactory.CreateLogger<IrcProvider>(), ircBuilder.Address, ircBuilder.Port.Value, ircBuilder.Nickname, ircBuilder.Password, ircBuilder.PasswordType, ircBuilder.UseSsl.Value);
case ChatProvider.Discord:
var discordBuilder = (DiscordConnectionStringBuilder)builder;
return new DiscordProvider(loggerFactory.CreateLogger<DiscordProvider>(), discordBuilder.BotToken);
@@ -25,6 +25,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// <inheritdoc />
public override string BotMention => client.Nickname;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="IrcProvider"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="IrcProvider"/>
/// </summary>
@@ -84,19 +89,21 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// <summary>
/// Construct an <see cref="IrcProvider"/>
/// </summary>
/// <param name="logger">The value of logger</param>
/// <param name="application">The <see cref="IApplication"/> to get the <see cref="IApplication.VersionString"/> from</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="logger">The value of logger</param>
/// <param name="address">The value of <see cref="address"/></param>
/// <param name="port">The value of <see cref="port"/></param>
/// <param name="nickname">The value of <see cref="nickname"/></param>
/// <param name="password">The value of <see cref="password"/></param>
/// <param name="passwordType">The value of <see cref="passwordType"/></param>
/// <param name="useSsl">If <see cref="IrcConnection.UseSsl"/> should be used</param>
public IrcProvider(ILogger<IrcProvider> logger, IApplication application, string address, ushort port, string nickname, string password, IrcPasswordType? passwordType, bool useSsl)
public IrcProvider(IApplication application, IAsyncDelayer asyncDelayer, ILogger<IrcProvider> logger, string address, ushort port, string nickname, string password, IrcPasswordType? passwordType, bool useSsl)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
if (application == null)
throw new ArgumentNullException(nameof(application));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.address = address ?? throw new ArgumentNullException(nameof(address));
this.port = port;
@@ -267,13 +274,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var startTime = DateTimeOffset.Now;
var endTime = DateTimeOffset.Now.AddSeconds(TimeoutSeconds);
cancellationToken.ThrowIfCancellationRequested();
for (; !recievedAck && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult())
var listenTimeSpan = TimeSpan.FromMilliseconds(10);
for (; !recievedAck && DateTimeOffset.Now <= endTime; asyncDelayer.Delay(listenTimeSpan, cancellationToken).GetAwaiter().GetResult())
client.Listen(false);
client.WriteLine("AUTHENTICATE PLAIN", Priority.Critical);
cancellationToken.ThrowIfCancellationRequested();
for (; !recievedPlus && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult())
for (; !recievedPlus && DateTimeOffset.Now <= endTime; asyncDelayer.Delay(listenTimeSpan, cancellationToken).GetAwaiter().GetResult())
client.Listen(false);
//Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196
@@ -103,6 +103,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="InstanceFactory"/>
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// Construct an <see cref="InstanceFactory"/>
/// </summary>
@@ -123,7 +128,8 @@ namespace Tgstation.Server.Host.Components
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/></param>
/// <param name="networkPromptReaper">The value of <see cref="networkPromptReaper"/></param>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/></param>
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider, INetworkPromptReaper networkPromptReaper, IGitHubClientFactory gitHubClientFactory)
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider, INetworkPromptReaper networkPromptReaper, IGitHubClientFactory gitHubClientFactory, IPlatformIdentifier platformIdentifier)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
@@ -142,6 +148,7 @@ namespace Tgstation.Server.Host.Components
this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper));
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
}
/// <inheritdoc />
@@ -156,7 +163,7 @@ namespace Tgstation.Server.Host.Components
var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game");
var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration");
var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, loggerFactory.CreateLogger<StaticFiles.Configuration>());
var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, platformIdentifier, loggerFactory.CreateLogger<StaticFiles.Configuration>());
var eventConsumer = new EventConsumer(configuration);
var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger<DmbFactory>(), metadata.CloneMetadata());
@@ -172,7 +179,7 @@ namespace Tgstation.Server.Host.Components
var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings);
try
{
var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, loggerFactory, metadata.CloneMetadata());
var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, platformIdentifier, loggerFactory, metadata.CloneMetadata());
var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger<ReattachInfoHandler>(), metadata.CloneMetadata());
var watchdog = watchdogFactory.CreateWatchdog(chat, dmbFactory, reattachInfoHandler, configuration, sessionControllerFactory, metadata.CloneMetadata(), metadata.DreamDaemonSettings);
eventConsumer.SetWatchdog(watchdog);
@@ -38,8 +38,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles
{ EventType.RepoPreSynchronize, "PreSynchronize" }
};
static readonly string SystemScriptFileExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "bat" : "sh";
/// <summary>
/// The <see cref="IIOManager"/> for <see cref="Configuration"/>
/// </summary>
@@ -65,6 +63,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// </summary>
readonly IPostWriteHandler postWriteHandler;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for <see cref="Configuration"/>
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="ILogger"/> for <see cref="Configuration"/>
/// </summary>
@@ -83,14 +86,16 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/></param>
/// <param name="processExecutor">The value of <see cref="processExecutor"/></param>
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/></param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, ILogger<Configuration> logger)
public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IPlatformIdentifier platformIdentifier, ILogger<Configuration> logger)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager));
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
semaphore = new SemaphoreSlim(1);
@@ -312,7 +317,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
var fileName = ioManager.GetFileName(x);
bool ignored;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (platformIdentifier.IsWindows)
//need to normalize
ignored = ignoreFiles.Any(y => fileName.ToUpperInvariant() == y.ToUpperInvariant());
else
@@ -438,7 +443,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
//always execute in serial
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, SystemScriptFileExtension, cancellationToken).ConfigureAwait(false);
var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, cancellationToken).ConfigureAwait(false);
var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory);
foreach (var I in files.Select(x => ioManager.GetFileName(x)).Where(x => x.StartsWith(scriptName, StringComparison.Ordinal)))
@@ -63,6 +63,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
readonly INetworkPromptReaper networkPromptReaper;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="SessionControllerFactory"/>
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="SessionControllerFactory"/>
/// </summary>
@@ -105,8 +110,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="chat">The value of <see cref="chat"/></param>
/// <param name="networkPromptReaper">The value of <see cref="networkPromptReaper"/></param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
public SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, INetworkPromptReaper networkPromptReaper, ILoggerFactory loggerFactory, Api.Models.Instance instance)
public SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, INetworkPromptReaper networkPromptReaper, IPlatformIdentifier platformIdentifier, ILoggerFactory loggerFactory, Api.Models.Instance instance)
{
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
this.byond = byond ?? throw new ArgumentNullException(nameof(byond));
@@ -117,6 +123,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
@@ -211,7 +218,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
parameters);
//See #719
var noShellExecute = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var noShellExecute = !platformIdentifier.IsWindows;
//launch dd
var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: noShellExecute);
try
@@ -60,11 +60,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
readonly IDmbFactory dmbFactory;
/// <summary>
/// The <see cref="ILogger{TCategoryName}"/> for the <see cref="Watchdog"/>
/// </summary>
readonly ILogger<Watchdog> logger;
/// <summary>
/// The <see cref="IReattachInfoHandler"/> for the <see cref="Watchdog"/>
/// </summary>
@@ -95,6 +90,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
readonly IRestartRegistration restartRegistration;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="Watchdog"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="ILogger{TCategoryName}"/> for the <see cref="Watchdog"/>
/// </summary>
readonly ILogger<Watchdog> logger;
/// <summary>
/// The <see cref="SemaphoreSlim"/> for the <see cref="Watchdog"/>
/// </summary>
@@ -145,27 +150,29 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="chat">The value of <see cref="chat"/></param>
/// <param name="sessionControllerFactory">The value of <see cref="sessionControllerFactory"/></param>
/// <param name="dmbFactory">The value of <see cref="dmbFactory"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="reattachInfoHandler">The value of <see cref="reattachInfoHandler"/></param>
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
/// <param name="serverControl">The <see cref="IServerControl"/> to populate <see cref="restartRegistration"/> with</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="initialLaunchParameters">The initial value of <see cref="ActiveLaunchParameters"/>. May be modified</param>
/// <param name="instance">The value of <see cref="instance"/></param>
/// <param name="autoStart">The value of <see cref="autoStart"/></param>
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ILogger<Watchdog> logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILogger<Watchdog> logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart)
{
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
ActiveLaunchParameters = initialLaunchParameters ?? throw new ArgumentNullException(nameof(initialLaunchParameters));
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
this.autoStart = autoStart;
@@ -635,7 +642,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
logger.LogWarning("Failed to automatically restart the watchdog! Attempt: {0}, Exception: {1}", retryAttempts, launchException);
var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); //max of one hour, increasing by a power of 2 each time
chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Failed to restart watchdog (Attempt: {0}), retrying in {1} seconds...", retryAttempts, retryDelay), cancellationToken);
await Task.WhenAll(Task.Delay((int)retryDelay, cancellationToken), chatTask).ConfigureAwait(false);
await Task.WhenAll(asyncDelayer.Delay(TimeSpan.FromSeconds(retryDelay), cancellationToken), chatTask).ConfigureAwait(false);
}
}
}
@@ -751,7 +758,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
//definitely not if reattaching though
if (reattachInfo == null && delay.TotalSeconds < AlphaBravoStartupSeperationInterval)
await Task.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false);
await asyncDelayer.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false);
//now bring bravo up
if (!doesntNeedNewDmb)
@@ -36,6 +36,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
readonly IJobManager jobManager;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogFactory"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// Construct a <see cref="WatchdogFactory"/>
/// </summary>
@@ -44,16 +49,18 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
public WatchdogFactory(IServerControl serverControl, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager)
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
public WatchdogFactory(IServerControl serverControl, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager, IAsyncDelayer asyncDelayer)
{
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
}
/// <inheritdoc />
public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, loggerFactory.CreateLogger<Watchdog>(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, settings, instance, settings.AutoStart.Value);
public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, asyncDelayer, loggerFactory.CreateLogger<Watchdog>(), settings, instance, settings.AutoStart.Value);
}
}
@@ -25,6 +25,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// </summary>
const int RecheckDelayMs = 250;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="WindowsNetworkPromptReaper"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="WindowsNetworkPromptReaper"/>
/// </summary>
@@ -78,9 +83,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <summary>
/// Construct a <see cref="WindowsNetworkPromptReaper"/>
/// </summary>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public WindowsNetworkPromptReaper(ILogger<WindowsNetworkPromptReaper> logger)
public WindowsNetworkPromptReaper(IAsyncDelayer asyncDelayer, ILogger<WindowsNetworkPromptReaper> logger)
{
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
registeredProcesses = new List<IProcess>();
@@ -97,7 +104,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromMilliseconds(RecheckDelayMs), cancellationToken).ConfigureAwait(false);
await asyncDelayer.Delay(TimeSpan.FromMilliseconds(RecheckDelayMs), cancellationToken).ConfigureAwait(false);
IntPtr window;
int processId;
@@ -52,6 +52,11 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="AdministrationController"/>
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="UpdatesConfiguration"/> for the <see cref="AdministrationController"/>
/// </summary>
@@ -71,15 +76,17 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="serverUpdater">The value of <see cref="serverUpdater"/></param>
/// <param name="application">The value of <see cref="application"/></param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/></param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="generalConfiguration"/></param>
public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClientFactory gitHubClientFactory, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger<AdministrationController> logger, IOptions<UpdatesConfiguration> updatesConfigurationOptions, IOptions<GeneralConfiguration> generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false)
public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClientFactory gitHubClientFactory, IServerControl serverUpdater, IApplication application, IIOManager ioManager, IPlatformIdentifier platformIdentifier, ILogger<AdministrationController> logger, IOptions<UpdatesConfiguration> updatesConfigurationOptions, IOptions<GeneralConfiguration> generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater));
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
}
@@ -123,7 +130,7 @@ namespace Tgstation.Server.Host.Controllers
{
LatestVersion = greatestVersion,
TrackedRepositoryUrl = repoUrl,
WindowsHost = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
WindowsHost = platformIdentifier.IsWindows
});
}
catch (RateLimitExceededException e)
@@ -40,10 +40,12 @@ namespace Tgstation.Server.Host.Controllers
/// The <see cref="IJobManager"/> for the <see cref="InstanceController"/>
/// </summary>
readonly IJobManager jobManager;
/// <summary>
/// The <see cref="IInstanceManager"/> for the <see cref="InstanceController"/>
/// </summary>
readonly IInstanceManager instanceManager;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="InstanceController"/>
/// </summary>
@@ -54,6 +56,11 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly IApplication application;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="InstanceController"/>
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// Construct a <see cref="InstanceController"/>
/// </summary>
@@ -63,13 +70,15 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="instanceManager">The value of <see cref="instanceManager"/></param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="application">The value of <see cref="application"/></param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager, IApplication application, ILogger<InstanceController> logger) : base(databaseContext, authenticationContextFactory, logger, false)
public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager, IApplication application, IPlatformIdentifier platformIdentifier, ILogger<InstanceController> logger) : base(databaseContext, authenticationContextFactory, logger, false)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
}
void NormalizeModelPath(Api.Models.Instance model, out string absolutePath)
@@ -80,7 +89,7 @@ namespace Tgstation.Server.Host.Controllers
return;
}
absolutePath = ioManager.ResolvePath(model.Path);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (platformIdentifier.IsWindows)
model.Path = absolutePath.ToUpperInvariant();
else
model.Path = absolutePath;
+22 -12
View File
@@ -60,6 +60,11 @@ namespace Tgstation.Server.Host.Core
/// </summary>
readonly TaskCompletionSource<object> startupTcs;
/// <summary>
/// The <see cref="ITokenFactory"/> for the <see cref="Application"/>
/// </summary>
ITokenFactory tokenFactory;
/// <summary>
/// Construct an <see cref="Application"/>
/// </summary>
@@ -97,21 +102,19 @@ namespace Tgstation.Server.Host.Core
//enable options which give us config reloading
services.AddOptions();
//setup stuff for setup wizard
//other stuff needed for for setup wizard and configuration
services.AddSingleton<IIOManager, DefaultIOManager>();
services.AddSingleton<IConsole, IO.Console>();
services.AddSingleton<IDBConnectionFactory, DBConnectionFactory>();
services.AddSingleton<ISetupWizard, SetupWizard>();
//needed here for JWT configuration
//we use a manually instatiated token factory to prevent it from regenerating the signing key after we configure it
services.AddSingleton<ITokenFactory>(new TokenFactory());
services.AddSingleton<IPlatformIdentifier, PlatformIdentifier>();
services.AddSingleton<IAsyncDelayer, AsyncDelayer>();
GeneralConfiguration generalConfiguration;
DatabaseConfiguration databaseConfiguration;
FileLoggingConfiguration fileLoggingConfiguration;
ITokenFactory tokenFactory;
IIOManager ioManager;
IPlatformIdentifier platformIdentifier;
//temporarily build the service provider in it's current state
//do it here so we can run the setup wizard if necessary
@@ -138,8 +141,8 @@ namespace Tgstation.Server.Host.Core
var loggingOptions = provider.GetRequiredService<IOptions<FileLoggingConfiguration>>();
fileLoggingConfiguration = loggingOptions.Value;
tokenFactory = provider.GetRequiredService<ITokenFactory>();
ioManager = provider.GetRequiredService<IIOManager>();
platformIdentifier = provider.GetRequiredService<IPlatformIdentifier>();
}
//setup file logging via serilog
@@ -195,6 +198,8 @@ namespace Tgstation.Server.Host.Core
//configure bearer token validation
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions =>
{
//this line isn't actually run until the first request is made
//at that point tokenFactory will be populated
jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters;
jwtBearerOptions.Events = new JwtBearerEvents
{
@@ -251,10 +256,11 @@ namespace Tgstation.Server.Host.Core
services.AddScoped<IClaimsInjector, ClaimsInjector>();
services.AddSingleton<IIdentityCache, IdentityCache>();
services.AddSingleton<ICryptographySuite, CryptographySuite>();
services.AddSingleton<ITokenFactory, TokenFactory>();
services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
//configure platform specific services
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (platformIdentifier.IsWindows)
{
services.AddSingleton<ISystemIdentityFactory, WindowsSystemIdentityFactory>();
services.AddSingleton<ISymlinkFactory, WindowsSymlinkFactory>();
@@ -303,17 +309,21 @@ namespace Tgstation.Server.Host.Core
/// Configure the <see cref="Application"/>
/// </summary>
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/> to configure</param>
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="Application"/></param>
/// <param name="tokenFactory">The value of <see cref="tokenFactory"/></param>
/// <param name="logger">The <see cref="Microsoft.Extensions.Logging.ILogger"/> for the <see cref="Application"/></param>
/// <param name="serverControl">The <see cref="IServerControl"/> for the application</param>
public void Configure(IApplicationBuilder applicationBuilder, ILogger<Application> logger, IServerControl serverControl)
public void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, ILogger<Application> logger)
{
if (applicationBuilder == null)
throw new ArgumentNullException(nameof(applicationBuilder));
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (serverControl == null)
throw new ArgumentNullException(nameof(serverControl));
this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
if (logger == null)
throw new ArgumentNullException(nameof(logger));
logger.LogInformation(VersionString);
//attempt to restart the server if the configuration changes
@@ -0,0 +1,13 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Host.Core
{
/// <inheritdoc />
sealed class AsyncDelayer : IAsyncDelayer
{
/// <inheritdoc />
public Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken) => Task.Delay(timeSpan, cancellationToken);
}
}
@@ -0,0 +1,20 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Host.Core
{
/// <summary>
/// For waiting asynchronously
/// </summary>
interface IAsyncDelayer
{
/// <summary>
/// Create a <see cref="Task"/> that completes after a given <paramref name="timeSpan"/>
/// </summary>
/// <param name="timeSpan">The <see cref="TimeSpan"/> that must elapse</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,18 @@
namespace Tgstation.Server.Host.Core
{
/// <summary>
/// For identifying the current platform
/// </summary>
public interface IPlatformIdentifier
{
/// <summary>
/// If the current platform is a Windows platform
/// </summary>
bool IsWindows { get; }
/// <summary>
/// The extension of executable script files for the system
/// </summary>
string ScriptFileExtension { get; }
}
}
@@ -0,0 +1,23 @@
using System.Runtime.InteropServices;
namespace Tgstation.Server.Host.Core
{
/// <inheritdoc />
sealed class PlatformIdentifier : IPlatformIdentifier
{
/// <inheritdoc />
public bool IsWindows { get; }
/// <inheritdoc />
public string ScriptFileExtension { get; }
/// <summary>
/// Construct a <see cref="PlatformIdentifier"/>
/// </summary>
public PlatformIdentifier()
{
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
ScriptFileExtension = IsWindows ? "bat" : "sh";
}
}
}
+18 -4
View File
@@ -45,6 +45,16 @@ namespace Tgstation.Server.Host.Core
/// </summary>
readonly IDBConnectionFactory dbConnectionFactory;
/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="SetupWizard"/>
/// </summary>
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="SetupWizard"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="SetupWizard"/>
/// </summary>
@@ -63,15 +73,19 @@ namespace Tgstation.Server.Host.Core
/// <param name="hostingEnvironment">The value of <see cref="hostingEnvironment"/></param>
/// <param name="application">The value of <see cref="application"/></param>
/// <param name="dbConnectionFactory">The value of <see cref="dbConnectionFactory"/></param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/></param>
public SetupWizard(IIOManager ioManager, IConsole console, IHostingEnvironment hostingEnvironment, IApplication application, IDBConnectionFactory dbConnectionFactory, ILogger<SetupWizard> logger, IOptions<GeneralConfiguration> generalConfigurationOptions)
public SetupWizard(IIOManager ioManager, IConsole console, IHostingEnvironment hostingEnvironment, IApplication application, IDBConnectionFactory dbConnectionFactory, IPlatformIdentifier platformIdentifier, IAsyncDelayer asyncDelayer, ILogger<SetupWizard> logger, IOptions<GeneralConfiguration> generalConfigurationOptions)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.console = console ?? throw new ArgumentNullException(nameof(console));
this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.dbConnectionFactory = dbConnectionFactory ?? throw new ArgumentNullException(nameof(dbConnectionFactory));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
}
@@ -170,7 +184,7 @@ namespace Tgstation.Server.Host.Core
var dbExists = await PromptYesNo("Does this database already exist? (y/n): ", cancellationToken).ConfigureAwait(false);
bool useWinAuth;
if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer && platformIdentifier.IsWindows)
useWinAuth = await PromptYesNo("Use Windows Authentication? (y/n): ", cancellationToken).ConfigureAwait(false);
else
useWinAuth = false;
@@ -494,7 +508,7 @@ namespace Tgstation.Server.Host.Core
await console.WriteAsync("Waiting for configuration changes to reload...", true, cancellationToken).ConfigureAwait(false);
//we need to wait for the configuration's file system watcher to read and reload the changes
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
await asyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
}
/// <summary>
@@ -577,7 +591,7 @@ namespace Tgstation.Server.Host.Core
}
//flush the logs to prevent console conflicts
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false);
return true;
@@ -9,18 +9,9 @@ namespace Tgstation.Server.Host.Security
sealed class CryptographySuite : ICryptographySuite
{
/// <summary>
/// Generates a secure set of <see cref="byte"/>s
/// Length in <see cref="byte"/>s of generated base64 secure string
/// </summary>
/// <returns>A secure set of <see cref="byte"/>s</returns>
public static byte[] GetSecureBytes(int amount)
{
using (var rng = new RNGCryptoServiceProvider())
{
var byt = new byte[amount];
rng.GetBytes(byt);
return byt;
}
}
const uint SecureStringLength = 30;
/// <summary>
/// The <see cref="IPasswordHasher{TUser}"/> for the <see cref="CryptographySuite"/>
@@ -31,14 +22,28 @@ namespace Tgstation.Server.Host.Security
/// Construct a <see cref="CryptographySuite"/>
/// </summary>
/// <param name="passwordHasher">The value of <see cref="passwordHasher"/></param>
public CryptographySuite(IPasswordHasher<User> passwordHasher) => this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher));
public CryptographySuite(IPasswordHasher<User> passwordHasher)
{
this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher));
}
/// <inheritdoc />
public byte[] GetSecureBytes(uint amount)
{
using (var rng = new RNGCryptoServiceProvider())
{
var byt = new byte[amount];
rng.GetBytes(byt);
return byt;
}
}
/// <inheritdoc />
public void SetUserPassword(User user, string newPassword, bool newUser)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
if (String.IsNullOrEmpty(newPassword))
if (newPassword == null)
throw new ArgumentNullException(nameof(newPassword));
user.PasswordHash = passwordHasher.HashPassword(user, newPassword);
if (!newUser)
@@ -53,14 +58,13 @@ namespace Tgstation.Server.Host.Security
case PasswordVerificationResult.Failed:
return false;
case PasswordVerificationResult.SuccessRehashNeeded:
user.PasswordHash = passwordHasher.HashPassword(user, password);
//don't update LastPasswordUpdate since it hasn't actually changed
SetUserPassword(user, password, false);
break;
}
return true;
}
/// <inheritdoc />
public string GetSecureString() => Convert.ToBase64String(GetSecureBytes(30));
public string GetSecureString() => Convert.ToBase64String(GetSecureBytes(SecureStringLength));
}
}
@@ -7,6 +7,13 @@ namespace Tgstation.Server.Host.Security
/// </summary>
public interface ICryptographySuite
{
/// <summary>
/// Generates a secure set of <see cref="byte"/>s
/// </summary>
/// <param name="amount">The amount of <see cref="byte"/>s to generate</param>
/// <returns>A secure set of <see cref="byte"/>s</returns>
byte[] GetSecureBytes(uint amount);
/// <summary>
/// Sets a <see cref="User.PasswordHash"/> for a given <paramref name="user"/>
/// </summary>
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Security
@@ -9,6 +10,11 @@ namespace Tgstation.Server.Host.Security
/// <inheritdoc />
sealed class IdentityCache : IIdentityCache, IDisposable
{
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="IdentityCache"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="IdentityCache"/>
/// </summary>
@@ -22,8 +28,11 @@ namespace Tgstation.Server.Host.Security
/// <summary>
/// Construct an <see cref="IdentityCache"/>
/// </summary>
public IdentityCache(ILogger<IdentityCache> logger)
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public IdentityCache(IAsyncDelayer asyncDelayer, ILogger<IdentityCache> logger)
{
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
cachedIdentities = new Dictionary<long, IdentityCacheObject>();
@@ -54,7 +63,7 @@ namespace Tgstation.Server.Host.Security
logger.LogTrace("Expiring previously cached identity...");
identCache.Dispose(); //also clears it out
}
identCache = new IdentityCacheObject(systemIdentity.Clone(), () =>
identCache = new IdentityCacheObject(systemIdentity.Clone(), asyncDelayer, () =>
{
logger.LogDebug("Expiring system identity cache for user {1}", uid, user.Id);
lock (cachedIdentities)
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security
{
@@ -28,11 +29,16 @@ namespace Tgstation.Server.Host.Security
/// Construct an <see cref="IdentityCache"/>
/// </summary>
/// <param name="systemIdentity">The value of <see cref="SystemIdentity"/></param>
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> used to delay the expiry</param>
/// <param name="onExpiry">The <see cref="Action"/> to take on expiry</param>
/// <param name="expiry">The <see cref="DateTimeOffset"/></param>
public IdentityCacheObject(ISystemIdentity systemIdentity, Action onExpiry, DateTimeOffset expiry)
public IdentityCacheObject(ISystemIdentity systemIdentity, IAsyncDelayer asyncDelayer, Action onExpiry, DateTimeOffset expiry)
{
SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity));
if (asyncDelayer == null)
throw new ArgumentNullException(nameof(asyncDelayer));
if (onExpiry == null)
throw new ArgumentNullException(nameof(onExpiry));
var now = DateTimeOffset.Now;
@@ -46,7 +52,7 @@ namespace Tgstation.Server.Host.Security
using (SystemIdentity)
try
{
await Task.Delay(expiry - now, cancellationToken).ConfigureAwait(false);
await asyncDelayer.Delay(expiry - now, cancellationToken).ConfigureAwait(false);
}
finally
{
@@ -7,6 +7,7 @@ using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Security
{
@@ -16,20 +17,37 @@ namespace Tgstation.Server.Host.Security
/// <summary>
/// Amount of minutes until generated <see cref="Token"/>s expire
/// </summary>
const int TokenExpiryMinutes = 15;
const uint TokenExpiryMinutes = 15;
/// <summary>
/// Amount of minutes to skew the clock for <see cref="Token"/> validation
/// </summary>
const uint TokenClockSkewMinutes = 1;
/// <summary>
/// Amount of bytes to use in the <see cref="TokenValidationParameters.IssuerSigningKey"/>
/// </summary>
const uint TokenSigningKeyByteAmount = 256;
/// <inheritdoc />
public TokenValidationParameters ValidationParameters { get; }
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="TokenFactory"/>
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// Construct a <see cref="TokenFactory"/>
/// </summary>
public TokenFactory()
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="cryptographySuite">The <see cref="ICryptographySuite"/> used for generating the <see cref="ValidationParameters"/></param>
public TokenFactory(IAsyncDelayer asyncDelayer, ICryptographySuite cryptographySuite)
{
ValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(CryptographySuite.GetSecureBytes(256)),
IssuerSigningKey = new SymmetricSecurityKey(cryptographySuite.GetSecureBytes(TokenSigningKeyByteAmount)),
ValidateIssuer = true,
ValidIssuer = Assembly.GetExecutingAssembly().GetName().Name,
@@ -38,12 +56,14 @@ namespace Tgstation.Server.Host.Security
ValidateAudience = true,
ValidAudience = typeof(Token).Assembly.GetName().Name,
ClockSkew = TimeSpan.FromMinutes(1),
ClockSkew = TimeSpan.FromMinutes(TokenClockSkewMinutes),
RequireSignedTokens = true,
RequireExpirationTime = true
};
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
}
/// <inheritdoc />
@@ -56,9 +76,13 @@ namespace Tgstation.Server.Host.Security
var nowUnix = now.ToUnixTimeSeconds();
//this prevents validation conflicts down the line
//tldr we can (theoretically) send a token the same second we receive it
//since unix time rounds down, it looks like it came from before the user changed their password
//this happens occasionally in unit tests
//just delay a second so we can force a round up
var lpuUnix = user.LastPasswordUpdate?.ToUnixTimeSeconds();
if (nowUnix == lpuUnix)
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
var expiry = now.AddMinutes(TokenExpiryMinutes);
var claims = new Claim[]
@@ -19,47 +19,50 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests
[TestMethod]
public void TestConstruction()
{
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(null, null, null, null, null, null, null, null, null, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(null, null, null, null, null, null, null, null, null, null, null, null, null, default));
var mockChat = new Mock<IChat>();
mockChat.Setup(x => x.RegisterCommandHandler(It.IsNotNull<ICustomCommandHandler>())).Verifiable();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, null, null, null, null, null, null, null, null, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, null, null, null, null, null, null, null, null, null, null, null, null, default));
var mockSessionControllerFactory = new Mock<ISessionControllerFactory>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, null, null, null, null, null, null, null, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, null, null, null, null, null, null, null, null, null, null, null, default));
var mockDmbFactory = new Mock<IDmbFactory>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, null, null, null, null, null, null, null, null, null, null, default));
var mockLogger = new Mock<ILogger<Watchdog>>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, null, null, null, null, null, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, null, null, null, null, null, null, null, null, null, null, default));
var mockReattachInfoHandler = new Mock<IReattachInfoHandler>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, null, null, null, null, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, null, null, null, null, null, null, null, null, null, default));
var mockDatabaseContextFactory = new Mock<IDatabaseContextFactory>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, null, null, null, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, null, null, null, null, null, null, null, null, default));
var mockByondTopicSender = new Mock<IByondTopicSender>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, null, null, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, null, null, null, null, null, null, null, default));
var mockEventConsumer = new Mock<IEventConsumer>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, null, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, null, null, null, null, null, null, default));
var mockJobManager = new Mock<IJobManager>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, null, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, null, null, null, null, null, default));
var mockRestartRegistration = new Mock<IRestartRegistration>();
mockRestartRegistration.Setup(x => x.Dispose()).Verifiable();
var mockServerControl = new Mock<IServerControl>();
mockServerControl.Setup(x => x.RegisterForRestart(It.IsNotNull<IRestartHandler>())).Returns(mockRestartRegistration.Object).Verifiable();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, null, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, null, null, null, null, default));
var mockAsyncDelayer = new Mock<IAsyncDelayer>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, null, null, null, default));
var mockLogger = new Mock<ILogger<Watchdog>>();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, null, null, default));
var mockLaunchParameters = new DreamDaemonLaunchParameters();
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockLaunchParameters, null, default));
Assert.ThrowsException<ArgumentNullException>(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, null, default));
var mockInstance = new Models.Instance();
new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockLaunchParameters, mockInstance, default).Dispose();
new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, mockInstance, default).Dispose();
mockRestartRegistration.VerifyAll();
mockServerControl.VerifyAll();
@@ -85,8 +88,9 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests
mockServerControl.Setup(x => x.RegisterForRestart(It.IsNotNull<IRestartHandler>())).Returns(mockRestartRegistration.Object).Verifiable();
var mockLaunchParameters = new DreamDaemonLaunchParameters();
var mockInstance = new Models.Instance();
var mockAsyncDelayer = new Mock<IAsyncDelayer>();
using (var wd = new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockLaunchParameters, mockInstance, default))
using (var wd = new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, mockInstance, default))
using (var cts = new CancellationTokenSource())
{
var mockCompileJob = new Models.CompileJob();
@@ -115,6 +119,7 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests
sessionsToVerify.Add(mockSession);
return Task.FromResult(mockSession.Object);
}).Verifiable();
mockAsyncDelayer.Setup(x => x.Delay(It.IsAny<TimeSpan>(), cancellationToken)).Returns(Task.CompletedTask).Verifiable();
cts.CancelAfter(TimeSpan.FromSeconds(15));
@@ -138,6 +143,7 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests
mockRestartRegistration.VerifyAll();
mockServerControl.VerifyAll();
mockChat.VerifyAll();
mockAsyncDelayer.VerifyAll();
}
}
}
@@ -2,7 +2,6 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
@@ -11,6 +10,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Core.Tests
{
@@ -32,13 +32,16 @@ namespace Tgstation.Server.Host.Core.Tests
var app = new Application(mockConfiguration.Object, mockHostingEnvironment.Object);
Assert.ThrowsException<ArgumentNullException>(() => app.ConfigureServices(null));
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(null, null, null, null));
var mockAppBuilder = new Mock<IApplicationBuilder>();
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, null, null));
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, null, null, null));
var mockLogger = new Mock<ILogger<Application>>();
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockLogger.Object, null));
var mockServerControl = new Mock<IServerControl>();
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null));
var mockTokenFactory = new Mock<ITokenFactory>();
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null));
}
class MockSetupWizard : ISetupWizard
@@ -0,0 +1,32 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Host.Core.Tests
{
[TestClass]
public sealed class TestAsyncDelayer
{
[TestMethod]
public async Task TestDelay()
{
var delayer = new AsyncDelayer();
var startDelay = delayer.Delay(TimeSpan.FromSeconds(1), default);
var checkDelay = Task.Delay(TimeSpan.FromSeconds(1) - TimeSpan.FromMilliseconds(10), default);
await startDelay.ConfigureAwait(false);
Assert.IsTrue(checkDelay.IsCompleted);
}
[TestMethod]
public async Task TestCancel()
{
var delayer = new AsyncDelayer();
using (var cts = new CancellationTokenSource())
{
cts.Cancel();
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => delayer.Delay(TimeSpan.FromSeconds(1), cts.Token)).ConfigureAwait(false);
}
}
}
}
@@ -0,0 +1,24 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Runtime.InteropServices;
namespace Tgstation.Server.Host.Core.Tests
{
[TestClass]
public sealed class TestPlatformIdentifier
{
[TestMethod]
public void TestCorrectPlatform()
{
var identifier = new PlatformIdentifier();
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
const string WindowsScriptExtension = "bat";
const string PosixScriptExtension = "sh";
Assert.AreEqual(isWindows, identifier.IsWindows);
Assert.AreEqual(isWindows ? WindowsScriptExtension : PosixScriptExtension, identifier.ScriptFileExtension);
}
}
}
@@ -22,19 +22,23 @@ namespace Tgstation.Server.Host.Core.Tests
[TestMethod]
public void TestConstructionThrows()
{
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(null, null, null, null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(null, null, null, null, null, null, null, null, null));
var mockIOManager = new Mock<IIOManager>();
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, null, null, null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, null, null, null, null, null, null, null, null));
var mockConsole = new Mock<IConsole>();
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, null, null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, null, null, null, null, null, null, null));
var mockHostingEnvironment = new Mock<IHostingEnvironment>();
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, null, null, null, null, null, null));
var mockApplication = new Mock<IApplication>();
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, null, null, null, null, null));
var mockDBConnectionFactory = new Mock<IDBConnectionFactory>();
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, null, null, null, null));
var mockPlatformIdentifier = new Mock<IPlatformIdentifier>();
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, null, null, null));
var mockAsyncDelayer = new Mock<IAsyncDelayer>();
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, mockAsyncDelayer.Object, null, null));
var mockLogger = new Mock<ILogger<SetupWizard>>();
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockLogger.Object, null));
Assert.ThrowsException<ArgumentNullException>(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, mockAsyncDelayer.Object, mockLogger.Object, null));
}
[TestMethod]
@@ -47,6 +51,8 @@ namespace Tgstation.Server.Host.Core.Tests
var mockDBConnectionFactory = new Mock<IDBConnectionFactory>();
var mockLogger = new Mock<ILogger<SetupWizard>>();
var mockGeneralConfigurationOptions = new Mock<IOptions<GeneralConfiguration>>();
var mockPlatformIdentifier = new Mock<IPlatformIdentifier>();
var mockAsyncDelayer = new Mock<IAsyncDelayer>();
var testGeneralConfig = new GeneralConfiguration
{
@@ -54,7 +60,10 @@ namespace Tgstation.Server.Host.Core.Tests
};
mockGeneralConfigurationOptions.SetupGet(x => x.Value).Returns(testGeneralConfig).Verifiable();
var wizard = new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockLogger.Object, mockGeneralConfigurationOptions.Object);
var wizard = new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, mockAsyncDelayer.Object, mockLogger.Object, mockGeneralConfigurationOptions.Object);
mockPlatformIdentifier.SetupGet(x => x.IsWindows).Returns(true).Verifiable();
mockAsyncDelayer.Setup(x => x.Delay(It.IsAny<TimeSpan>(), It.IsAny<CancellationToken>())).Returns(Task.CompletedTask).Verifiable();
Assert.IsFalse(await wizard.CheckRunWizard(default).ConfigureAwait(false));
@@ -116,18 +125,8 @@ namespace Tgstation.Server.Host.Core.Tests
"this isn't validated",
"nor is this",
"no",
};
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
//test winauth
finalInputSequence.Add("yes");
else
finalInputSequence.AddRange(new List<string>
{
"username",
"password"
});
finalInputSequence.AddRange(new List<string>
{
"yes",
//sql server will always fail so reconfigure with maria
nameof(DatabaseType.MariaDB),
"bleh",
@@ -183,7 +182,7 @@ namespace Tgstation.Server.Host.Core.Tests
"fake",
"None",
"Critical"
});
};
var inputPos = 0;
@@ -234,6 +233,8 @@ namespace Tgstation.Server.Host.Core.Tests
mockUglyDbConnection.VerifyAll();
mockDBConnectionFactory.VerifyAll();
mockApplication.VerifyAll();
mockPlatformIdentifier.VerifyAll();
mockAsyncDelayer.VerifyAll();
}
}
}