Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/make automatic header optional #542

Merged
merged 9 commits into from
Apr 13, 2023
18 changes: 7 additions & 11 deletions src/GraphQL.Client/GraphQLHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,13 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson
_disposeHttpClient = true;
}

public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use");
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));

if (!HttpClient.DefaultRequestHeaders.UserAgent.Any())
HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString()));

_lazyHttpWebSocket = new Lazy<GraphQLHttpWebSocket>(CreateGraphQLHttpWebSocket);
}
public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use");
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_lazyHttpWebSocket = new Lazy<GraphQLHttpWebSocket>(CreateGraphQLHttpWebSocket);
}

#endregion

Expand Down
7 changes: 7 additions & 0 deletions src/GraphQL.Client/GraphQLHttpClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,11 @@ public class GraphQLHttpClientOptions
/// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init.
/// </summary>
public Func<GraphQLHttpClientOptions, object?> ConfigureWebSocketConnectionInitPayload { get; set; } = options => null;

/// <summary>
/// The default user agent request header.
/// Default to the GraphQL client assembly.
/// </summary>
public ProductInfoHeaderValue? DefaultUserAgentRequestHeader { get; set; }
= new ProductInfoHeaderValue(typeof(GraphQLHttpClient).Assembly.GetName().Name, typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString());
}
3 changes: 3 additions & 0 deletions src/GraphQL.Client/GraphQLHttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
message.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8"));

if (options.DefaultUserAgentRequestHeader != null)
message.Headers.UserAgent.Add(options.DefaultUserAgentRequestHeader);

#pragma warning disable CS0618 // Type or member is obsolete
PreprocessHttpRequestMessage(message);
#pragma warning restore CS0618 // Type or member is obsolete
Expand Down
19 changes: 18 additions & 1 deletion tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using GraphQL.Types;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace GraphQL.Client.Tests.Common.Chat.Schema;

public class ChatQuery : ObjectGraphType
{
private readonly IServiceProvider _serviceProvider;

public static readonly Dictionary<string, object> TestExtensions = new()
{
{"extension1", "hello world"},
Expand All @@ -16,8 +20,9 @@ public class ChatQuery : ObjectGraphType
public readonly ManualResetEventSlim LongRunningQueryBlocker = new ManualResetEventSlim();
public readonly ManualResetEventSlim WaitingOnQueryBlocker = new ManualResetEventSlim();

public ChatQuery(IChat chat)
public ChatQuery(IChat chat, IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
Name = "ChatQuery";

Field<ListGraphType<MessageType>>("messages").Resolve(context => chat.AllMessages.Take(100));
Expand All @@ -37,5 +42,17 @@ public ChatQuery(IChat chat)
WaitingOnQueryBlocker.Reset();
return "finally returned";
});

Field<StringGraphType>("clientUserAgent")
.Resolve(context =>
{
var contextAccessor = _serviceProvider.GetRequiredService<IHttpContextAccessor>();
if (!contextAccessor.HttpContext.Request.Headers.UserAgent.Any())
{
context.Errors.Add(new ExecutionError("user agent header not set"));
return null;
}
return contextAccessor.HttpContext.Request.Headers.UserAgent.ToString();
});
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand All @@ -16,4 +16,7 @@
<ProjectReference Include="..\..\src\GraphQL.Client\GraphQL.Client.csproj" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
Copy link
Member

@sungam3r sungam3r Apr 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can change <Project Sdk="Microsoft.NET.Sdk"> to <Project Sdk="Microsoft.NET.Sdk.Web"> to achive the same.

</ItemGroup>
</Project>
12 changes: 12 additions & 0 deletions tests/GraphQL.Client.Tests.Common/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"profiles": {
"GraphQL.Client.Tests.Common": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:59034"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public abstract class IntegrationServerTestFixture
{
public int Port { get; private set; }

public IWebHost Server { get; private set; }
public IWebHost? Server { get; private set; }

public abstract IGraphQLWebsocketJsonSerializer Serializer { get; }

Expand All @@ -27,7 +27,7 @@ public async Task CreateServer()
{
if (Server != null)
return;
Server = await WebHostHelpers.CreateServer(Port);
Server = await WebHostHelpers.CreateServer(Port).ConfigureAwait(false);
}

public async Task ShutdownServer()
Expand All @@ -40,18 +40,20 @@ public async Task ShutdownServer()
Server = null;
}

public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false)
=> GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket);
public GraphQLHttpClient GetStarWarsClient(Action<GraphQLHttpClientOptions>? configure = null)
=> GetGraphQLClient(Common.STAR_WARS_ENDPOINT, configure);

public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false)
=> GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket);
public GraphQLHttpClient GetChatClient(Action<GraphQLHttpClientOptions>? configure = null)
=> GetGraphQLClient(Common.CHAT_ENDPOINT, configure);

private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false)
{
if (Serializer == null)
throw new InvalidOperationException("JSON serializer not configured");
return WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket, Serializer, WebsocketProtocol);
}
private GraphQLHttpClient GetGraphQLClient(string endpoint, Action<GraphQLHttpClientOptions>? configure) =>
Serializer == null
? throw new InvalidOperationException("JSON serializer not configured")
: WebHostHelpers.GetGraphQLClient(Port, endpoint, Serializer, options =>
{
configure?.Invoke(options);
options.WebSocketProtocol = WebsocketProtocol;
});
}

public class NewtonsoftGraphQLWsServerTestFixture : IntegrationServerTestFixture
Expand Down
42 changes: 9 additions & 33 deletions tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
using GraphQL.Client.Http;
using GraphQL.Client.Http.Websocket;
using GraphQL.Client.Serializer.Newtonsoft;
using GraphQL.Client.Tests.Common;
using GraphQL.Client.Tests.Common.Helpers;
using IntegrationTestServer;

namespace GraphQL.Integration.Tests.Helpers;
Expand Down Expand Up @@ -31,37 +29,15 @@ public static async Task<IWebHost> CreateServer(int port)
return host;
}

public static GraphQLHttpClient GetGraphQLClient(int port, string endpoint, bool requestsViaWebsocket = false, IGraphQLWebsocketJsonSerializer serializer = null, string websocketProtocol = WebSocketProtocols.GRAPHQL_WS)
=> new GraphQLHttpClient(new GraphQLHttpClientOptions
{
EndPoint = new Uri($"http://localhost:{port}{endpoint}"),
UseWebSocketForQueriesAndMutations = requestsViaWebsocket,
WebSocketProtocol = websocketProtocol
},
serializer ?? new NewtonsoftJsonSerializer());
}

public class TestServerSetup : IDisposable
{
public TestServerSetup(IGraphQLWebsocketJsonSerializer serializer)
public static GraphQLHttpClient GetGraphQLClient(
int port,
string endpoint,
IGraphQLWebsocketJsonSerializer? serializer = null,
Action<GraphQLHttpClientOptions>? configure = null)
{
Serializer = serializer;
Port = NetworkHelpers.GetFreeTcpPortNumber();
var options = new GraphQLHttpClientOptions();
configure?.Invoke(options);
options.EndPoint = new Uri($"http://localhost:{port}{endpoint}");
return new GraphQLHttpClient(options, serializer ?? new NewtonsoftJsonSerializer());
}

public int Port { get; }

public IWebHost Server { get; set; }

public IGraphQLWebsocketJsonSerializer Serializer { get; set; }

public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false)
=> GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket);

public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false)
=> GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket);

private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false) => WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket);

public void Dispose() => Server?.Dispose();
}
68 changes: 68 additions & 0 deletions tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Net.Http.Headers;
using FluentAssertions;
using GraphQL.Client.Abstractions;
using GraphQL.Client.Http;
using GraphQL.Integration.Tests.Helpers;
using Xunit;

namespace GraphQL.Integration.Tests;

public class UserAgentHeaderTests : IAsyncLifetime, IClassFixture<SystemTextJsonAutoNegotiateServerTestFixture>
{
private readonly IntegrationServerTestFixture Fixture;
private GraphQLHttpClient? ChatClient;

public UserAgentHeaderTests(SystemTextJsonAutoNegotiateServerTestFixture fixture)
{
Fixture = fixture;
}

public async Task InitializeAsync() => await Fixture.CreateServer().ConfigureAwait(false);

public Task DisposeAsync()
{
ChatClient?.Dispose();
return Task.CompletedTask;
}

[Fact]
public async void Can_set_custom_user_agent()
{
const string userAgent = "CustomUserAgent";
ChatClient = Fixture.GetChatClient(options => options.DefaultUserAgentRequestHeader = ProductInfoHeaderValue.Parse(userAgent));

var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }");
var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false);

response.Errors.Should().BeNull();
response.Data.clientUserAgent.Should().Be(userAgent);
}

[Fact]
public async void Default_user_agent_is_set_as_expected()
{
string? expectedUserAgent = new ProductInfoHeaderValue(
typeof(GraphQLHttpClient).Assembly.GetName().Name,
typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString()).ToString();

ChatClient = Fixture.GetChatClient();

var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }");
var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false);

response.Errors.Should().BeNull();
response.Data.clientUserAgent.Should().Be(expectedUserAgent);
}

[Fact]
public async void No_Default_user_agent_if_set_to_null()
{
ChatClient = Fixture.GetChatClient(options => options.DefaultUserAgentRequestHeader = null);

var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }");
var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false);

response.Errors.Should().HaveCount(1);
response.Errors[0].Message.Should().Be("user agent header not set");
}
}
4 changes: 2 additions & 2 deletions tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public abstract class Base : IAsyncLifetime
{
protected readonly ITestOutputHelper Output;
protected readonly IntegrationServerTestFixture Fixture;
protected GraphQLHttpClient ChatClient;
protected GraphQLHttpClient? ChatClient;

protected Base(ITestOutputHelper output, IntegrationServerTestFixture fixture)
{
Expand All @@ -43,7 +43,7 @@ public async Task InitializeAsync()
Fixture.Server.Services.GetService<Chat>().AddMessage(InitialMessage);

// then create the chat client
ChatClient ??= Fixture.GetChatClient(true);
ChatClient ??= Fixture.GetChatClient(options => options.UseWebSocketForQueriesAndMutations = true);
}

public Task DisposeAsync()
Expand Down
2 changes: 1 addition & 1 deletion tests/IntegrationTestServer/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public Startup(IConfiguration configuration, IWebHostEnvironment environment)
public void ConfigureServices(IServiceCollection services)
{
services.Configure<KestrelServerOptions>(options => options.AllowSynchronousIO = true);
//
services.AddHttpContextAccessor();
services.AddChatSchema();
services.AddStarWarsSchema();
services.AddGraphQL(builder => builder
Expand Down