mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-21 20:17:22 +01:00
Merge branch 'master' into dev
This commit is contained in:
@@ -41,7 +41,6 @@ env:
|
||||
TGS_WEBPANEL_NODE_VERSION: 20.x
|
||||
TGS_TEST_GITHUB_TOKEN: ${{ secrets.LIVE_TESTS_TOKEN }}
|
||||
PACKAGING_PRIVATE_KEY_PASSPHRASE: ${{ secrets.PACKAGING_PRIVATE_KEY_PASSPHRASE }}
|
||||
Internal__EnableGraphQL: true
|
||||
|
||||
concurrency:
|
||||
group: "ci-${{ (github.event_name != 'push' && github.event_name != 'schedule' && github.event.inputs.pull_request_number) || github.run_id }}-${{ github.event_name }}"
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="WebpanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>6.11.0</TgsCoreVersion>
|
||||
<TgsCoreVersion>6.11.1</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.3.0</TgsConfigVersion>
|
||||
<TgsRestVersion>10.10.0</TgsRestVersion>
|
||||
<TgsGraphQLVersion>0.2.0</TgsGraphQLVersion>
|
||||
|
||||
@@ -295,55 +295,54 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<IAbstractHttpClientFactory, AbstractHttpClientFactory>();
|
||||
|
||||
// configure graphql
|
||||
if (postSetupServices.InternalConfiguration.EnableGraphQL)
|
||||
services
|
||||
.AddScoped<GraphQL.Subscriptions.ITopicEventReceiver, ShutdownAwareTopicEventReceiver>()
|
||||
.AddGraphQLServer()
|
||||
.AddAuthorization()
|
||||
.ModifyOptions(options =>
|
||||
{
|
||||
options.EnsureAllNodesCanBeResolved = true;
|
||||
options.EnableFlagEnums = true;
|
||||
})
|
||||
services
|
||||
.AddScoped<GraphQL.Subscriptions.ITopicEventReceiver, ShutdownAwareTopicEventReceiver>()
|
||||
.AddGraphQLServer()
|
||||
.AddAuthorization()
|
||||
.ModifyOptions(options =>
|
||||
{
|
||||
options.EnsureAllNodesCanBeResolved = true;
|
||||
options.EnableFlagEnums = true;
|
||||
})
|
||||
#if DEBUG
|
||||
.ModifyCostOptions(options =>
|
||||
{
|
||||
options.EnforceCostLimits = false;
|
||||
})
|
||||
.ModifyCostOptions(options =>
|
||||
{
|
||||
options.EnforceCostLimits = false;
|
||||
})
|
||||
#endif
|
||||
.AddMutationConventions()
|
||||
.AddInMemorySubscriptions(
|
||||
new SubscriptionOptions
|
||||
{
|
||||
TopicBufferCapacity = 1024, // mainly so high for tests, not possible to DoS the server without authentication and some other access to generate messages
|
||||
})
|
||||
.AddGlobalObjectIdentification()
|
||||
.AddQueryFieldToMutationPayloads()
|
||||
.ModifyOptions(options =>
|
||||
.AddMutationConventions()
|
||||
.AddInMemorySubscriptions(
|
||||
new SubscriptionOptions
|
||||
{
|
||||
options.EnableDefer = true;
|
||||
TopicBufferCapacity = 1024, // mainly so high for tests, not possible to DoS the server without authentication and some other access to generate messages
|
||||
})
|
||||
.ModifyPagingOptions(pagingOptions =>
|
||||
{
|
||||
pagingOptions.IncludeTotalCount = true;
|
||||
pagingOptions.RequirePagingBoundaries = false;
|
||||
pagingOptions.DefaultPageSize = ApiController.DefaultPageSize;
|
||||
pagingOptions.MaxPageSize = ApiController.MaximumPageSize;
|
||||
})
|
||||
.AddFiltering()
|
||||
.AddSorting()
|
||||
.AddHostTypes()
|
||||
.AddErrorFilter<ErrorMessageFilter>()
|
||||
.AddType<StandaloneNode>()
|
||||
.AddType<LocalGateway>()
|
||||
.AddType<RemoteGateway>()
|
||||
.AddType<GraphQL.Types.UserName>()
|
||||
.AddType<UnsignedIntType>()
|
||||
.BindRuntimeType<Version, SemverType>()
|
||||
.TryAddTypeInterceptor<RightsTypeInterceptor>()
|
||||
.AddQueryType<Query>()
|
||||
.AddMutationType<Mutation>()
|
||||
.AddSubscriptionType<Subscription>();
|
||||
.AddGlobalObjectIdentification()
|
||||
.AddQueryFieldToMutationPayloads()
|
||||
.ModifyOptions(options =>
|
||||
{
|
||||
options.EnableDefer = true;
|
||||
})
|
||||
.ModifyPagingOptions(pagingOptions =>
|
||||
{
|
||||
pagingOptions.IncludeTotalCount = true;
|
||||
pagingOptions.RequirePagingBoundaries = false;
|
||||
pagingOptions.DefaultPageSize = ApiController.DefaultPageSize;
|
||||
pagingOptions.MaxPageSize = ApiController.MaximumPageSize;
|
||||
})
|
||||
.AddFiltering()
|
||||
.AddSorting()
|
||||
.AddHostTypes()
|
||||
.AddErrorFilter<ErrorMessageFilter>()
|
||||
.AddType<StandaloneNode>()
|
||||
.AddType<LocalGateway>()
|
||||
.AddType<RemoteGateway>()
|
||||
.AddType<GraphQL.Types.UserName>()
|
||||
.AddType<UnsignedIntType>()
|
||||
.BindRuntimeType<Version, SemverType>()
|
||||
.TryAddTypeInterceptor<RightsTypeInterceptor>()
|
||||
.AddQueryType<Query>()
|
||||
.AddMutationType<Mutation>()
|
||||
.AddSubscriptionType<Subscription>();
|
||||
|
||||
void AddTypedContext<TContext>()
|
||||
where TContext : DatabaseContext
|
||||
|
||||
@@ -158,6 +158,9 @@ namespace Tgstation.Server.Tests.Live
|
||||
"Telemetry:DisableVersionReporting=true",
|
||||
};
|
||||
|
||||
if (MultiServerClient.UseGraphQL)
|
||||
args.Add("Internal:EnableGraphQL=true");
|
||||
|
||||
swarmArgs = new List<string>();
|
||||
if (swarmConfiguration != null)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
public ValueTask DisposeAsync()
|
||||
=> ValueTaskExtensions.WhenAll(
|
||||
RestClient.DisposeAsync(),
|
||||
GraphQLClient.DisposeAsync());
|
||||
GraphQLClient?.DisposeAsync() ?? ValueTask.CompletedTask);
|
||||
|
||||
public ValueTask Execute(
|
||||
Func<IRestServerClient, ValueTask> restAction,
|
||||
@@ -48,6 +48,11 @@ namespace Tgstation.Server.Tests.Live
|
||||
where TGraphQLResult : class
|
||||
{
|
||||
var restTask = restAction(RestClient);
|
||||
if (!UseGraphQL)
|
||||
{
|
||||
return (await restTask, null);
|
||||
}
|
||||
|
||||
var graphQLResult = await GraphQLClient.RunOperation(graphQLAction, cancellationToken);
|
||||
|
||||
graphQLResult.EnsureNoErrors();
|
||||
@@ -60,6 +65,6 @@ namespace Tgstation.Server.Tests.Live
|
||||
}
|
||||
|
||||
public ValueTask<IDisposable> Subscribe<TResultData>(Func<IGraphQLClient, IObservable<IOperationResult<TResultData>>> operationExecutor, IObserver<IOperationResult<TResultData>> observer, CancellationToken cancellationToken) where TResultData : class
|
||||
=> GraphQLClient.Subscribe(operationExecutor, observer, cancellationToken);
|
||||
=> GraphQLClient?.Subscribe(operationExecutor, observer, cancellationToken) ?? ValueTask.FromResult<IDisposable>(new CancellationTokenSource());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,6 +457,9 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
static async Task TestGraphQLLogin(IRestServerClientFactory clientFactory, IRestServerClient restClient, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!MultiServerClient.UseGraphQL)
|
||||
return;
|
||||
|
||||
await using var gqlClient = new GraphQLServerClientFactory(clientFactory).CreateUnauthenticated(restClient.Url);
|
||||
var result = await gqlClient.RunOperation(client => client.Login.ExecuteAsync(cancellationToken), cancellationToken);
|
||||
|
||||
|
||||
@@ -1372,12 +1372,13 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
var firstAdminRestClient = firstAdminMultiClient.RestClient;
|
||||
|
||||
await using (var tokenOnlyGraphQLClient = graphQLClientFactory.CreateFromToken(server.RootUrl, firstAdminRestClient.Token.Bearer))
|
||||
{
|
||||
// just testing auth works the same here
|
||||
var result = await tokenOnlyGraphQLClient.RunOperation(client => client.ServerVersion.ExecuteAsync(cancellationToken), cancellationToken);
|
||||
Assert.IsTrue(result.IsSuccessResult());
|
||||
}
|
||||
if (MultiServerClient.UseGraphQL)
|
||||
await using (var tokenOnlyGraphQLClient = graphQLClientFactory.CreateFromToken(server.RootUrl, firstAdminRestClient.Token.Bearer))
|
||||
{
|
||||
// just testing auth works the same here
|
||||
var result = await tokenOnlyGraphQLClient.RunOperation(client => client.ServerVersion.ExecuteAsync(cancellationToken), cancellationToken);
|
||||
Assert.IsTrue(result.IsSuccessResult());
|
||||
}
|
||||
|
||||
await using (var tokenOnlyRestClient = restClientFactory.CreateFromToken(server.RootUrl, firstAdminRestClient.Token))
|
||||
{
|
||||
@@ -1393,32 +1394,33 @@ namespace Tgstation.Server.Tests.Live
|
||||
}
|
||||
|
||||
// basic graphql test, to be used everywhere eventually
|
||||
await using (var unauthenticatedGraphQLClient = graphQLClientFactory.CreateUnauthenticated(server.RootUrl))
|
||||
{
|
||||
// check auth works as expected
|
||||
var result = await unauthenticatedGraphQLClient.RunOperation(client => client.ServerVersion.ExecuteAsync(cancellationToken), cancellationToken);
|
||||
Assert.IsTrue(result.IsErrorResult());
|
||||
if (MultiServerClient.UseGraphQL)
|
||||
await using (var unauthenticatedGraphQLClient = graphQLClientFactory.CreateUnauthenticated(server.RootUrl))
|
||||
{
|
||||
// check auth works as expected
|
||||
var result = await unauthenticatedGraphQLClient.RunOperation(client => client.ServerVersion.ExecuteAsync(cancellationToken), cancellationToken);
|
||||
Assert.IsTrue(result.IsErrorResult());
|
||||
|
||||
// test getting server info
|
||||
var unAuthedMultiClient = new MultiServerClient(firstAdminRestClient, unauthenticatedGraphQLClient);
|
||||
// test getting server info
|
||||
var unAuthedMultiClient = new MultiServerClient(firstAdminRestClient, unauthenticatedGraphQLClient);
|
||||
|
||||
await unauthenticatedGraphQLClient.RunQueryEnsureNoErrors(
|
||||
gqlClient => gqlClient.UnauthenticatedServerInformation.ExecuteAsync(cancellationToken),
|
||||
cancellationToken);
|
||||
await unauthenticatedGraphQLClient.RunQueryEnsureNoErrors(
|
||||
gqlClient => gqlClient.UnauthenticatedServerInformation.ExecuteAsync(cancellationToken),
|
||||
cancellationToken);
|
||||
|
||||
var testObserver = new HoldLastObserver<IOperationResult<ISessionInvalidationResult>>();
|
||||
using var subscription = await unauthenticatedGraphQLClient.Subscribe(
|
||||
gql => gql.SessionInvalidation.Watch(),
|
||||
testObserver,
|
||||
cancellationToken);
|
||||
var testObserver = new HoldLastObserver<IOperationResult<ISessionInvalidationResult>>();
|
||||
using var subscription = await unauthenticatedGraphQLClient.Subscribe(
|
||||
gql => gql.SessionInvalidation.Watch(),
|
||||
testObserver,
|
||||
cancellationToken);
|
||||
|
||||
await Task.Delay(1000, cancellationToken);
|
||||
await Task.Delay(1000, cancellationToken);
|
||||
|
||||
Assert.AreEqual(0U, testObserver.ErrorCount);
|
||||
Assert.AreEqual(1U, testObserver.ResultCount);
|
||||
Assert.IsTrue(testObserver.LastValue.IsAuthenticationError());
|
||||
Assert.IsTrue(testObserver.Completed);
|
||||
}
|
||||
Assert.AreEqual(0U, testObserver.ErrorCount);
|
||||
Assert.AreEqual(1U, testObserver.ResultCount);
|
||||
Assert.IsTrue(testObserver.LastValue.IsAuthenticationError());
|
||||
Assert.IsTrue(testObserver.Completed);
|
||||
}
|
||||
|
||||
async ValueTask<MultiServerClient> CreateUserWithNoInstancePerms()
|
||||
{
|
||||
@@ -1477,9 +1479,10 @@ namespace Tgstation.Server.Tests.Live
|
||||
if (!openDreamOnly)
|
||||
{
|
||||
// force a session refresh if necessary
|
||||
await firstAdminMultiClient.GraphQLClient.RunQueryEnsureNoErrors(
|
||||
gql => gql.ReadCurrentUser.ExecuteAsync(cancellationToken),
|
||||
cancellationToken);
|
||||
if (MultiServerClient.UseGraphQL)
|
||||
await firstAdminMultiClient.GraphQLClient.RunQueryEnsureNoErrors(
|
||||
gql => gql.ReadCurrentUser.ExecuteAsync(cancellationToken),
|
||||
cancellationToken);
|
||||
|
||||
jobsHubTestTask = FailFast(await jobsHubTest.Run(cancellationToken)); // returns Task<Task>
|
||||
var rootTest = FailFast(RawRequestTests.Run(restClientFactory, firstAdminRestClient, cancellationToken));
|
||||
@@ -1610,14 +1613,19 @@ namespace Tgstation.Server.Tests.Live
|
||||
initialSessionId = dd.SessionId.Value;
|
||||
|
||||
// force a session refresh if necessary
|
||||
await firstAdminMultiClient.GraphQLClient.RunQueryEnsureNoErrors(
|
||||
gql => gql.ReadCurrentUser.ExecuteAsync(cancellationToken),
|
||||
cancellationToken);
|
||||
if (MultiServerClient.UseGraphQL)
|
||||
{
|
||||
await firstAdminMultiClient.GraphQLClient.RunQueryEnsureNoErrors(
|
||||
gql => gql.ReadCurrentUser.ExecuteAsync(cancellationToken),
|
||||
cancellationToken);
|
||||
|
||||
restartSubscription = await firstAdminMultiClient.GraphQLClient.Subscribe(
|
||||
gql => gql.SessionInvalidation.Watch(),
|
||||
restartObserver,
|
||||
cancellationToken);
|
||||
restartSubscription = await firstAdminMultiClient.GraphQLClient.Subscribe(
|
||||
gql => gql.SessionInvalidation.Watch(),
|
||||
restartObserver,
|
||||
cancellationToken);
|
||||
}
|
||||
else
|
||||
restartSubscription = null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1628,7 +1636,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
}
|
||||
catch
|
||||
{
|
||||
restartSubscription.Dispose();
|
||||
restartSubscription?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -1638,15 +1646,18 @@ namespace Tgstation.Server.Tests.Live
|
||||
await Task.WhenAny(serverTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken));
|
||||
Assert.IsTrue(serverTask.IsCompleted);
|
||||
|
||||
Assert.AreEqual(0U, restartObserver.ErrorCount);
|
||||
Assert.AreEqual(1U, restartObserver.ResultCount);
|
||||
restartObserver.LastValue.EnsureNoErrors();
|
||||
Assert.IsTrue(restartObserver.Completed);
|
||||
Assert.AreEqual(SessionInvalidationReason.ServerShutdown, restartObserver.LastValue.Data.SessionInvalidated);
|
||||
if (MultiServerClient.UseGraphQL)
|
||||
{
|
||||
Assert.AreEqual(0U, restartObserver.ErrorCount);
|
||||
Assert.AreEqual(1U, restartObserver.ResultCount);
|
||||
restartObserver.LastValue.EnsureNoErrors();
|
||||
Assert.IsTrue(restartObserver.Completed);
|
||||
Assert.AreEqual(SessionInvalidationReason.ServerShutdown, restartObserver.LastValue.Data.SessionInvalidated);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
restartSubscription.Dispose();
|
||||
restartSubscription?.Dispose();
|
||||
}
|
||||
|
||||
// test the reattach message queueing
|
||||
@@ -1930,11 +1941,13 @@ namespace Tgstation.Server.Tests.Live
|
||||
password,
|
||||
cancellationToken: cancellationToken);
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
graphQLClientTask = graphQLClientFactory.CreateFromLogin(
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
cancellationToken: cts.Token);
|
||||
graphQLClientTask = MultiServerClient.UseGraphQL
|
||||
? graphQLClientFactory.CreateFromLogin(
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
cancellationToken: cts.Token)
|
||||
: ValueTask.FromResult<IAuthenticatedGraphQLServerClient>(null);
|
||||
|
||||
IRestServerClient restClient;
|
||||
try
|
||||
|
||||
@@ -48,6 +48,9 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
await TestPagination(cancellationToken);
|
||||
|
||||
if (!MultiServerClient.UseGraphQL)
|
||||
return;
|
||||
|
||||
Assert.IsFalse(observer.Completed);
|
||||
Assert.AreEqual(0U, observer.ErrorCount);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user