コード例 #1
0
        public void Parse_WithParameter_AddsStringToDictionary()
        {
            var context    = BuildContext("http://localhost/test?param=value", "/test");
            var parameters = ParameterParser.Parse(context);

            ApiAssert.ContainsParameter(parameters, "param", "value");
        }
コード例 #2
0
        public async Task Run(CancellationToken cancellationToken)
        {
            global::System.Console.WriteLine("TEST: START WATCHDOG TESTS");
            // Increase startup timeout, disable heartbeats
            await instanceClient.DreamDaemon.Update(new DreamDaemon
            {
                StartupTimeout   = 45,
                HeartbeatSeconds = 0,
            }, cancellationToken);

            await ApiAssert.ThrowsException <ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemon
            {
                SoftShutdown = true,
                SoftRestart  = true
            }, cancellationToken), ErrorCode.DreamDaemonDoubleSoft);

            await RunBasicTest(cancellationToken);

            // await RunLongRunningTestThenUpdate(cancellationToken);
            // await RunLongRunningTestThenUpdateWithByondVersionSwitch(cancellationToken);
            // Remove this deploy when the above tests are reenabled
            await DeployTestDme("LongRunning/long_running_test", DreamDaemonSecurity.Trusted, cancellationToken);

            await RunHeartbeatTest(cancellationToken);

            await StartAndLeaveRunning(cancellationToken);

            global::System.Console.WriteLine("TEST: END WATCHDOG TESTS");
        }
コード例 #3
0
        async Task <Repository> Checkout(Repository updated, bool expectFailure, bool isRef, bool checkBusy, CancellationToken cancellationToken)
        {
            var newRef      = isRef ? updated.Reference : updated.CheckoutSha;
            var checkingOut = await repositoryClient.Update(updated, cancellationToken);

            Assert.IsNotNull(checkingOut.ActiveJob);
            if (checkBusy)
            {
                await ApiAssert.ThrowsException <ConflictException>(() => repositoryClient.Read(cancellationToken), ErrorCode.RepoBusy);
            }

            await WaitForJob(checkingOut.ActiveJob, 30, expectFailure, cancellationToken);

            var result = await repositoryClient.Read(cancellationToken);

            if (!expectFailure)
            {
                if (isRef)
                {
                    Assert.AreEqual(newRef, result.Reference);
                }
                else
                {
                    Assert.IsTrue(result.RevisionInformation.CommitSha.StartsWith(newRef, StringComparison.OrdinalIgnoreCase));
                }
            }

            Assert.AreEqual(result.RevisionInformation.CommitSha, result.RevisionInformation.OriginCommitSha);

            return(result);
        }
コード例 #4
0
        public void Parse_WithID_ReturnsDictionaryWithParameter()
        {
            var context    = BuildContext("http://localhost/test/1", "/test/{id}");
            var parameters = ParameterParser.Parse(context);

            ApiAssert.ContainsParameter(parameters, "id", 1);
        }
コード例 #5
0
        async Task TestDeleteDirectory(CancellationToken cancellationToken)
        {
            //try to delete non-existent
            var TestDir = new ConfigurationFile
            {
                Path = "/TestDeleteDir"
            };

            await configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken).ConfigureAwait(false);

            //try to delete non-empty
            var file = await configurationClient.Write(new ConfigurationFile
            {
                Content = Encoding.UTF8.GetBytes("Hello world!"),
                Path    = TestDir.Path + "/test.txt"
            }, cancellationToken).ConfigureAwait(false);

            Assert.IsTrue(FileExists(file));

            await ApiAssert.ThrowsException <ConflictException>(() => configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken), ErrorCode.ConfigurationDirectoryNotEmpty).ConfigureAwait(false);

            file.Content = null;
            await configurationClient.Write(file, cancellationToken).ConfigureAwait(false);

            await configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken).ConfigureAwait(false);
        }
コード例 #6
0
        public void Parse_WithParameter_DecodesParameter()
        {
            var value      = HttpUtility.UrlEncode("encoded value");
            var context    = BuildContext("http://localhost/test?param=" + value, "/test");
            var parameters = ParameterParser.Parse(context);

            ApiAssert.ContainsParameter(parameters, "param", "encoded value");
        }
コード例 #7
0
        async Task TestDeleteDirectory(CancellationToken cancellationToken)
        {
            //try to delete non-existent
            var TestDir = new ConfigurationFileRequest
            {
                Path = "/TestDeleteDir"
            };

            await configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken).ConfigureAwait(false);

            //try to delete non-empty
            const string TestString = "Hello world!";

            using var uploadMs = new MemoryStream(Encoding.UTF8.GetBytes(TestString));
            var file = await configurationClient.Write(new ConfigurationFileRequest
            {
                Path = TestDir.Path + "/test.txt"
            }, uploadMs, cancellationToken).ConfigureAwait(false);

            Assert.IsTrue(FileExists(file));
            Assert.IsNull(file.LastReadHash);

            var updatedFileTuple = await configurationClient.Read(file, cancellationToken).ConfigureAwait(false);

            var updatedFile = updatedFileTuple.Item1;

            Assert.IsNotNull(updatedFile.LastReadHash);
            using (var downloadMemoryStream = new MemoryStream())
            {
                using (var downloadStream = updatedFileTuple.Item2)
                {
                    var requestStream = downloadStream as CachedResponseStream;
                    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);
                }
                Assert.AreEqual(TestString, Encoding.UTF8.GetString(downloadMemoryStream.ToArray()).Trim());
            }

            await ApiAssert.ThrowsException <ConflictException>(() => configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken), ErrorCode.ConfigurationDirectoryNotEmpty).ConfigureAwait(false);

            file.FileTicket = null;
            await configurationClient.Write(new ConfigurationFileRequest
            {
                Path         = updatedFile.Path,
                LastReadHash = updatedFile.LastReadHash
            }, null, cancellationToken).ConfigureAwait(false);

            Assert.IsFalse(FileExists(file));

            await configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken).ConfigureAwait(false);

            var tmp  = (TestDir.Path?.StartsWith('/') ?? false) ? '.' + TestDir.Path : TestDir.Path;
            var path = Path.Combine(instance.Path, "Configuration", tmp);

            Assert.IsFalse(Directory.Exists(path));
        }
コード例 #8
0
        async Task TestCustomInstalls(CancellationToken cancellationToken)
        {
            var byondInstaller = new PlatformIdentifier().IsWindows
                                ? (IByondInstaller) new WindowsByondInstaller(
                Mock.Of <IProcessExecutor>(),
                new DefaultIOManager(),
                Mock.Of <ILogger <WindowsByondInstaller> >())
                                : new PosixByondInstaller(
                Mock.Of <IPostWriteHandler>(),
                new DefaultIOManager(),
                Mock.Of <ILogger <PosixByondInstaller> >());

            // get the bytes for stable
            using var stableBytesMs = new MemoryStream(
                      await byondInstaller.DownloadVersion(TestVersion, cancellationToken));

            var test = await byondClient.SetActiveVersion(
                new ByondVersionRequest
            {
                Version         = TestVersion,
                UploadCustomZip = true
            },
                stableBytesMs,
                cancellationToken)
                       .ConfigureAwait(false);

            Assert.IsNotNull(test.InstallJob);
            await WaitForJob(test.InstallJob, 60, false, null, cancellationToken).ConfigureAwait(false);

            var newSettings = await byondClient.ActiveVersion(cancellationToken);

            Assert.AreEqual(new Version(TestVersion.Major, TestVersion.Minor, 1), newSettings.Version);

            // test a few switches
            var installResponse = await byondClient.SetActiveVersion(new ByondVersionRequest
            {
                Version = TestVersion
            }, null, cancellationToken);

            Assert.IsNull(installResponse.InstallJob);
            await ApiAssert.ThrowsException <ApiConflictException>(() => byondClient.SetActiveVersion(new ByondVersionRequest
            {
                Version = new Version(TestVersion.Major, TestVersion.Minor, 2)
            }, null, cancellationToken), ErrorCode.ByondNonExistentCustomVersion);

            installResponse = await byondClient.SetActiveVersion(new ByondVersionRequest
            {
                Version = new Version(TestVersion.Major, TestVersion.Minor, 1)
            }, null, cancellationToken);

            Assert.IsNull(installResponse.InstallJob);
        }
コード例 #9
0
        async Task RunLimitTests(CancellationToken cancellationToken)
        {
            await ApiAssert.ThrowsException <ConflictException>(() => chatClient.Create(new ChatBotCreateRequest
            {
                Name             = "asdf",
                ConnectionString = "asdf",
                Provider         = ChatProvider.Irc
            }, cancellationToken), ErrorCode.ChatBotMax);

            var bots = await chatClient.List(null, cancellationToken);

            var ogDiscordBot  = bots.First(bot => bot.Provider.Value == ChatProvider.Discord);;
            var discordBotReq = new ChatBotUpdateRequest
            {
                Id           = ogDiscordBot.Id,
                Channels     = ogDiscordBot.Channels.ToList(),
                ChannelLimit = 1
            };

            // We limited chat bots and channels to 1 and 2 respectively, try violating them
            discordBotReq.Channels.Add(
                new ChatChannel
            {
                IsAdminChannel    = true,
                IsUpdatesChannel  = false,
                IsWatchdogChannel = true,
                Tag = "butt",
                DiscordChannelId = discordBotReq.Channels.First().DiscordChannelId
            });

            await ApiAssert.ThrowsException <ApiConflictException>(() => chatClient.Update(discordBotReq, cancellationToken), ErrorCode.ChatBotMaxChannels);

            var oldChannels = discordBotReq.Channels;

            discordBotReq.Channels     = null;
            discordBotReq.ChannelLimit = 0;
            await ApiAssert.ThrowsException <ConflictException>(() => chatClient.Update(discordBotReq, cancellationToken), ErrorCode.ChatBotMaxChannels);

            discordBotReq.Channels     = oldChannels;
            discordBotReq.ChannelLimit = null;
            await ApiAssert.ThrowsException <ConflictException>(() => chatClient.Update(discordBotReq, cancellationToken), ErrorCode.ChatBotMaxChannels);

            await ApiAssert.ThrowsException <ConflictException>(() => instanceClient.Update(new InstanceUpdateRequest
            {
                Id           = metadata.Id,
                ChatBotLimit = 0
            }, cancellationToken), ErrorCode.ChatBotMax);

            discordBotReq.ChannelLimit = 20;
            discordBotReq.Channels     = null;
            await chatClient.Update(discordBotReq, cancellationToken);
        }
コード例 #10
0
        public void Parse_WithJsonBody_AddsJObjectToDictionary()
        {
            var context = new ApiContext {
                Uri   = new Uri("http://localhost/test"),
                Route = new Route {
                    Url = "/test"
                },
                RequestBody = "{ string: 'value', integer: 1 }"
            };

            var parameters = ParameterParser.Parse(context);

            ApiAssert.ContainsParameter(parameters, "json");
        }
コード例 #11
0
        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 DreamDaemon
            {
                StartupTimeout   = 60,
                HeartbeatSeconds = 0,
                Port             = IntegrationTest.DDPort
            }, cancellationToken);

            await ApiAssert.ThrowsException <ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemon
            {
                Port = 0
            }, cancellationToken), ErrorCode.ModelValidationFailure);

            await ApiAssert.ThrowsException <ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemon
            {
                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 RunBasicTest(cancellationToken);

            await TestDMApiFreeDeploy(cancellationToken);

            await RunLongRunningTestThenUpdate(cancellationToken);
            await RunLongRunningTestThenUpdateWithNewDme(cancellationToken);
            await RunLongRunningTestThenUpdateWithByondVersionSwitch(cancellationToken);

            await RunHeartbeatTest(cancellationToken);

            await StartAndLeaveRunning(cancellationToken);

            await DumpTests(cancellationToken);

            System.Console.WriteLine("TEST: END WATCHDOG TESTS");
        }
コード例 #12
0
        public void Parse_WithHttpContext_AddsJObjectToDictionary()
        {
            var httpRequest  = new HttpRequest("test.jpg", "http://localhost/uploadImage", "{}");
            var stringWriter = new StringWriter();
            var httpResponce = new HttpResponse(stringWriter);

            HttpContext httpContext = new HttpContext(httpRequest, httpResponce);

            var context = new ApiContext
            {
                Uri   = new Uri("http://localhost/test"),
                Route = new Route {
                    Url = "/test"
                },
                RequestBody        = "{ string: 'value', integer: 1 }",
                HttpFileCollection = new ShoreSweep.Api.Framework.HttpFileCollectionWrapper(httpContext.Request.Files)
            };

            var parameters = ParameterParser.Parse(context);

            ApiAssert.ContainsParameter(parameters, "httpFileCollection");
        }
コード例 #13
0
        public async Task RunPreWatchdog(CancellationToken cancellationToken)
        {
            const string TestRefEnvVar = "TGS4_GITHUB_REF";
            var          envVar        = Environment.GetEnvironmentVariable(TestRefEnvVar);
            string       workingBranch = null;

            if (!String.IsNullOrWhiteSpace(envVar))
            {
                workingBranch = envVar;
                Console.WriteLine($"TEST: Set working branch to '{workingBranch}' from env var '{TestRefEnvVar}'");
            }

            if (workingBranch == null)
            {
                workingBranch = "master";
                Console.WriteLine($"TEST: Set working branch to default '{workingBranch}'");
            }

            var initalRepo = await repositoryClient.Read(cancellationToken);

            Assert.IsNotNull(initalRepo);
            Assert.IsNull(initalRepo.Origin);
            Assert.IsNull(initalRepo.Reference);
            Assert.IsNull(initalRepo.RevisionInformation);
            Assert.IsNull(initalRepo.ActiveJob);

            const string Origin       = "https://github.com/tgstation/tgstation-server";
            var          cloneRequest = new RepositoryCreateRequest
            {
                Origin    = new Uri(Origin),
                Reference = workingBranch,
            };

            var clone = await repositoryClient.Clone(cloneRequest, cancellationToken).ConfigureAwait(false);

            await ApiAssert.ThrowsException <ConflictException>(() => repositoryClient.Read(cancellationToken), ErrorCode.RepoCloning);

            Assert.IsNotNull(clone);
            Assert.AreEqual(cloneRequest.Origin, clone.Origin);
            Assert.AreEqual(workingBranch, clone.Reference);
            Assert.IsNull(clone.RevisionInformation);
            Assert.IsNotNull(clone.ActiveJob);

            await WaitForJobProgressThenCancel(clone.ActiveJob, 20, cancellationToken).ConfigureAwait(false);

            var secondRead = await repositoryClient.Read(cancellationToken).ConfigureAwait(false);

            Assert.IsNotNull(secondRead);
            Assert.IsNull(secondRead.ActiveJob);

            clone = await repositoryClient.Clone(cloneRequest, cancellationToken).ConfigureAwait(false);

            await WaitForJob(clone.ActiveJob, 9000, false, null, cancellationToken).ConfigureAwait(false);

            var readAfterClone = await repositoryClient.Read(cancellationToken);

            Assert.AreEqual(cloneRequest.Origin, readAfterClone.Origin);
            Assert.AreEqual(workingBranch, readAfterClone.Reference);
            Assert.IsNotNull(readAfterClone.RevisionInformation);
            Assert.IsNotNull(readAfterClone.RevisionInformation.ActiveTestMerges);
            Assert.AreEqual(0, readAfterClone.RevisionInformation.ActiveTestMerges.Count);
            Assert.IsNotNull(readAfterClone.RevisionInformation.CommitSha);
            Assert.IsNotNull(readAfterClone.RevisionInformation.OriginCommitSha);
            Assert.IsNotNull(readAfterClone.RevisionInformation.CompileJobs);
            Assert.AreEqual(0, readAfterClone.RevisionInformation.CompileJobs.Count);
            Assert.IsNotNull(readAfterClone.RevisionInformation.OriginCommitSha);
            Assert.IsNull(readAfterClone.RevisionInformation.PrimaryTestMerge);
            Assert.AreEqual(readAfterClone.RevisionInformation.CommitSha, readAfterClone.RevisionInformation.OriginCommitSha);
            Assert.AreNotEqual(default, readAfterClone.RevisionInformation.Timestamp);
コード例 #14
0
        public async Task Run(Task repositoryTask, CancellationToken cancellationToken)
        {
            var deployJob = await dreamMakerClient.Compile(cancellationToken);

            deployJob = await WaitForJob(deployJob, 30, true, null, cancellationToken);

            Assert.IsTrue(deployJob.ErrorCode == ErrorCode.RepoCloning || deployJob.ErrorCode == ErrorCode.RepoMissing);

            var dmSettings = await dreamMakerClient.Read(cancellationToken);

            Assert.AreEqual(true, dmSettings.RequireDMApiValidation);
            Assert.AreEqual(null, dmSettings.ProjectName);

            await repositoryTask;

            // by alphabetization rules, it should discover api_free here
            if (!new PlatformIdentifier().IsWindows)
            {
                var updatedDM = await dreamMakerClient.Update(new DreamMakerRequest
                {
                    ProjectName       = "tests/DMAPI/ApiFree/api_free",
                    ApiValidationPort = IntegrationTest.DMPort
                }, cancellationToken);

                Assert.AreEqual(IntegrationTest.DMPort, updatedDM.ApiValidationPort);
                Assert.AreEqual("tests/DMAPI/ApiFree/api_free", updatedDM.ProjectName);
            }
            else
            {
                var updatedDM = await dreamMakerClient.Update(new DreamMakerRequest
                {
                    ApiValidationPort = IntegrationTest.DMPort
                }, cancellationToken);

                Assert.AreEqual(IntegrationTest.DMPort, updatedDM.ApiValidationPort);
            }

            var updatedDD = await dreamDaemonClient.Update(new DreamDaemonRequest
            {
                StartupTimeout = 5,
                Port           = IntegrationTest.DDPort
            }, cancellationToken);

            Assert.AreEqual(5U, updatedDD.StartupTimeout);
            Assert.AreEqual(IntegrationTest.DDPort, updatedDD.Port);

            await ApiAssert.ThrowsException <ConflictException>(() => dreamDaemonClient.Update(new DreamDaemonRequest
            {
                Port = IntegrationTest.DMPort
            }, cancellationToken), ErrorCode.PortNotAvailable);

            await ApiAssert.ThrowsException <ConflictException>(() => dreamMakerClient.Update(new DreamMakerRequest
            {
                ApiValidationPort = IntegrationTest.DDPort
            }, cancellationToken), ErrorCode.PortNotAvailable);

            deployJob = await dreamMakerClient.Compile(cancellationToken);
            await WaitForJob(deployJob, 30, true, ErrorCode.DreamMakerNeverValidated, cancellationToken);

            const string FailProject = "tests/DMAPI/BuildFail/build_fail";
            var          updated     = await dreamMakerClient.Update(new DreamMakerRequest
            {
                ProjectName = FailProject
            }, cancellationToken);

            Assert.AreEqual(FailProject, updated.ProjectName);

            deployJob = await dreamMakerClient.Compile(cancellationToken);
            await WaitForJob(deployJob, 30, true, ErrorCode.DreamMakerExitCode, cancellationToken);

            await dreamMakerClient.Update(new DreamMakerRequest
            {
                ProjectName = "tests/DMAPI/ThisDoesntExist/this_doesnt_exist"
            }, cancellationToken);

            deployJob = await dreamMakerClient.Compile(cancellationToken);
            await WaitForJob(deployJob, 30, true, ErrorCode.DreamMakerMissingDme, cancellationToken);
        }
コード例 #15
0
        public async Task RunPreWatchdog(CancellationToken cancellationToken)
        {
            const string TestRefEnvVar = "TGS4_GITHUB_REF";
            var          envVar        = Environment.GetEnvironmentVariable(TestRefEnvVar);
            string       workingBranch = null;

            if (!String.IsNullOrWhiteSpace(envVar))
            {
                workingBranch = envVar;
                Console.WriteLine($"TEST: Set working branch to '{workingBranch}' from env var '{TestRefEnvVar}'");
            }

            if (workingBranch == null)
            {
                workingBranch = "master";
                Console.WriteLine($"TEST: Set working branch to default '{workingBranch}'");
            }

            var initalRepo = await repositoryClient.Read(cancellationToken);

            Assert.IsNotNull(initalRepo);
            Assert.IsNull(initalRepo.Origin);
            Assert.IsNull(initalRepo.Reference);
            Assert.IsNull(initalRepo.RevisionInformation);
            Assert.IsNull(initalRepo.ActiveJob);

            const string Origin = "https://github.com/tgstation/tgstation-server";

            initalRepo.Origin    = new Uri(Origin);
            initalRepo.Reference = workingBranch;

            var clone = await repositoryClient.Clone(initalRepo, cancellationToken).ConfigureAwait(false);

            await ApiAssert.ThrowsException <ConflictException>(() => repositoryClient.Read(cancellationToken), ErrorCode.RepoCloning);

            Assert.IsNotNull(clone);
            Assert.AreEqual(initalRepo.Origin, clone.Origin);
            Assert.AreEqual(workingBranch, clone.Reference);
            Assert.IsNull(clone.RevisionInformation);
            Assert.IsNotNull(clone.ActiveJob);

            await WaitForJobProgressThenCancel(clone.ActiveJob, 20, cancellationToken).ConfigureAwait(false);

            var secondRead = await repositoryClient.Read(cancellationToken).ConfigureAwait(false);

            Assert.IsNotNull(secondRead);
            Assert.IsNull(secondRead.ActiveJob);

            clone = await repositoryClient.Clone(initalRepo, cancellationToken).ConfigureAwait(false);

            await WaitForJob(clone.ActiveJob, 900, false, null, cancellationToken).ConfigureAwait(false);

            var readAfterClone = await repositoryClient.Read(cancellationToken);

            Assert.AreEqual(initalRepo.Origin, readAfterClone.Origin);
            Assert.AreEqual(workingBranch, readAfterClone.Reference);
            Assert.IsNotNull(readAfterClone.RevisionInformation);
            Assert.IsNotNull(readAfterClone.RevisionInformation.ActiveTestMerges);
            Assert.AreEqual(0, readAfterClone.RevisionInformation.ActiveTestMerges.Count);
            Assert.IsNotNull(readAfterClone.RevisionInformation.CommitSha);
            Assert.IsNotNull(readAfterClone.RevisionInformation.OriginCommitSha);
            Assert.IsNotNull(readAfterClone.RevisionInformation.CompileJobs);
            Assert.AreEqual(0, readAfterClone.RevisionInformation.CompileJobs.Count);
            Assert.IsNotNull(readAfterClone.RevisionInformation.OriginCommitSha);
            Assert.IsNull(readAfterClone.RevisionInformation.PrimaryTestMerge);
            Assert.AreEqual(readAfterClone.RevisionInformation.CommitSha, readAfterClone.RevisionInformation.OriginCommitSha);

            readAfterClone.Origin = new Uri("https://github.com/tgstation/tgstation");
            await ApiAssert.ThrowsException <ApiConflictException>(() => repositoryClient.Update(readAfterClone, cancellationToken), ErrorCode.RepoCantChangeOrigin);

            readAfterClone.Origin = new Uri(Origin);

            // checkout V3 and back
            readAfterClone.Reference = "V3";
            var updated = await Checkout(readAfterClone, false, true, cancellationToken);

            // Specific SHA
            updated.CheckoutSha = "f43f5bd";
            await ApiAssert.ThrowsException <ApiConflictException>(() => Checkout(updated, false, false, cancellationToken), ErrorCode.RepoMismatchShaAndReference);

            updated.Reference = null;
            updated           = await Checkout(updated, false, false, cancellationToken);

            // Fake SHA
            updated.Reference   = null;
            updated.CheckoutSha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
            updated             = await Checkout(updated, true, false, cancellationToken);

            // Fake ref
            updated.Reference = "Tgs4IntegrationTestFakeBranchNeverNameABranchThis";
            updated           = await Checkout(updated, true, true, cancellationToken);

            // Back
            updated.Reference = workingBranch;
            updated           = await Checkout(updated, false, true, cancellationToken);

            var testPRString = Environment.GetEnvironmentVariable("TGS4_TEST_PULL_REQUEST_NUMBER");

            if (String.IsNullOrWhiteSpace(testPRString))
            {
                testPRString = Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER");
            }
            if (String.IsNullOrWhiteSpace(testPRString))
            {
                testPRString = Environment.GetEnvironmentVariable("TRAVIS_PULL_REQUEST");
            }

            if (String.IsNullOrWhiteSpace(testPRString))
            {
                if (workingBranch == "dev")
                {
                    testPRString = "957";
                }
                else
                {
                    testPRString = "958";
                }
            }

            if (!int.TryParse(testPRString, out var prNumber))
            {
                Assert.Inconclusive($"Invalid PR #: {testPRString}");
            }
            await TestMergeTests(updated, prNumber, cancellationToken);
        }