mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 07:04:57 +01:00
Add support for GitHub App installation authentication to IGitHubClientFactory
This commit is contained in:
+6
-6
@@ -79,13 +79,13 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
IGitHubService gitHubService;
|
||||
if (instanceAuthenticated)
|
||||
{
|
||||
authenticatedGitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken!);
|
||||
authenticatedGitHubService = await gitHubServiceFactory.CreateService(repositorySettings.AccessToken!, cancellationToken);
|
||||
gitHubService = authenticatedGitHubService;
|
||||
}
|
||||
else
|
||||
{
|
||||
authenticatedGitHubService = null;
|
||||
gitHubService = gitHubServiceFactory.CreateService();
|
||||
gitHubService = await gitHubServiceFactory.CreateService(cancellationToken);
|
||||
}
|
||||
|
||||
var repoOwner = remoteInformation.RemoteRepositoryOwner!;
|
||||
@@ -175,8 +175,8 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
}
|
||||
|
||||
var gitHubService = repositorySettings.AccessToken != null
|
||||
? gitHubServiceFactory.CreateService(repositorySettings.AccessToken)
|
||||
: gitHubServiceFactory.CreateService();
|
||||
? await gitHubServiceFactory.CreateService(repositorySettings.AccessToken, cancellationToken)
|
||||
: await gitHubServiceFactory.CreateService(cancellationToken);
|
||||
|
||||
var tasks = revisionInformation
|
||||
.ActiveTestMerges
|
||||
@@ -255,7 +255,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
int testMergeNumber,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var gitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken!);
|
||||
var gitHubService = await gitHubServiceFactory.CreateService(repositorySettings.AccessToken!, cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -343,7 +343,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
return;
|
||||
}
|
||||
|
||||
var gitHubService = gitHubServiceFactory.CreateService(gitHubAccessToken);
|
||||
var gitHubService = await gitHubServiceFactory.CreateService(gitHubAccessToken, cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var gitHubService = repositorySettings.AccessToken != null
|
||||
? gitHubServiceFactory.CreateService(repositorySettings.AccessToken)
|
||||
: gitHubServiceFactory.CreateService();
|
||||
? await gitHubServiceFactory.CreateService(repositorySettings.AccessToken, cancellationToken)
|
||||
: await gitHubServiceFactory.CreateService(cancellationToken);
|
||||
|
||||
PullRequest? pr = null;
|
||||
ApiException? exception = null;
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Uri? repoUrl = null;
|
||||
try
|
||||
{
|
||||
var gitHubService = gitHubServiceFactory.CreateService();
|
||||
var gitHubService = await gitHubServiceFactory.CreateService(cancellationToken);
|
||||
var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken);
|
||||
var releases = await gitHubService.GetTgsReleases(cancellationToken);
|
||||
|
||||
|
||||
@@ -288,7 +288,7 @@ namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
logger.LogDebug("Looking for GitHub releases version {version}...", newVersion);
|
||||
|
||||
var gitHubService = gitHubServiceFactory.CreateService();
|
||||
var gitHubService = await gitHubServiceFactory.CreateService(cancellationToken);
|
||||
var releases = await gitHubService.GetTgsReleases(cancellationToken);
|
||||
foreach (var kvp in releases)
|
||||
{
|
||||
|
||||
@@ -60,12 +60,12 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
{
|
||||
logger.LogTrace("Validating response code...");
|
||||
|
||||
var gitHubService = gitHubServiceFactory.CreateService();
|
||||
var gitHubService = await gitHubServiceFactory.CreateService(cancellationToken);
|
||||
var token = await gitHubService.CreateOAuthAccessToken(oAuthConfiguration, code, cancellationToken);
|
||||
if (token == null)
|
||||
return null;
|
||||
|
||||
var authenticatedClient = gitHubServiceFactory.CreateService(token);
|
||||
var authenticatedClient = await gitHubServiceFactory.CreateService(token, cancellationToken);
|
||||
|
||||
logger.LogTrace("Getting user details...");
|
||||
var userId = await authenticatedClient.GetCurrentUserId(cancellationToken);
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
using Octokit;
|
||||
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
@@ -12,7 +19,7 @@ using Tgstation.Server.Host.System;
|
||||
namespace Tgstation.Server.Host.Utils.GitHub
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class GitHubClientFactory : IGitHubClientFactory
|
||||
sealed class GitHubClientFactory : IGitHubClientFactory, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Limit to the amount of days a <see cref="GitHubClient"/> can live in the <see cref="clientCache"/>.
|
||||
@@ -45,6 +52,11 @@ namespace Tgstation.Server.Host.Utils.GitHub
|
||||
/// </summary>
|
||||
readonly Dictionary<string, (GitHubClient Client, DateTimeOffset LastUsed)> clientCache;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> used to guard access to <see cref="clientCache"/>.
|
||||
/// </summary>
|
||||
readonly SemaphoreSlim clientCacheSemaphore;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GitHubClientFactory"/> class.
|
||||
/// </summary>
|
||||
@@ -61,50 +73,139 @@ namespace Tgstation.Server.Host.Utils.GitHub
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
|
||||
clientCache = new Dictionary<string, (GitHubClient, DateTimeOffset)>();
|
||||
clientCacheSemaphore = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGitHubClient CreateClient() => GetOrCreateClient(generalConfiguration.GitHubAccessToken);
|
||||
public void Dispose() => clientCacheSemaphore.Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGitHubClient CreateClient(string accessToken)
|
||||
=> GetOrCreateClient(
|
||||
accessToken ?? throw new ArgumentNullException(nameof(accessToken)));
|
||||
public async ValueTask<IGitHubClient> CreateClient(CancellationToken cancellationToken)
|
||||
=> (await GetOrCreateClient(
|
||||
generalConfiguration.GitHubAccessToken,
|
||||
null,
|
||||
cancellationToken))!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<IGitHubClient> CreateClient(string accessToken, CancellationToken cancellationToken)
|
||||
=> (await GetOrCreateClient(
|
||||
accessToken ?? throw new ArgumentNullException(nameof(accessToken)),
|
||||
null,
|
||||
cancellationToken))!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<IGitHubClient?> CreateInstallationClient(string serializedPem, long repositoryId, CancellationToken cancellationToken)
|
||||
=> GetOrCreateClient(serializedPem, repositoryId, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a <see cref="GitHubClient"/> from the <see cref="clientCache"/> or add a new one based on a given <paramref name="accessToken"/>.
|
||||
/// Retrieve a <see cref="GitHubClient"/> from the <see cref="clientCache"/> or add a new one based on a given <paramref name="accessTokenOrSerializedPem"/>.
|
||||
/// </summary>
|
||||
/// <param name="accessToken">Optional access token to use as credentials.</param>
|
||||
/// <returns>The <see cref="GitHubClient"/> for the given <paramref name="accessToken"/>.</returns>
|
||||
GitHubClient GetOrCreateClient(string? accessToken)
|
||||
/// <param name="accessTokenOrSerializedPem">Optional access token to use as credentials or GitHub App private key. If using a private key, <paramref name="installationRepositoryId"/> must be set.</param>
|
||||
/// <param name="installationRepositoryId">Setting this specifies <paramref name="accessTokenOrSerializedPem"/> is a private key and a GitHub App installation authenticated client will be returned.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="GitHubClient"/> for the given <paramref name="accessTokenOrSerializedPem"/> or <see langword="null"/> if authentication failed.</returns>
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
async ValueTask<IGitHubClient?> GetOrCreateClient(string? accessTokenOrSerializedPem, long? installationRepositoryId, CancellationToken cancellationToken)
|
||||
#pragma warning restore CA1506
|
||||
{
|
||||
GitHubClient client;
|
||||
bool cacheHit;
|
||||
DateTimeOffset? lastUsed;
|
||||
lock (clientCache)
|
||||
using (await SemaphoreSlimContext.Lock(clientCacheSemaphore, cancellationToken))
|
||||
{
|
||||
string cacheKey;
|
||||
if (String.IsNullOrWhiteSpace(accessToken))
|
||||
if (String.IsNullOrWhiteSpace(accessTokenOrSerializedPem))
|
||||
{
|
||||
accessToken = null;
|
||||
accessTokenOrSerializedPem = null;
|
||||
cacheKey = DefaultCacheKey;
|
||||
}
|
||||
else
|
||||
cacheKey = accessToken;
|
||||
cacheKey = accessTokenOrSerializedPem;
|
||||
|
||||
cacheHit = clientCache.TryGetValue(cacheKey, out var tuple);
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
if (!cacheHit)
|
||||
{
|
||||
logger.LogTrace("Creating new GitHubClient...");
|
||||
var product = assemblyInformationProvider.ProductInfoHeaderValue.Product!;
|
||||
client = new GitHubClient(
|
||||
new ProductHeaderValue(
|
||||
product.Name,
|
||||
product.Version));
|
||||
|
||||
if (accessToken != null)
|
||||
client.Credentials = new Credentials(accessToken);
|
||||
if (accessTokenOrSerializedPem != null)
|
||||
{
|
||||
if (installationRepositoryId.HasValue)
|
||||
{
|
||||
logger.LogTrace("Performing GitHub App authentication for installation on repository {installationRepositoryId}", installationRepositoryId.Value);
|
||||
var splits = accessTokenOrSerializedPem.Split(':');
|
||||
if (splits.Length != 2)
|
||||
{
|
||||
logger.LogError("Failed to parse serialized Client ID & PEM! Expected 2 chunks, got {chunkCount}", splits.Length);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] pemBytes;
|
||||
try
|
||||
{
|
||||
pemBytes = Convert.FromBase64String(splits[1]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to parse supposed base64 PEM!");
|
||||
return null;
|
||||
}
|
||||
|
||||
var pem = Encoding.UTF8.GetString(pemBytes);
|
||||
|
||||
using var rsa = RSA.Create();
|
||||
rsa.ImportFromPem(pem);
|
||||
|
||||
var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
|
||||
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler { SetDefaultTimesOnTokenCreation = false };
|
||||
|
||||
var nowDateTime = DateTime.UtcNow;
|
||||
|
||||
var jwt = jwtSecurityTokenHandler.CreateToken(new SecurityTokenDescriptor
|
||||
{
|
||||
Issuer = splits[0],
|
||||
Expires = nowDateTime.AddMinutes(10),
|
||||
IssuedAt = nowDateTime,
|
||||
SigningCredentials = signingCredentials,
|
||||
});
|
||||
|
||||
var jwtStr = jwtSecurityTokenHandler.WriteToken(jwt);
|
||||
|
||||
client.Credentials = new Credentials(jwtStr, AuthenticationType.Bearer);
|
||||
|
||||
Installation installation;
|
||||
try
|
||||
{
|
||||
installation = await client.GitHubApps.GetRepositoryInstallationForCurrent(installationRepositoryId.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to perform app authentication!");
|
||||
return null;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
var installToken = await client.GitHubApps.CreateInstallationToken(installation.Id);
|
||||
|
||||
client.Credentials = new Credentials(installToken.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to perform installation authentication!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
client.Credentials = new Credentials(accessTokenOrSerializedPem);
|
||||
}
|
||||
|
||||
clientCache.Add(cacheKey, (Client: client, LastUsed: now));
|
||||
lastUsed = null;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -44,13 +46,16 @@ namespace Tgstation.Server.Host.Utils.GitHub
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGitHubService CreateService() => CreateServiceImpl(gitHubClientFactory.CreateClient());
|
||||
public async ValueTask<IGitHubService> CreateService(CancellationToken cancellationToken)
|
||||
=> CreateServiceImpl(
|
||||
await gitHubClientFactory.CreateClient(cancellationToken));
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAuthenticatedGitHubService CreateService(string accessToken)
|
||||
public async ValueTask<IAuthenticatedGitHubService> CreateService(string accessToken, CancellationToken cancellationToken)
|
||||
=> CreateServiceImpl(
|
||||
gitHubClientFactory.CreateClient(
|
||||
accessToken ?? throw new ArgumentNullException(nameof(accessToken))));
|
||||
await gitHubClientFactory.CreateClient(
|
||||
accessToken ?? throw new ArgumentNullException(nameof(accessToken)),
|
||||
cancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="GitHubService"/>.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Octokit;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Octokit;
|
||||
|
||||
namespace Tgstation.Server.Host.Utils.GitHub
|
||||
{
|
||||
@@ -10,14 +13,25 @@ namespace Tgstation.Server.Host.Utils.GitHub
|
||||
/// <summary>
|
||||
/// Create a <see cref="IGitHubClient"/> client. Low rate limit unless the server's GitHubAccessToken is set to bypass it.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A new <see cref="IGitHubClient"/>.</returns>
|
||||
IGitHubClient CreateClient();
|
||||
ValueTask<IGitHubClient> CreateClient(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a client with authentication using a personal access token.
|
||||
/// </summary>
|
||||
/// <param name="accessToken">The GitHub personal access token.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A new <see cref="IGitHubClient"/>.</returns>
|
||||
IGitHubClient CreateClient(string accessToken);
|
||||
ValueTask<IGitHubClient> CreateClient(string accessToken, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a GitHub App client for an installation.
|
||||
/// </summary>
|
||||
/// <param name="pem">The private key <see cref="string"/>.</param>
|
||||
/// <param name="repositoryId">The GitHub repository ID.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IGitHubClient"/> for the given <paramref name="repositoryId"/> or <see langword="null"/> if authentication failed.</returns>
|
||||
ValueTask<IGitHubClient?> CreateInstallationClient(string pem, long repositoryId, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace Tgstation.Server.Host.Utils.GitHub
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Utils.GitHub
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for <see cref="IGitHubService"/>s.
|
||||
@@ -8,14 +11,16 @@
|
||||
/// <summary>
|
||||
/// Create a <see cref="IGitHubService"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="IGitHubService"/>.</returns>
|
||||
public IGitHubService CreateService();
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IGitHubService"/>.</returns>
|
||||
public ValueTask<IGitHubService> CreateService(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create an <see cref="IAuthenticatedGitHubService"/>.
|
||||
/// </summary>
|
||||
/// <param name="accessToken">The access token to use for communication with GitHub.</param>
|
||||
/// <returns>A new <see cref="IAuthenticatedGitHubService"/>.</returns>
|
||||
public IAuthenticatedGitHubService CreateService(string accessToken);
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="IAuthenticatedGitHubService"/>.</returns>
|
||||
public ValueTask<IAuthenticatedGitHubService> CreateService(string accessToken, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -57,14 +58,14 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
|
||||
mockOptions.SetupGet(x => x.Value).Returns(gc);
|
||||
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
|
||||
|
||||
var client = factory.CreateClient();
|
||||
var client = await factory.CreateClient(CancellationToken.None);
|
||||
Assert.IsNotNull(client);
|
||||
var credentials = await client.Connection.CredentialStore.GetCredentials();
|
||||
|
||||
Assert.AreEqual(AuthenticationType.Anonymous, credentials.AuthenticationType);
|
||||
|
||||
gc.GitHubAccessToken = "asdfasdfasdfasdfasdfasdf";
|
||||
client = factory.CreateClient();
|
||||
client = await factory.CreateClient(CancellationToken.None);
|
||||
Assert.IsNotNull(client);
|
||||
credentials = await client.Connection.CredentialStore.GetCredentials();
|
||||
|
||||
@@ -83,9 +84,9 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
|
||||
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
|
||||
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(() => factory.CreateClient(null));
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => factory.CreateClient(null, CancellationToken.None).AsTask());
|
||||
|
||||
var client = factory.CreateClient("asdf");
|
||||
var client = await factory.CreateClient("asdf", CancellationToken.None);
|
||||
Assert.IsNotNull(client);
|
||||
|
||||
var credentials = await client.Connection.CredentialStore.GetCredentials();
|
||||
@@ -96,7 +97,7 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestClientCaching()
|
||||
public async Task TestClientCaching()
|
||||
{
|
||||
var mockApp = new Mock<IAssemblyInformationProvider>();
|
||||
mockApp.SetupGet(x => x.ProductInfoHeaderValue).Returns(new ProductInfoHeaderValue("TGSTests", "1.2.3")).Verifiable();
|
||||
@@ -105,10 +106,10 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
|
||||
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
|
||||
var factory = new GitHubClientFactory(mockApp.Object, loggerFactory.CreateLogger<GitHubClientFactory>(), mockOptions.Object);
|
||||
|
||||
var client1 = factory.CreateClient();
|
||||
var client2 = factory.CreateClient("asdf");
|
||||
var client3 = factory.CreateClient();
|
||||
var client4 = factory.CreateClient("asdf");
|
||||
var client1 = await factory.CreateClient(CancellationToken.None);
|
||||
var client2 = await factory.CreateClient("asdf", CancellationToken.None);
|
||||
var client3 = await factory.CreateClient(CancellationToken.None);
|
||||
var client4 = await factory.CreateClient("asdf", CancellationToken.None);
|
||||
Assert.ReferenceEquals(client1, client3);
|
||||
Assert.ReferenceEquals(client2, client4);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -28,27 +30,29 @@ namespace Tgstation.Server.Host.Utils.GitHub.Tests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestCreateService()
|
||||
public async Task TestCreateService()
|
||||
{
|
||||
var mockFactory = new Mock<IGitHubClientFactory>();
|
||||
|
||||
mockFactory.Setup(x => x.CreateClient()).Returns(Mock.Of<IGitHubClient>()).Verifiable();
|
||||
#pragma warning disable CA2012 // Use ValueTasks correctly
|
||||
mockFactory.Setup(x => x.CreateClient(It.IsAny<CancellationToken>())).Returns(ValueTask.FromResult(Mock.Of<IGitHubClient>())).Verifiable();
|
||||
|
||||
var mockToken = "asdf";
|
||||
mockFactory.Setup(x => x.CreateClient(mockToken)).Returns(Mock.Of<IGitHubClient>()).Verifiable();
|
||||
mockFactory.Setup(x => x.CreateClient(mockToken, It.IsAny<CancellationToken>())).Returns(ValueTask.FromResult(Mock.Of<IGitHubClient>())).Verifiable();
|
||||
#pragma warning restore CA2012 // Use ValueTasks correctly
|
||||
|
||||
var mockOptions = new Mock<IOptions<UpdatesConfiguration>>();
|
||||
mockOptions.SetupGet(x => x.Value).Returns(new UpdatesConfiguration());
|
||||
|
||||
var factory = new GitHubServiceFactory(mockFactory.Object, Mock.Of<ILoggerFactory>(), mockOptions.Object);
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(() => factory.CreateService(null));
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => factory.CreateService(null, CancellationToken.None).AsTask());
|
||||
Assert.AreEqual(0, mockFactory.Invocations.Count);
|
||||
|
||||
var result1 = factory.CreateService();
|
||||
var result1 = await factory.CreateService(CancellationToken.None);
|
||||
Assert.IsNotNull(result1);
|
||||
|
||||
var result2 = factory.CreateService(mockToken);
|
||||
var result2 = factory.CreateService(mockToken, CancellationToken.None);
|
||||
Assert.IsNotNull(result2);
|
||||
|
||||
mockFactory.VerifyAll();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -18,13 +20,13 @@ namespace Tgstation.Server.Tests.Live
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public IGitHubService CreateService() => CreateDummyService();
|
||||
public ValueTask<IGitHubService> CreateService(CancellationToken cancellationToken) => ValueTask.FromResult<IGitHubService>(CreateDummyService());
|
||||
|
||||
public IAuthenticatedGitHubService CreateService(string accessToken)
|
||||
public ValueTask<IAuthenticatedGitHubService> CreateService(string accessToken, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(accessToken);
|
||||
|
||||
return CreateDummyService();
|
||||
return ValueTask.FromResult<IAuthenticatedGitHubService>(CreateDummyService());
|
||||
}
|
||||
|
||||
TestingGitHubService CreateDummyService() => new TestingGitHubService(cryptographySuite, logger);
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
});
|
||||
|
||||
var gitHubClientFactory = new GitHubClientFactory(new AssemblyInformationProvider(), Mock.Of<ILogger<GitHubClientFactory>>(), mockOptions.Object);
|
||||
RealClient = gitHubClientFactory.CreateClient();
|
||||
RealClient = gitHubClientFactory.CreateClient(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public static async Task InitializeAndInject(CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user