diff --git a/README.md b/README.md index e4cd2d0800..6c02c8f7ca 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Note that the ratio of application installations to databases is 1:1. Do not att For the Windows service version start the `tgstation-server-4` service. If it fails to start, check the Windows event log under Windows/Application for entries from tgstation-server-4 for errors. -For the console version run `dotnet Tgstation.Server.Host.Console.dll` in the installation directory. The `tgs.bat` and `tgs.sh` shell scripts are shortcuts for this. If on Windows and you wish to install byond versions >= 512.1427 you must do this as admin to give the server permission to install the required DirectX dependency +For the console version run `dotnet Tgstation.Server.Host.Console.dll` in the installation directory. The `tgs.bat` and `tgs.sh` shell scripts are shortcuts for this. If on Windows, you must do this as admin to give the server permission to install the required DirectX dependency for BYOND as well as create symlinks. For the docker version run `docker start tgs` diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 77d2db004c..9ebd873506 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0 + 4.0.1 true Cyberboss /tg/station 13 diff --git a/src/Tgstation.Server.Client/UnrecognizedResponseException.cs b/src/Tgstation.Server.Client/UnrecognizedResponseException.cs index 24dfa9227d..56c5578f96 100644 --- a/src/Tgstation.Server.Client/UnrecognizedResponseException.cs +++ b/src/Tgstation.Server.Client/UnrecognizedResponseException.cs @@ -5,6 +5,9 @@ using Tgstation.Server.Api.Models; namespace Tgstation.Server.Client { + /// + /// Occurs when a response is received that did not deserialize to one of the expected + /// sealed class UnrecognizedResponseException : ClientException { /// diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index 793140f189..39751be2d8 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -4,8 +4,7 @@ Exe netcoreapp2.1 Full - 4.0.0.4 - 4.0.0.4 + 4.0.0.5 diff --git a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs index 32a4208542..d336b8f403 100644 --- a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs +++ b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs @@ -7,5 +7,5 @@ using System.Runtime.InteropServices; [assembly: Guid("29927416-3b78-49a7-a560-5ccaa638b6b4")] [assembly: InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")] -[assembly: AssemblyVersion("4.0.0.4")] -[assembly: AssemblyFileVersion("4.0.0.4")] +[assembly: AssemblyVersion("4.0.0.5")] +[assembly: AssemblyFileVersion("4.0.0.5")] diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index 7c70efa5fd..bc54f79353 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -4,8 +4,7 @@ netstandard2.0 Full false - 4.0.0.4 - 4.0.0.4 + 4.0.0.5 diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs index 3431ede531..3e33e03e0c 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs @@ -6,13 +6,26 @@ namespace Tgstation.Server.Host.Components.Byond sealed class ByondExecutableLock : IByondExecutableLock { /// - public Version Version { get; set; } + public Version Version { get; } /// - public string DreamDaemonPath { get; set; } + public string DreamDaemonPath { get; } /// - public string DreamMakerPath { get; set; } + public string DreamMakerPath { get; } + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + public ByondExecutableLock(Version version, string dreamDaemonPath, string dreamMakerPath) + { + Version = version ?? throw new ArgumentNullException(nameof(version)); + dreamDaemonPath = dreamDaemonPath ?? throw new ArgumentNullException(nameof(dreamDaemonPath)); + dreamMakerPath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath)); + } //at one point in design, byond versions were to delete themselves if they werent the active version //That changed at some point so these functions are intentioanlly left blank diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index e152c2ddca..8c597e7d23 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -197,12 +197,7 @@ namespace Tgstation.Server.Host.Components.Byond var versionKey = VersionKey(versionToUse); - return new ByondExecutableLock - { - DreamDaemonPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamDaemonName)), - DreamMakerPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamMakerName)), - Version = versionToUse - }; + return new ByondExecutableLock(versionToUse, ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamDaemonName)), ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamMakerName))); } /// diff --git a/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs b/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs index 38dd416d11..23c2cdb7f2 100644 --- a/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs +++ b/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs @@ -2,6 +2,7 @@ using LibGit2Sharp.Handlers; using Microsoft.Extensions.Logging; using System; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Components.Repository { diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 1365a98810..bac6bdafa5 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Repository diff --git a/src/Tgstation.Server.Host/Core/ServerSideModifications.cs b/src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs similarity index 100% rename from src/Tgstation.Server.Host/Core/ServerSideModifications.cs rename to src/Tgstation.Server.Host/Components/StaticFiles/ServerSideModifications.cs diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs index 2298971e6a..ef11168489 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs @@ -1,8 +1,8 @@ -using Microsoft.Extensions.Hosting; +using BetterWin32Errors; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -49,10 +49,10 @@ namespace Tgstation.Server.Host.Components.Watchdog { var gcChildhandlesList = GCHandle.FromIntPtr(lParam); - if (gcChildhandlesList == null || gcChildhandlesList.Target == null) + if (gcChildhandlesList.Target == null) return false; - var childHandles = (List )gcChildhandlesList.Target; + var childHandles = (List)gcChildhandlesList.Target; childHandles.Add(hWnd); return true; @@ -61,12 +61,10 @@ namespace Tgstation.Server.Host.Components.Watchdog static List GetAllChildHandles(IntPtr main) { var childHandles = new List(); - var gcChildhandlesList = GCHandle.Alloc(childHandles); - var pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList); - try { + var pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList); NativeMethods.EnumWindowProc childProc = new NativeMethods.EnumWindowProc(EnumWindow); NativeMethods.EnumChildWindows(main, childProc, pointerChildHandlesList); } @@ -74,7 +72,6 @@ namespace Tgstation.Server.Host.Components.Watchdog { gcChildhandlesList.Free(); } - return childHandles; } @@ -129,7 +126,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (NativeMethods.GetWindowText(I, stringBuilder, MaxLength) == 0) { - logger.LogWarning("Error calling GetWindowText! Exception: {0}", new Win32Exception(Marshal.GetLastWin32Error())); + logger.LogWarning("Error calling GetWindowText! Exception: {0}", new Win32Exception()); continue; } @@ -152,7 +149,10 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogDebug("Unable to find \"Yes\" button for \"Network Accessibility\" window in owned process {0}!", processId); } } - catch (OperationCanceledException) { } + catch (OperationCanceledException) + { + logger.LogTrace("Cancelled!"); + } finally { logger.LogDebug("Exiting network prompt reaper..."); diff --git a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs index 69c5287aa9..fc23255d12 100644 --- a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs +++ b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs @@ -28,6 +28,9 @@ namespace Tgstation.Server.Host.Core /// public async Task UseContext(Func operation) { + if (operation == null) + throw new ArgumentNullException(nameof(operation)); + using (var scope = scopeFactory.CreateScope()) await operation(scope.ServiceProvider.GetRequiredService()).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Core/JobException.cs b/src/Tgstation.Server.Host/Core/JobException.cs index 88877c9548..fbf4449365 100644 --- a/src/Tgstation.Server.Host/Core/JobException.cs +++ b/src/Tgstation.Server.Host/Core/JobException.cs @@ -1,6 +1,6 @@ using System; -namespace Tgstation.Server.Host +namespace Tgstation.Server.Host.Core { /// /// Operation exceptions thrown from the context of a diff --git a/src/Tgstation.Server.Host/Core/RestartRegistration.cs b/src/Tgstation.Server.Host/Core/RestartRegistration.cs index 8df2cf3ad2..8e6fca271e 100644 --- a/src/Tgstation.Server.Host/Core/RestartRegistration.cs +++ b/src/Tgstation.Server.Host/Core/RestartRegistration.cs @@ -1,5 +1,4 @@ using System; -using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Core { diff --git a/src/Tgstation.Server.Host/Core/IPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs similarity index 86% rename from src/Tgstation.Server.Host/Core/IPostWriteHandler.cs rename to src/Tgstation.Server.Host/IO/IPostWriteHandler.cs index 7209b44761..7686d742c8 100644 --- a/src/Tgstation.Server.Host/Core/IPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/IPostWriteHandler.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.IO { interface IPostWriteHandler { diff --git a/src/Tgstation.Server.Host/Core/PosixPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs similarity index 94% rename from src/Tgstation.Server.Host/Core/PosixPostWriteHandler.cs rename to src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs index d121d2bca8..d656e0fa99 100644 --- a/src/Tgstation.Server.Host/Core/PosixPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs @@ -1,7 +1,7 @@ using Mono.Unix; using Mono.Unix.Native; -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.IO { /// /// for POSIX systems diff --git a/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs index 90a00410be..4499d5ac13 100644 --- a/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs @@ -1,4 +1,5 @@ using Mono.Unix; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -13,6 +14,11 @@ namespace Tgstation.Server.Host.IO /// public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { + if (targetPath == null) + throw new ArgumentNullException(nameof(targetPath)); + if (linkPath == null) + throw new ArgumentNullException(nameof(linkPath)); + UnixFileSystemInfo fsInfo; var isFile = File.Exists(targetPath); cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Tgstation.Server.Host/Core/WindowsPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs similarity index 84% rename from src/Tgstation.Server.Host/Core/WindowsPostWriteHandler.cs rename to src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs index 42ed2cdf1b..6d149644fa 100644 --- a/src/Tgstation.Server.Host/Core/WindowsPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/WindowsPostWriteHandler.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Core +namespace Tgstation.Server.Host.IO { /// /// for Windows systems diff --git a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs index 0fbacb6341..13aae902c2 100644 --- a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs @@ -1,6 +1,6 @@ -using System.ComponentModel; +using BetterWin32Errors; +using System; using System.IO; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -14,15 +14,27 @@ namespace Tgstation.Server.Host.IO /// public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { + if (targetPath == null) + throw new ArgumentNullException(nameof(targetPath)); + if (linkPath == null) + throw new ArgumentNullException(nameof(linkPath)); + //check if its not a file - var flags = File.Exists(targetPath) ? 0 : 1; //SYMBOLIC_LINK_FLAG_DIRECTORY -#if DEBUG - flags |= 2; //SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE on win10 1607+ developer mode -#endif + var flags = File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory; + + flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate; cancellationToken.ThrowIfCancellationRequested(); if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) - throw new Win32Exception(Marshal.GetLastWin32Error()); + { + if (Win32Exception.GetLastWin32Error() == Win32Error.ERROR_INVALID_PARAMETER) //SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE isn't supported + { + flags &= ~NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate; + if (NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) + return; + } + throw new Win32Exception(); + } }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs index afe778312a..bcc1865686 100644 --- a/src/Tgstation.Server.Host/NativeMethods.cs +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -9,6 +9,17 @@ namespace Tgstation.Server.Host /// static class NativeMethods { + /// + /// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createsymboliclinka#parameters + /// + [Flags] + public enum CreateSymbolicLinkFlags : int + { + None = 0, + Directory = 1, + AllowUnprivilegedCreate = 2 + } + /// /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowthreadprocessid /// @@ -35,8 +46,7 @@ namespace Tgstation.Server.Host /// /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enumchildwindows /// - [DllImport("user32")] - [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr lParam); /// @@ -55,6 +65,6 @@ namespace Tgstation.Server.Host /// See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createsymboliclinkw /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); + public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, CreateSymbolicLinkFlags dwFlags); } } diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index b8b34e0553..cefdfd8897 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore; +using System; namespace Tgstation.Server.Host { @@ -6,6 +7,6 @@ namespace Tgstation.Server.Host public sealed class ServerFactory : IServerFactory { /// - public IServer CreateServer(string[] args, string updatePath) => new Server(WebHost.CreateDefaultBuilder(args), updatePath); + public IServer CreateServer(string[] args, string updatePath) => new Server(WebHost.CreateDefaultBuilder(args ?? throw new ArgumentNullException(nameof(args))), updatePath); } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index f2234badd7..9d33cd974a 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -3,8 +3,7 @@ netcoreapp2.1 Full - 4.0.0.4 - 4.0.0.4 + 4.0.0.5 @@ -21,6 +20,7 @@ + diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestDatabaseContextFactory.cs b/tests/Tgstation.Server.Host.Tests/Core/TestDatabaseContextFactory.cs new file mode 100644 index 0000000000..7585c8a300 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Core/TestDatabaseContextFactory.cs @@ -0,0 +1,56 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; + +namespace Tgstation.Server.Host.Core.Tests +{ + [TestClass] + public sealed class TestDatabaseContextFactory + { + [TestMethod] + public void TestConstructionThrows() + { + Assert.ThrowsException(() => new DatabaseContextFactory(null)); + var mockProvider = new Mock(); + mockProvider.Setup(x => x.GetService(typeof(IDatabaseContext))).Verifiable(); + var mockScope = new Mock(); + mockScope.SetupGet(x => x.ServiceProvider).Returns(mockProvider.Object).Verifiable(); + var mockScopeFactory = new Mock(); + mockScopeFactory.Setup(x => x.CreateScope()).Returns(mockScope.Object).Verifiable(); + Assert.ThrowsException(() => new DatabaseContextFactory(mockScopeFactory.Object)); + mockScopeFactory.VerifyAll(); + mockScope.VerifyAll(); + mockProvider.VerifyAll(); + } + + [TestMethod] + public async Task TestWorks() + { + var mockDatabase = new Mock(); + var mockProvider = new Mock(); + var mockDbo = mockDatabase.Object; + mockProvider.Setup(x => x.GetService(typeof(IDatabaseContext))).Returns(mockDbo).Verifiable(); + var mockScope = new Mock(); + mockScope.SetupGet(x => x.ServiceProvider).Returns(mockProvider.Object).Verifiable(); + var mockScopeFactory = new Mock(); + mockScopeFactory.Setup(x => x.CreateScope()).Returns(mockScope.Object).Verifiable(); + + var factory = new DatabaseContextFactory(mockScopeFactory.Object); + + await Assert.ThrowsExceptionAsync(() => factory.UseContext(null)).ConfigureAwait(false); + + await factory.UseContext(context => + { + Assert.AreSame(mockDbo, context); + return Task.CompletedTask; + }); + + mockScopeFactory.VerifyAll(); + mockScope.VerifyAll(); + mockProvider.VerifyAll(); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs b/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs new file mode 100644 index 0000000000..dc8cf7b0b4 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Core/TestGitHubClientFactory.cs @@ -0,0 +1,55 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Octokit; +using System; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core.Tests +{ + [TestClass] + public sealed class TestGitHubClientFactory + { + [TestMethod] + public void TestContruction() => Assert.ThrowsException(() => new GitHubClientFactory(null)); + + [TestMethod] + public async Task TestCreateBasicClient() + { + var mockApp = new Mock(); + mockApp.SetupGet(x => x.Version).Returns(new Version()).Verifiable(); + mockApp.SetupGet(x => x.VersionPrefix).Returns("TGSTests").Verifiable(); + + var factory = new GitHubClientFactory(mockApp.Object); + + var client = factory.CreateClient(); + Assert.IsNotNull(client); + var credentials = await client.Connection.CredentialStore.GetCredentials().ConfigureAwait(false); + + Assert.AreEqual(AuthenticationType.Anonymous, credentials.AuthenticationType); + + + mockApp.VerifyAll(); + } + + [TestMethod] + public async Task TestCreateTokenClient() + { + var mockApp = new Mock(); + mockApp.SetupGet(x => x.Version).Returns(new Version()).Verifiable(); + mockApp.SetupGet(x => x.VersionPrefix).Returns("TGSTests").Verifiable(); + + var factory = new GitHubClientFactory(mockApp.Object); + + Assert.ThrowsException(() => factory.CreateClient(null)); + + var client = factory.CreateClient("asdf"); + Assert.IsNotNull(client); + + var credentials = await client.Connection.CredentialStore.GetCredentials().ConfigureAwait(false); + + Assert.AreEqual(AuthenticationType.Oauth, credentials.AuthenticationType); + + mockApp.VerifyAll(); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestJobException.cs b/tests/Tgstation.Server.Host.Tests/Core/TestJobException.cs new file mode 100644 index 0000000000..9f5abc5235 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Core/TestJobException.cs @@ -0,0 +1,17 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Tgstation.Server.Host.Core.Tests +{ + [TestClass] + public sealed class TestJobException + { + [TestMethod] + public void TestConstruction() + { + new JobException(); + new JobException("Message"); + new JobException("Message", new Exception()); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/IO/TestSymlinkFactory.cs b/tests/Tgstation.Server.Host.Tests/IO/TestSymlinkFactory.cs new file mode 100644 index 0000000000..0b8b6fbfc5 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/IO/TestSymlinkFactory.cs @@ -0,0 +1,102 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.IO.Tests +{ + [TestClass] + public sealed class TestSymlinkFactory + { + ISymlinkFactory symlinkFactory; + + [TestInitialize] + public void SelectFactory() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + symlinkFactory = new WindowsSymlinkFactory(); + else + symlinkFactory = new PosixSymlinkFactory(); + } + + [TestMethod] + public async Task TestFileWorks() + { + const string Text = "Hello world"; + string f2 = null; + var f1 = Path.GetTempFileName(); + try + { + f2 = f1 + ".linked"; + File.WriteAllText(f1, Text); + + await Assert.ThrowsExceptionAsync(() => symlinkFactory.CreateSymbolicLink(null, null, default)).ConfigureAwait(false); + await Assert.ThrowsExceptionAsync(() => symlinkFactory.CreateSymbolicLink(f1, null, default)).ConfigureAwait(false); + + await symlinkFactory.CreateSymbolicLink(f1, f2, default).ConfigureAwait(false); + Assert.IsTrue(File.Exists(f2)); + + var f2Contents = File.ReadAllText(f2); + Assert.AreEqual(Text, f2Contents); + } + finally + { + File.Delete(f2); + File.Delete(f1); + } + } + + [TestMethod] + public async Task TestDirectoryWorks() + { + const string FileName = "TestFile.txt"; + const string Text = "Hello world"; + string f2 = null; + var f1 = Path.GetTempFileName(); + File.Delete(f1); + Directory.CreateDirectory(f1); + try + { + f2 = f1 + ".linked"; + var p1 = Path.Combine(f1, FileName); + File.WriteAllText(p1, Text); + + await Assert.ThrowsExceptionAsync(() => symlinkFactory.CreateSymbolicLink(null, null, default)).ConfigureAwait(false); + await Assert.ThrowsExceptionAsync(() => symlinkFactory.CreateSymbolicLink(f1, null, default)).ConfigureAwait(false); + + await symlinkFactory.CreateSymbolicLink(f1, f2, default).ConfigureAwait(false); + + var p2 = Path.Combine(f2, FileName); + Assert.IsTrue(File.Exists(p2)); + + var f2Contents = File.ReadAllText(p2); + Assert.AreEqual(Text, f2Contents); + + File.Delete(p2); + Assert.IsFalse(File.Exists(p1)); + } + finally + { + Directory.Delete(f2, true); + Directory.Delete(f1, true); + } + } + + [TestMethod] + public async Task TestFailsProperly() + { + const string BadPath = "/../../?>O(UF+}P{{??>/////"; + + try + { + await symlinkFactory.CreateSymbolicLink(BadPath, BadPath, default).ConfigureAwait(false); + } + catch + { + return; + } + Assert.Fail("No exception thrown!"); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs b/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs new file mode 100644 index 0000000000..1334ae87c9 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/TestServerFactory.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Tgstation.Server.Host.Tests +{ + /// + /// Tests for + /// + [TestClass] + public sealed class TestServerFactory + { + [TestMethod] + public void TestWorksWithoutUpdatePath() + { + var factory = new ServerFactory(); + + Assert.ThrowsException(() => factory.CreateServer(null, null)); + factory.CreateServer(Array.Empty(), null); + } + + [TestMethod] + public void TestWorksWithUpdatePath() + { + var factory = new ServerFactory(); + const string Path = "/test"; + + Assert.ThrowsException(() => factory.CreateServer(null, null)); + Assert.ThrowsException(() => factory.CreateServer(null, Path)); + factory.CreateServer(Array.Empty(), Path); + } + } +}