Various cleanups and test parallelization

- Fix a bunch of linter messages
- Document whitebox testing
- Fix race condition in deployment test with BYOND install
This commit is contained in:
Dominion
2023-04-16 23:19:57 -04:00
parent fce684f407
commit 9e72e47bbc
13 changed files with 140 additions and 113 deletions
+2
View File
@@ -107,7 +107,9 @@ namespace Tgstation.Server.Client
}
#pragma warning disable IDE0010 // Add missing cases
#pragma warning disable IDE0066 // Convert switch statement to expression
switch (response.StatusCode)
#pragma warning restore IDE0066 // Convert switch statement to expression
#pragma warning restore IDE0010 // Add missing cases
{
case HttpStatusCode.UpgradeRequired:
@@ -358,7 +358,7 @@ namespace Tgstation.Server.Host.Components.Deployment
// The difficulty with compile jobs is they have a two part commit
await databaseContext.Save(cancellationToken);
logger.LogTrace("Created CompileJob {0}", compileJob.Id);
logger.LogTrace("Created CompileJob {compileJobId}", compileJob.Id);
try
{
await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken);
@@ -576,7 +576,7 @@ namespace Tgstation.Server.Host.Components.Deployment
CancellationToken cancellationToken)
{
var outputDirectory = job.DirectoryName.ToString();
logger.LogTrace("Compile output GUID: {0}", outputDirectory);
logger.LogTrace("Compile output GUID: {dirGuid}", outputDirectory);
try
{
@@ -624,7 +624,7 @@ namespace Tgstation.Server.Host.Components.Deployment
throw new JobException(ErrorCode.DreamMakerMissingDme);
}
logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName);
logger.LogDebug("Selected {dmeName}.dme for compilation!", job.DmeName);
currentStage = "Modifying .dme";
await ModifyDme(job, cancellationToken);
@@ -727,7 +727,7 @@ namespace Tgstation.Server.Host.Components.Deployment
if (estimatedDuration.HasValue)
{
logger.LogDebug("Compile is expected to take: {0}", estimatedDuration);
logger.LogDebug("Compile is expected to take: {estimatedDuration}", estimatedDuration);
}
else
{
@@ -792,7 +792,7 @@ namespace Tgstation.Server.Host.Components.Deployment
bool logOutput,
CancellationToken cancellationToken)
{
logger.LogTrace("Verifying {0}DMAPI...", requireValidate ? "required " : String.Empty);
logger.LogTrace("Verifying {possiblyRequired}DMAPI...", requireValidate ? "required " : String.Empty);
var launchParameters = new DreamDaemonLaunchParameters
{
AllowWebClient = false,
@@ -827,7 +827,7 @@ namespace Tgstation.Server.Host.Components.Deployment
if (requireValidate && validationStatus == ApiValidationStatus.NeverValidated)
throw new JobException(ErrorCode.DreamMakerNeverValidated);
logger.LogTrace("API validation status: {0}", validationStatus);
logger.LogTrace("API validation status: {validationStatus}", validationStatus);
job.DMApiVersion = controller.DMApiVersion;
}
@@ -883,10 +883,10 @@ namespace Tgstation.Server.Host.Components.Deployment
exitCode = await dm.Lifetime;
cancellationToken.ThrowIfCancellationRequested();
logger.LogDebug("DreamMaker exit code: {0}", exitCode);
logger.LogDebug("DreamMaker exit code: {exitCode}", exitCode);
job.Output = await dm.GetCombinedOutput(cancellationToken);
currentDreamMakerOutput = job.Output;
logger.LogDebug("DreamMaker output: {0}{1}", Environment.NewLine, job.Output);
logger.LogDebug("DreamMaker output: {newLine}{output}", Environment.NewLine, job.Output);
return exitCode;
}
@@ -926,7 +926,7 @@ namespace Tgstation.Server.Host.Components.Deployment
{
var headIncludeLineNumber = dmeLineIndex + 1;
logger.LogDebug(
"Inserting HeadInclude.dm at line {0}: {1}",
"Inserting HeadInclude.dm at line {lineNumber}: {includeLine}",
headIncludeLineNumber,
dmeModifications.HeadIncludeLine);
dmeLines.Insert(headIncludeLineNumber, dmeModifications.HeadIncludeLine);
@@ -935,7 +935,7 @@ namespace Tgstation.Server.Host.Components.Deployment
else if (line.Contains("END_INCLUDE", StringComparison.Ordinal) && dmeModifications.TailIncludeLine != null)
{
logger.LogDebug(
"Inserting TailInclude.dm at line {0}: {1}",
"Inserting TailInclude.dm at line {lineNumber}: {includeLine}",
dmeLineIndex,
dmeModifications.TailIncludeLine);
dmeLines.Insert(dmeLineIndex, dmeModifications.TailIncludeLine);
@@ -968,7 +968,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
catch (Exception e)
{
logger.LogWarning(e, "Error cleaning up compile directory {0}!", ioManager.ResolvePath(jobPath));
logger.LogWarning(e, "Error cleaning up compile directory {path}!", ioManager.ResolvePath(jobPath));
}
}
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Template error message for when tracking of the most recent origin commit fails.
/// </summary>
public const string OriginTrackingErrorTemplate = "Unable to determine most recent origin commit of {0}. Marking it as an origin commit. This may result in invalid git metadata until the next hard reset to an origin reference.";
public const string OriginTrackingErrorTemplate = "Unable to determine most recent origin commit of {sha}. Marking it as an origin commit. This may result in invalid git metadata until the next hard reset to an origin reference.";
/// <summary>
/// The branch name used for publishing testmerge commits.
@@ -44,11 +44,11 @@ namespace Tgstation.Server.Host.System
foreach (ProcessThread thread in process.Threads)
{
var threadId = (uint)thread.Id;
logger.LogTrace("Suspending thread {0}...", threadId);
logger.LogTrace("Suspending thread {threadId}...", threadId);
var pOpenThread = NativeMethods.OpenThread(NativeMethods.ThreadAccess.SuspendResume, false, threadId);
if (pOpenThread == IntPtr.Zero)
{
logger.LogDebug(new Win32Exception(), "Failed to open thread {0}!", threadId);
logger.LogDebug(new Win32Exception(), "Failed to open thread {threadId}!", threadId);
continue;
}
@@ -74,11 +74,11 @@ namespace Tgstation.Server.Host.System
foreach (ProcessThread thread in process.Threads)
{
var threadId = (uint)thread.Id;
logger.LogTrace("Resuming thread {0}...", threadId);
logger.LogTrace("Resuming thread {threadId}...", threadId);
var pOpenThread = NativeMethods.OpenThread(NativeMethods.ThreadAccess.SuspendResume, false, threadId);
if (pOpenThread == IntPtr.Zero)
{
logger.LogDebug(new Win32Exception(), "Failed to open thread {0}!", threadId);
logger.LogDebug(new Win32Exception(), "Failed to open thread {threadId}!", threadId);
continue;
}
@@ -99,7 +99,7 @@ namespace Tgstation.Server.Host.System
{
string query = $"SELECT * FROM Win32_Process WHERE ProcessId = {process?.Id ?? throw new ArgumentNullException(nameof(process))}";
using var searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject obj in searcher.Get())
foreach (var obj in searcher.Get().Cast<ManagementObject>())
{
var argList = new string[] { String.Empty, String.Empty };
var returnString = obj.InvokeMethod(
@@ -34,10 +34,16 @@ namespace Tgstation.Server.Tests.Instance
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
}
public async Task Run(CancellationToken cancellationToken)
public Task Run(CancellationToken cancellationToken, out Task firstInstall)
{
firstInstall = TestInstallStable(cancellationToken);
return RunContinued(firstInstall, cancellationToken);
}
async Task RunContinued(Task firstInstall, CancellationToken cancellationToken)
{
await firstInstall;
await TestNoVersion(cancellationToken);
await TestInstallStable(cancellationToken);
await TestInstallFakeVersion(cancellationToken);
await TestCustomInstalls(cancellationToken);
}
@@ -70,7 +70,7 @@ namespace Tgstation.Server.Tests.Instance
Assert.IsNotNull(requestStream);
var response = (HttpResponseMessage)requestStream.GetType().GetField("response", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(requestStream);
Assert.AreEqual(response.Content.Headers.ContentType.MediaType, MediaTypeNames.Application.Octet);
await downloadStream.CopyToAsync(downloadMemoryStream);
await downloadStream.CopyToAsync(downloadMemoryStream, cancellationToken);
}
Assert.AreEqual(TestString, Encoding.UTF8.GetString(downloadMemoryStream.ToArray()).Trim());
}
@@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
@@ -22,7 +23,7 @@ namespace Tgstation.Server.Tests.Instance
public DeploymentTest(IInstanceClient instanceClient, IJobsClient jobsClient) : base(jobsClient)
{
this.instanceClient = instanceClient ?? throw new ArgumentException(nameof(instanceClient));
this.instanceClient = instanceClient ?? throw new ArgumentNullException(nameof(instanceClient));
this.dreamMakerClient = instanceClient.DreamMaker;
this.dreamDaemonClient = instanceClient.DreamDaemon;
}
@@ -40,7 +41,7 @@ namespace Tgstation.Server.Tests.Instance
Assert.AreEqual(null, dmSettings.ProjectName);
}
public async Task RunPostRepoClone(CancellationToken cancellationToken)
public async Task RunPostRepoClone(Task byondTask, CancellationToken cancellationToken)
{
Assert.IsNotNull(vpTest);
// by alphabetization rules, it should discover api_free here
@@ -71,17 +72,26 @@ namespace Tgstation.Server.Tests.Instance
Assert.AreEqual(15U, updatedDD.StartupTimeout);
Assert.AreEqual(IntegrationTest.DDPort, updatedDD.Port);
await ApiAssert.ThrowsException<ConflictException>(() => dreamDaemonClient.Update(new DreamDaemonRequest
async Task<JobResponse> CompileAfterByondInstall()
{
Port = IntegrationTest.DMPort
}, cancellationToken), ErrorCode.PortNotAvailable);
await byondTask;
return await dreamMakerClient.Compile(cancellationToken);
}
await ApiAssert.ThrowsException<ConflictException>(() => dreamMakerClient.Update(new DreamMakerRequest
{
ApiValidationPort = IntegrationTest.DDPort
}, cancellationToken), ErrorCode.PortNotAvailable);
var deployJobTask = CompileAfterByondInstall();
await Task.WhenAll(
ApiAssert.ThrowsException<ConflictException>(() => dreamDaemonClient.Update(new DreamDaemonRequest
{
Port = IntegrationTest.DMPort
}, cancellationToken), ErrorCode.PortNotAvailable),
ApiAssert.ThrowsException<ConflictException>(() => dreamMakerClient.Update(new DreamMakerRequest
{
ApiValidationPort = IntegrationTest.DDPort
}, cancellationToken), ErrorCode.PortNotAvailable),
deployJobTask);
var deployJob = await deployJobTask;
var deployJob = await dreamMakerClient.Compile(cancellationToken);
await WaitForJob(deployJob, 40, true, ErrorCode.DreamMakerNeverValidated, cancellationToken);
const string FailProject = "tests/DMAPI/BuildFail/build_fail";
@@ -31,21 +31,22 @@ namespace Tgstation.Server.Tests.Instance
var repoTest = new RepositoryTest(instanceClient.Repository, instanceClient.Jobs);
var dmTest = new DeploymentTest(instanceClient, instanceClient.Jobs);
var byondTask = byondTest.Run(cancellationToken);
var byondTask = byondTest.Run(cancellationToken, out var firstInstall);
var chatTask = chatTest.RunPreWatchdog(cancellationToken);
var repoLongJob = repoTest.RunLongClone(cancellationToken);
await dmTest.RunPreRepoClone(cancellationToken);
await repoTest.AbortLongCloneAndCloneSomethingQuick(await repoLongJob, cancellationToken);
await repoTest.AbortLongCloneAndCloneSomethingQuick(repoLongJob, cancellationToken);
await configTest.RunPreWatchdog(cancellationToken);
var dmTask = dmTest.RunPostRepoClone(cancellationToken);
var dmTask = dmTest.RunPostRepoClone(firstInstall, cancellationToken);
await byondTask;
await chatTask;
await dmTask;
await new WatchdogTest(instanceClient, instanceManager, serverPort).Run(cancellationToken);
await byondTask;
}
}
}
@@ -62,9 +62,9 @@ namespace Tgstation.Server.Tests.Instance
return clone.ActiveJob;
}
public async Task AbortLongCloneAndCloneSomethingQuick(JobResponse longCloneJob, CancellationToken cancellationToken)
public async Task AbortLongCloneAndCloneSomethingQuick(Task<JobResponse> longCloneJob, CancellationToken cancellationToken)
{
await WaitForJobProgressThenCancel(longCloneJob, 40, cancellationToken);
await WaitForJobProgressThenCancel(await longCloneJob, 40, cancellationToken);
var secondRead = await repositoryClient.Read(cancellationToken);
Assert.IsNotNull(secondRead);
@@ -2,6 +2,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Newtonsoft.Json;
@@ -23,6 +24,7 @@ using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Components.Chat;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Components.Watchdog;
using Tgstation.Server.Host.Controllers;
@@ -51,27 +53,26 @@ namespace Tgstation.Server.Tests.Instance
public async Task Run(CancellationToken cancellationToken)
{
global::System.Console.WriteLine("TEST: START WATCHDOG TESTS");
// Increase startup timeout, disable heartbeats
var initialSettings = await instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
StartupTimeout = 15,
HeartbeatSeconds = 0,
Port = IntegrationTest.DDPort
}, cancellationToken);
await ApiAssert.ThrowsException<ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
Port = 0
}, cancellationToken), ErrorCode.ModelValidationFailure);
await ApiAssert.ThrowsException<ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
SoftShutdown = true,
SoftRestart = true
}, cancellationToken), ErrorCode.DreamDaemonDoubleSoft);
await ApiAssert.ThrowsException<ConflictException>(() => instanceClient.DreamDaemon.CreateDump(cancellationToken), ErrorCode.WatchdogNotRunning);
await ApiAssert.ThrowsException<ConflictException>(() => instanceClient.DreamDaemon.Restart(cancellationToken), ErrorCode.WatchdogNotRunning);
await Task.WhenAll(
// Increase startup timeout, disable heartbeats
instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
StartupTimeout = 15,
HeartbeatSeconds = 0,
Port = IntegrationTest.DDPort
}, cancellationToken),
ApiAssert.ThrowsException<ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
SoftShutdown = true,
SoftRestart = true
}, cancellationToken), ErrorCode.DreamDaemonDoubleSoft),
ApiAssert.ThrowsException<ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
Port = 0
}, cancellationToken), ErrorCode.ModelValidationFailure),
ApiAssert.ThrowsException<ConflictException>(() => instanceClient.DreamDaemon.CreateDump(cancellationToken), ErrorCode.WatchdogNotRunning),
ApiAssert.ThrowsException<ConflictException>(() => instanceClient.DreamDaemon.Restart(cancellationToken), ErrorCode.WatchdogNotRunning));
await RunBasicTest(cancellationToken);
@@ -94,18 +95,21 @@ namespace Tgstation.Server.Tests.Instance
{
await StartAndLeaveRunning(cancellationToken);
await Task.WhenAll(
WhiteBoxChatCommandTest(cancellationToken),
SendChatOverloadCommand(cancellationToken));
await WhiteBoxChatCommandTest(cancellationToken);
await SendChatOverloadCommand(cancellationToken);
await ValidateTopicLimits(cancellationToken);
// This one fucks with the access_identifer, run it in isolation
await WhiteBoxValidateDMApiLimits(cancellationToken);
await WhiteBoxValidateBridgeRequestLimitAndTestChunking(cancellationToken);
var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken);
await CheckDMApiFail(ddInfo.ActiveCompileJob, cancellationToken);
// And this freezes DD
await DumpTests(cancellationToken);
}
async Task SendChatOverloadCommand(CancellationToken cancellationToken)
static async Task SendChatOverloadCommand(CancellationToken cancellationToken)
{
// for the code coverage really...
var topicRequestResult = await TopicClient.SendTopic(
@@ -271,7 +275,7 @@ namespace Tgstation.Server.Tests.Instance
using var ddProc = ddProcs.Single();
IProcessExecutor executor = null;
executor = new ProcessExecutor(
System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => executor), Mock.Of<IIOManager>(), Mock.Of<ILogger<PosixProcessFeatures>>()),
Mock.Of<IIOManager>(),
@@ -363,7 +367,9 @@ namespace Tgstation.Server.Tests.Instance
public string Payload { get; set; }
}
async Task WhiteBoxValidateDMApiLimits(CancellationToken cancellationToken)
// - Uses instance manager concrete
// - Injects a custom bridge handler into the bridge registrar and makes the test hack into the DMAPI and change its access_identifier
async Task WhiteBoxValidateBridgeRequestLimitAndTestChunking(CancellationToken cancellationToken)
{
// first check the bridge limits
var bridgeTestsTcs = new TaskCompletionSource();
@@ -389,7 +395,10 @@ namespace Tgstation.Server.Tests.Instance
// Time for DD to revert the bridge access identifier change
await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
}
static async Task ValidateTopicLimits(CancellationToken cancellationToken)
{
// Time for topic tests
// Request
@@ -484,21 +493,19 @@ namespace Tgstation.Server.Tests.Instance
cancellationToken.ThrowIfCancellationRequested();
Assert.AreEqual(DMApiConstants.MaximumTopicResponseLength, (uint)lastSize);
var ddInfo = await instanceClient.DreamDaemon.Read(cancellationToken);
await CheckDMApiFail(ddInfo.ActiveCompileJob, cancellationToken);
}
// - Uses instance manager concrete
// - Injects a custom bridge handler into the bridge registrar and makes the test hack into the DMAPI and change its access_identifier
async Task WhiteBoxChatCommandTest(CancellationToken cancellationToken)
{
// oh god, oh fuck, blackbox testing
MessageContent embedsResponse, overloadResponse, overloadResponse2, embedsResponse2;
var startTime = DateTimeOffset.UtcNow - TimeSpan.FromSeconds(5);
using (var instanceReference = instanceManager.GetInstanceReference(instanceClient.Metadata))
{
var mockChatUser = new Host.Components.Chat.ChatUser
var mockChatUser = new ChatUser
{
Channel = new Host.Components.Chat.ChannelRepresentation
Channel = new ChannelRepresentation
{
IsAdminChannel = true,
ConnectionName = "test_connection",
@@ -513,25 +520,25 @@ namespace Tgstation.Server.Tests.Instance
RealId = 1234,
};
var embedsResponseTask = ((BasicWatchdog)instanceReference.Watchdog).HandleChatCommand(
var embedsResponseTask = ((WatchdogBase)instanceReference.Watchdog).HandleChatCommand(
"embeds_test",
String.Empty,
mockChatUser,
cancellationToken);
var embedsResponseTask2 = ((BasicWatchdog)instanceReference.Watchdog).HandleChatCommand(
var embedsResponseTask2 = ((WatchdogBase)instanceReference.Watchdog).HandleChatCommand(
"embeds_test",
new string('a', (int)DMApiConstants.MaximumTopicRequestLength * 3),
mockChatUser,
cancellationToken);
var overloadResponseTask2 = ((BasicWatchdog)instanceReference.Watchdog).HandleChatCommand(
var overloadResponseTask2 = ((WatchdogBase)instanceReference.Watchdog).HandleChatCommand(
"response_overload_test",
String.Empty,
mockChatUser,
cancellationToken);
overloadResponse = await ((BasicWatchdog)instanceReference.Watchdog).HandleChatCommand(
overloadResponse = await ((WatchdogBase)instanceReference.Watchdog).HandleChatCommand(
"response_overload_test",
String.Empty,
mockChatUser,
@@ -557,7 +564,7 @@ namespace Tgstation.Server.Tests.Instance
Assert.AreEqual(expectedString, overloadResponse2.Text);
}
void CheckEmbedsTest(MessageContent embedsResponse, DateTimeOffset startTime, DateTimeOffset endTime)
static void CheckEmbedsTest(MessageContent embedsResponse, DateTimeOffset startTime, DateTimeOffset endTime)
{
Assert.IsNotNull(embedsResponse);
Assert.AreEqual("Embed support test2", embedsResponse.Text);
@@ -46,7 +46,7 @@ namespace Tgstation.Server.Tests
ChatBotLimit = 2
}, cancellationToken);
static TRequestType FromResponse<TRequestType>(InstanceResponse response) where TRequestType : Api.Models.Instance, new() => new TRequestType
static TRequestType FromResponse<TRequestType>(InstanceResponse response) where TRequestType : Api.Models.Instance, new() => new ()
{
Id = response.Id,
Path = response.Path,
@@ -181,7 +181,7 @@ namespace Tgstation.Server.Tests
var token = serverClient.Token.Bearer;
// check that 400s are returned appropriately
using var httpClient = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ListRoute(Routes.InstanceManager).Substring(1) + "?pageSize=2");
using var request = new HttpRequestMessage(HttpMethod.Get, String.Concat(url.ToString(), Routes.ListRoute(Routes.InstanceManager).AsSpan(1), "?pageSize=2"));
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RegressionTest1256", "1.0.0"));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
@@ -190,7 +190,7 @@ namespace Tgstation.Server.Tests
using var response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var json = await response.Content.ReadAsStringAsync(cancellationToken);
var paginated = JsonConvert.DeserializeObject<PaginatedResponse<InstanceResponse>>(json);
Assert.AreEqual(2, paginated.PageSize);
@@ -871,7 +871,7 @@ namespace Tgstation.Server.Tests
}
}
var rootTest = FailFast(new RawRequestTests().Run(clientFactory, adminClient, cancellationToken));
var rootTest = FailFast(RawRequestTests.Run(clientFactory, adminClient, cancellationToken));
var adminTest = FailFast(new AdministrationTest(adminClient.Administration).Run(cancellationToken));
var usersTest = FailFast(new UsersTest(adminClient).Run(cancellationToken));
instance = await new InstanceManagerTest(adminClient, server.Directory).RunPreInstanceTest(cancellationToken);
@@ -1151,10 +1151,10 @@ namespace Tgstation.Server.Tests
using var repo = new Repository(
libGit2Repo,
new LibGit2Commands(),
Mock.Of<Host.IO.IIOManager>(),
Mock.Of<IIOManager>(),
Mock.Of<IEventConsumer>(),
Mock.Of<ICredentialsProvider>(),
Mock.Of<Host.IO.IPostWriteHandler>(),
Mock.Of<IPostWriteHandler>(),
Mock.Of<IGitRemoteFeaturesFactory>(),
Mock.Of<ILogger<Repository>>(),
() => { });
@@ -1165,7 +1165,7 @@ namespace Tgstation.Server.Tests
Assert.IsTrue(result);
Assert.AreEqual(StartSha, repo.Head);
result = await repo.ShaIsParent("f636418bf47d238d33b0e4a34f0072b23a8aad0e", default);
Assert.IsFalse(result); ;
Assert.IsFalse(result);
Assert.AreEqual(StartSha, repo.Head);
}
}
+33 -32
View File
@@ -1,4 +1,4 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using System;
using System.Net;
@@ -18,9 +18,9 @@ using Tgstation.Server.Host;
namespace Tgstation.Server.Tests
{
sealed class RawRequestTests
static class RawRequestTests
{
async Task TestRequestValidation(IServerClient serverClient, CancellationToken cancellationToken)
static async Task TestRequestValidation(IServerClient serverClient, CancellationToken cancellationToken)
{
var url = serverClient.Url;
var token = serverClient.Token.Bearer;
@@ -50,7 +50,7 @@ namespace Tgstation.Server.Tests
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(ErrorCode.BadHeaders, message.ErrorCode);
}
@@ -63,7 +63,7 @@ namespace Tgstation.Server.Tests
request.Headers.Add(ApiHeaders.ApiVersionHeader, "Tgstation.Server.Api/" + ApiHeaders.Version);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ServerInformationResponse>(content);
Assert.AreEqual(ApiHeaders.Version, message.ApiVersion);
}
@@ -77,12 +77,12 @@ namespace Tgstation.Server.Tests
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.UpgradeRequired, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(ErrorCode.ApiMismatch, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.Administration.Substring(1)))
using (var request = new HttpRequestMessage(HttpMethod.Get, String.Concat(url.ToString(), Routes.Administration.AsSpan(1))))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -91,12 +91,12 @@ namespace Tgstation.Server.Tests
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.UpgradeRequired, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(ErrorCode.ApiMismatch, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Post, url.ToString() + Routes.Administration.Substring(1)))
using (var request = new HttpRequestMessage(HttpMethod.Post, String.Concat(url.ToString(), Routes.Administration.AsSpan(1))))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -109,12 +109,12 @@ namespace Tgstation.Server.Tests
MediaTypeNames.Application.Json);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(ErrorCode.ModelValidationFailure, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Post, url.ToString() + Routes.DreamDaemon.Substring(1)))
using (var request = new HttpRequestMessage(HttpMethod.Post, String.Concat(url.ToString(), Routes.DreamDaemon.AsSpan(1))))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -124,7 +124,7 @@ namespace Tgstation.Server.Tests
Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Post, url.ToString() + Routes.DreamDaemon.Substring(1)))
using (var request = new HttpRequestMessage(HttpMethod.Post, String.Concat(url.ToString(), Routes.DreamDaemon.AsSpan(1))))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -133,7 +133,7 @@ namespace Tgstation.Server.Tests
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(ErrorCode.InstanceHeaderRequired, message.ErrorCode);
}
@@ -147,7 +147,7 @@ namespace Tgstation.Server.Tests
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme.ToLower(), token);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(ErrorCode.BadHeaders, message.ErrorCode);
}
@@ -162,7 +162,7 @@ namespace Tgstation.Server.Tests
request.Headers.Add(ApiHeaders.OAuthProviderHeader, "FakeProvider");
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(ErrorCode.BadHeaders, message.ErrorCode);
}
@@ -179,13 +179,13 @@ namespace Tgstation.Server.Tests
Encoding.UTF8.GetBytes(":")));
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(ErrorCode.BadHeaders, message.ErrorCode);
}
}
async Task TestServerInformation(IServerClientFactory clientFactory, IServerClient serverClient, CancellationToken cancellationToken)
static async Task TestServerInformation(IServerClientFactory clientFactory, IServerClient serverClient, CancellationToken cancellationToken)
{
var serverInfo = await serverClient.ServerInformation(default);
@@ -209,7 +209,7 @@ namespace Tgstation.Server.Tests
await Assert.ThrowsExceptionAsync<UnauthorizedException>(() => badClient.Administration.Read(cancellationToken));
}
async Task TestOAuthFails(IServerClient serverClient, CancellationToken cancellationToken)
static async Task TestOAuthFails(IServerClient serverClient, CancellationToken cancellationToken)
{
var url = serverClient.Url;
var token = serverClient.Token.Bearer;
@@ -230,14 +230,15 @@ namespace Tgstation.Server.Tests
Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
}
async Task TestInvalidTransfers(IServerClient serverClient, CancellationToken cancellationToken)
static async Task TestInvalidTransfers(IServerClient serverClient, CancellationToken cancellationToken)
{
var url = serverClient.Url;
var token = serverClient.Token.Bearer;
// check that 400s are returned appropriately
using var httpClient = new HttpClient();
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.Transfer.Substring(1)))
using (var request = new HttpRequestMessage(HttpMethod.Get, String.Concat(url.ToString(), Routes.Transfer.AsSpan(1))))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -246,13 +247,13 @@ namespace Tgstation.Server.Tests
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(MediaTypeNames.Application.Json, response.Content.Headers.ContentType.MediaType);
Assert.AreEqual(ErrorCode.ModelValidationFailure, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Put, url.ToString() + Routes.Transfer.Substring(1)))
using (var request = new HttpRequestMessage(HttpMethod.Put, String.Concat(url.ToString(), Routes.Transfer.AsSpan(1))))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -261,13 +262,13 @@ namespace Tgstation.Server.Tests
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var message = JsonConvert.DeserializeObject<ErrorMessageResponse>(content);
Assert.AreEqual(MediaTypeNames.Application.Json, response.Content.Headers.ContentType.MediaType);
Assert.AreEqual(ErrorCode.ModelValidationFailure, message.ErrorCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.Transfer.Substring(1) + "?ticket=veryfaketicket"))
using (var request = new HttpRequestMessage(HttpMethod.Get, String.Concat(url.ToString(), Routes.Transfer.AsSpan(1), "?ticket=veryfaketicket")))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -279,7 +280,7 @@ namespace Tgstation.Server.Tests
Assert.AreEqual(HttpStatusCode.NotAcceptable, response.StatusCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.Transfer.Substring(1) + "?ticket=veryfaketicket"))
using (var request = new HttpRequestMessage(HttpMethod.Get, String.Concat(url.ToString(), Routes.Transfer.AsSpan(1), "?ticket=veryfaketicket")))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -292,7 +293,7 @@ namespace Tgstation.Server.Tests
Assert.AreEqual(HttpStatusCode.Gone, response.StatusCode);
}
using (var request = new HttpRequestMessage(HttpMethod.Put, url.ToString() + Routes.Transfer.Substring(1) + "?ticket=veryfaketicket"))
using (var request = new HttpRequestMessage(HttpMethod.Put, String.Concat(url.ToString(), Routes.Transfer.AsSpan(1), "?ticket=veryfaketicket")))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -305,7 +306,7 @@ namespace Tgstation.Server.Tests
}
}
async Task RegressionTestForLeakedPasswordHashesBug(IServerClient serverClient, CancellationToken cancellationToken)
static async Task RegressionTestForLeakedPasswordHashesBug(IServerClient serverClient, CancellationToken cancellationToken)
{
// See what https://github.com/tgstation/tgstation-server/commit/6c8dc87c4af36620885b262175d7974aca2b3c2b fixed
@@ -314,7 +315,7 @@ namespace Tgstation.Server.Tests
// check that 400s are returned appropriately
using var httpClient = new HttpClient();
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.User.Substring(1)))
using (var request = new HttpRequestMessage(HttpMethod.Get, String.Concat(url.ToString(), Routes.User.AsSpan(1))))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -323,11 +324,11 @@ namespace Tgstation.Server.Tests
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
Assert.IsFalse(content.Contains("passwordHash"));
}
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.User.Substring(1) + '/' + Routes.List + "?pageSize=100"))
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.User[1..] + '/' + Routes.List + "?pageSize=100"))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RootTest", "1.0.0"));
@@ -336,12 +337,12 @@ namespace Tgstation.Server.Tests
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
Assert.IsFalse(content.Contains("passwordHash"));
}
}
public Task Run(IServerClientFactory clientFactory, IServerClient serverClient, CancellationToken cancellationToken)
public static Task Run(IServerClientFactory clientFactory, IServerClient serverClient, CancellationToken cancellationToken)
=> Task.WhenAll(
TestRequestValidation(serverClient, cancellationToken),
TestOAuthFails(serverClient, cancellationToken),