From 7efe212d62f7b7aa8271e9ce17e04f419f0a3e1e Mon Sep 17 00:00:00 2001 From: Tobias Tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Tue, 21 Feb 2023 11:39:14 +0100 Subject: [PATCH] Fixed HttpTransportVersion.Legacy selecting wrong Content-Type with media type ranges (#5865) --- .../AspNetCore/src/AspNetCore/ContentType.cs | 10 +- .../AspNetCoreResources.Designer.cs | 319 +++++--- .../Properties/AspNetCoreResources.resx | 2 +- .../DefaultHttpResponseFormatter.cs | 29 +- .../GraphQLOverHttpSpecTests.cs | 747 ++++-------------- 5 files changed, 409 insertions(+), 698 deletions(-) diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs index a3e4646ab99..f2c42773a3b 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/ContentType.cs @@ -6,11 +6,11 @@ internal static class ContentType { private const string _utf8 = "charset=utf-8"; private const string _boundary = "boundary=\"-\""; - public const string GraphQL = $"{Types.Application}/{SubTypes.GraphQL};{_utf8}"; - public const string Json = $"{Types.Application}/{SubTypes.Json};{_utf8}"; - public const string MultiPartMixed = $"{Types.MultiPart}/{SubTypes.Mixed};{_boundary}"; - public const string GraphQLResponse = $"{Types.Application}/{SubTypes.GraphQLResponse};{_utf8}"; - public const string EventStream = $"{Types.Text}/{SubTypes.EventStream};{_utf8}"; + public const string GraphQL = $"{Types.Application}/{SubTypes.GraphQL}; {_utf8}"; + public const string Json = $"{Types.Application}/{SubTypes.Json}; {_utf8}"; + public const string MultiPartMixed = $"{Types.MultiPart}/{SubTypes.Mixed}; {_boundary}"; + public const string GraphQLResponse = $"{Types.Application}/{SubTypes.GraphQLResponse}; {_utf8}"; + public const string EventStream = $"{Types.Text}/{SubTypes.EventStream}; {_utf8}"; public const string Html = $"{Types.Text}/{SubTypes.Html}"; private static readonly char[] _jsonArray = diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.Designer.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.Designer.cs index 7d804a563c8..bd2ef55133d 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.Designer.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.Designer.cs @@ -1,6 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -11,32 +12,46 @@ namespace HotChocolate.AspNetCore.Properties { using System; - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [System.Diagnostics.DebuggerNonUserCodeAttribute()] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class AspNetCoreResources { - private static System.Resources.ResourceManager resourceMan; + private static global::System.Resources.ResourceManager resourceMan; - private static System.Globalization.CultureInfo resourceCulture; + private static global::System.Globalization.CultureInfo resourceCulture; - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal AspNetCoreResources() { } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { get { - if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.AspNetCore.Properties.AspNetCoreResources", typeof(AspNetCoreResources).Assembly); + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HotChocolate.AspNetCore.Properties.AspNetCoreResources", typeof(AspNetCoreResources).Assembly); resourceMan = temp; } return resourceMan; } } - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -45,243 +60,363 @@ internal static System.Globalization.CultureInfo Culture { } } - internal static string ThrowHelper_DefaultHttpRequestParser_RequestIsEmpty { + /// + /// Looks up a localized string similar to Invalid message type.. + /// + internal static string Apollo_OnReceive_InvalidMessageType { get { - return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_RequestIsEmpty", resourceCulture); + return ResourceManager.GetString("Apollo_OnReceive_InvalidMessageType", resourceCulture); } } - internal static string ThrowHelper_DefaultHttpRequestParser_QueryAndIdMissing { + /// + /// Looks up a localized string similar to Invalid subscribe message structure.. + /// + internal static string Apollo_OnReceive_InvalidSubscribeMessage { get { - return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_QueryAndIdMissing", resourceCulture); + return ResourceManager.GetString("Apollo_OnReceive_InvalidSubscribeMessage", resourceCulture); } } - internal static string ThrowHelper_DefaultHttpRequestParser_MaxRequestSizeExceeded { + /// + /// Looks up a localized string similar to A message must be a json object.. + /// + internal static string Apollo_OnReceive_MessageMustBeJson { get { - return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_MaxRequestSizeExceeded", resourceCulture); + return ResourceManager.GetString("Apollo_OnReceive_MessageMustBeJson", resourceCulture); } } - internal static string ThrowHelper_DataStartMessageHandler_RequestTypeNotSupported { + /// + /// Looks up a localized string similar to The subscription id is not unique.. + /// + internal static string Apollo_OnReceive_SubscriptionIdNotUnique { get { - return ResourceManager.GetString("ThrowHelper_DataStartMessageHandler_RequestTypeNotSupported", resourceCulture); + return ResourceManager.GetString("Apollo_OnReceive_SubscriptionIdNotUnique", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_Invalid_Form { + /// + /// Looks up a localized string similar to Too many initialization requests.. + /// + internal static string Apollo_OnReceive_ToManyInitializations { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_Invalid_Form", resourceCulture); + return ResourceManager.GetString("Apollo_OnReceive_ToManyInitializations", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_No_Operations_Specified { + /// + /// Looks up a localized string similar to The type property on the message is mandatory.. + /// + internal static string Apollo_OnReceive_TypePropMissing { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_No_Operations_Specified", resourceCulture); + return ResourceManager.GetString("Apollo_OnReceive_TypePropMissing", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_Fields_Misordered { + /// + /// Looks up a localized string similar to Message cannot be null or empty.. + /// + internal static string ConnectionStatus_Reject_Message_cannot_be_null_or_empty_ { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_Fields_Misordered", resourceCulture); + return ResourceManager.GetString("ConnectionStatus_Reject_Message_cannot_be_null_or_empty_", resourceCulture); } } - internal static string HttpMultipartMiddleware_InsertFilesIntoRequest_VariablesImmutable { + /// + /// Looks up a localized string similar to Message cannot be null or empty.. + /// + internal static string ConnectionStatus_Reject_MessageCannotBeNullOrEmpty { get { - return ResourceManager.GetString("HttpMultipartMiddleware_InsertFilesIntoRequest_VariablesImmutable", resourceCulture); + return ResourceManager.GetString("ConnectionStatus_Reject_MessageCannotBeNullOrEmpty", resourceCulture); } } - internal static string VariablePath_Parse_FirstSegmentMustBeKey { + /// + /// Looks up a localized string similar to The specified result object is not a valid subscription result.. + /// + internal static string DataStartMessageHandler_Not_A_SubscriptionResult { get { - return ResourceManager.GetString("VariablePath_Parse_FirstSegmentMustBeKey", resourceCulture); + return ResourceManager.GetString("DataStartMessageHandler_Not_A_SubscriptionResult", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_NoObjectPath { + /// + /// Looks up a localized string similar to Unable to parse the accept header value `{0}`.. + /// + internal static string ErrorHelper_InvalidAcceptMediaType { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_NoObjectPath", resourceCulture); + return ResourceManager.GetString("ErrorHelper_InvalidAcceptMediaType", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_FileMissing { + /// + /// Looks up a localized string similar to Invalid GraphQL Request.. + /// + internal static string ErrorHelper_InvalidRequest { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_FileMissing", resourceCulture); + return ResourceManager.GetString("ErrorHelper_InvalidRequest", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_VariableNotFound { + /// + /// Looks up a localized string similar to The type name is invalid.. + /// + internal static string ErrorHelper_InvalidTypeName { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_VariableNotFound", resourceCulture); + return ResourceManager.GetString("ErrorHelper_InvalidTypeName", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_VariableStructureInvalid { + /// + /// Looks up a localized string similar to None of the `Accept` header values is supported.. + /// + internal static string ErrorHelper_NoSupportedAcceptMediaType { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_VariableStructureInvalid", resourceCulture); + return ResourceManager.GetString("ErrorHelper_NoSupportedAcceptMediaType", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_InvalidPath { + /// + /// Looks up a localized string similar to The GraphQL batch request has no elements.. + /// + internal static string ErrorHelper_RequestHasNoElements { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_InvalidPath", resourceCulture); + return ResourceManager.GetString("ErrorHelper_RequestHasNoElements", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_PathMustStartWithVariable { + /// + /// Looks up a localized string similar to The specified types argument is empty.. + /// + internal static string ErrorHelper_TypeNameIsEmpty { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_PathMustStartWithVariable", resourceCulture); + return ResourceManager.GetString("ErrorHelper_TypeNameIsEmpty", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_InvalidMapJson { + /// + /// Looks up a localized string similar to The type `{0}` does not exist.. + /// + internal static string ErrorHelper_TypeNotFound { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_InvalidMapJson", resourceCulture); + return ResourceManager.GetString("ErrorHelper_TypeNotFound", resourceCulture); } } - internal static string ThrowHelper_HttpMultipartMiddleware_MapNotSpecified { + /// + /// Looks up a localized string similar to The variables are expected to mutable at this point.. + /// + internal static string HttpMultipartMiddleware_InsertFilesIntoRequest_VariablesImmutable { get { - return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_MapNotSpecified", resourceCulture); + return ResourceManager.GetString("HttpMultipartMiddleware_InsertFilesIntoRequest_VariablesImmutable", resourceCulture); } } - internal static string ErrorHelper_InvalidRequest { + /// + /// Looks up a localized string similar to The sessionId mustn't be null or empty.. + /// + internal static string OperationManager_Register_SessionIdNullOrEmpty { get { - return ResourceManager.GetString("ErrorHelper_InvalidRequest", resourceCulture); + return ResourceManager.GetString("OperationManager_Register_SessionIdNullOrEmpty", resourceCulture); } } - internal static string ErrorHelper_RequestHasNoElements { + /// + /// Looks up a localized string similar to The message type cannot be null or empty.. + /// + internal static string OperationMessage_TypeCannotBeNullOrEmpty { get { - return ResourceManager.GetString("ErrorHelper_RequestHasNoElements", resourceCulture); + return ResourceManager.GetString("OperationMessage_TypeCannotBeNullOrEmpty", resourceCulture); } } - internal static string WebSocketSession_SessionEnded { + /// + /// Looks up a localized string similar to Connection terminated by user.. + /// + internal static string TerminateConnectionMessageHandler_Message { get { - return ResourceManager.GetString("WebSocketSession_SessionEnded", resourceCulture); + return ResourceManager.GetString("TerminateConnectionMessageHandler_Message", resourceCulture); } } - internal static string DataStartMessageHandler_Not_A_SubscriptionResult { + /// + /// Looks up a localized string similar to The response type is not supported.. + /// + internal static string ThrowHelper_DataStartMessageHandler_RequestTypeNotSupported { get { - return ResourceManager.GetString("DataStartMessageHandler_Not_A_SubscriptionResult", resourceCulture); + return ResourceManager.GetString("ThrowHelper_DataStartMessageHandler_RequestTypeNotSupported", resourceCulture); } } - internal static string OperationMessage_TypeCannotBeNullOrEmpty { + /// + /// Looks up a localized string similar to Max GraphQL request size reached.. + /// + internal static string ThrowHelper_DefaultHttpRequestParser_MaxRequestSizeExceeded { get { - return ResourceManager.GetString("OperationMessage_TypeCannotBeNullOrEmpty", resourceCulture); + return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_MaxRequestSizeExceeded", resourceCulture); } } - internal static string TerminateConnectionMessageHandler_Message { + /// + /// Looks up a localized string similar to Either the parameter query or the parameter id has to be set.. + /// + internal static string ThrowHelper_DefaultHttpRequestParser_QueryAndIdMissing { get { - return ResourceManager.GetString("TerminateConnectionMessageHandler_Message", resourceCulture); + return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_QueryAndIdMissing", resourceCulture); } } - internal static string OperationManager_Register_SessionIdNullOrEmpty { + /// + /// Looks up a localized string similar to The GraphQL request is empty.. + /// + internal static string ThrowHelper_DefaultHttpRequestParser_RequestIsEmpty { get { - return ResourceManager.GetString("OperationManager_Register_SessionIdNullOrEmpty", resourceCulture); + return ResourceManager.GetString("ThrowHelper_DefaultHttpRequestParser_RequestIsEmpty", resourceCulture); } } - internal static string Apollo_OnReceive_MessageMustBeJson { + /// + /// Looks up a localized string similar to Invalid accept media types specified.. + /// + internal static string ThrowHelper_Formatter_InvalidAcceptMediaType { get { - return ResourceManager.GetString("Apollo_OnReceive_MessageMustBeJson", resourceCulture); + return ResourceManager.GetString("ThrowHelper_Formatter_InvalidAcceptMediaType", resourceCulture); } } - internal static string Apollo_OnReceive_TypePropMissing { + /// + /// Looks up a localized string similar to The specified response content-type `{0}` is not supported.. + /// + internal static string ThrowHelper_Formatter_ResponseContentTypeNotSupported { get { - return ResourceManager.GetString("Apollo_OnReceive_TypePropMissing", resourceCulture); + return ResourceManager.GetString("ThrowHelper_Formatter_ResponseContentTypeNotSupported", resourceCulture); } } - internal static string Apollo_OnReceive_ToManyInitializations { + /// + /// Looks up a localized string similar to The execution result kind is not supported.. + /// + internal static string ThrowHelper_Formatter_ResultKindNotSupported { get { - return ResourceManager.GetString("Apollo_OnReceive_ToManyInitializations", resourceCulture); + return ResourceManager.GetString("ThrowHelper_Formatter_ResultKindNotSupported", resourceCulture); } } - internal static string Apollo_OnReceive_InvalidSubscribeMessage { + /// + /// Looks up a localized string similar to Misordered multipart fields; 'map' should follow 'operations'.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_Fields_Misordered { get { - return ResourceManager.GetString("Apollo_OnReceive_InvalidSubscribeMessage", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_Fields_Misordered", resourceCulture); } } - internal static string Apollo_OnReceive_SubscriptionIdNotUnique { + /// + /// Looks up a localized string similar to File of key '{0}' is missing.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_FileMissing { get { - return ResourceManager.GetString("Apollo_OnReceive_SubscriptionIdNotUnique", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_FileMissing", resourceCulture); } } - internal static string Apollo_OnReceive_InvalidMessageType { + /// + /// Looks up a localized string similar to The multipart form could not be read.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_Invalid_Form { get { - return ResourceManager.GetString("Apollo_OnReceive_InvalidMessageType", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_Invalid_Form", resourceCulture); } } - internal static string ConnectionStatus_Reject_Message_cannot_be_null_or_empty_ { + /// + /// Looks up a localized string similar to Invalid JSON in the `map` multipart field; Expected type of Dictionary<string, string[]>.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_InvalidMapJson { get { - return ResourceManager.GetString("ConnectionStatus_Reject_Message_cannot_be_null_or_empty_", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_InvalidMapJson", resourceCulture); } } - internal static string ConnectionStatus_Reject_MessageCannotBeNullOrEmpty { + /// + /// Looks up a localized string similar to Invalid variable path `{0}` in `map`.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_InvalidPath { get { - return ResourceManager.GetString("ConnectionStatus_Reject_MessageCannotBeNullOrEmpty", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_InvalidPath", resourceCulture); } } - internal static string ErrorHelper_NoSupportedAcceptMediaType { + /// + /// Looks up a localized string similar to No `map` specified.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_MapNotSpecified { get { - return ResourceManager.GetString("ErrorHelper_NoSupportedAcceptMediaType", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_MapNotSpecified", resourceCulture); } } - internal static string ThrowHelper_Formatter_ResultKindNotSupported { + /// + /// Looks up a localized string similar to No 'operations' specified.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_No_Operations_Specified { get { - return ResourceManager.GetString("ThrowHelper_Formatter_ResultKindNotSupported", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_No_Operations_Specified", resourceCulture); } } - internal static string ThrowHelper_Formatter_ResponseContentTypeNotSupported { + /// + /// Looks up a localized string similar to No object paths specified for key '{0}' in 'map'.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_NoObjectPath { get { - return ResourceManager.GetString("ThrowHelper_Formatter_ResponseContentTypeNotSupported", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_NoObjectPath", resourceCulture); } } - internal static string ThrowHelper_Formatter_InvalidAcceptMediaType { + /// + /// Looks up a localized string similar to The variable path must start with `variables`.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_PathMustStartWithVariable { get { - return ResourceManager.GetString("ThrowHelper_Formatter_InvalidAcceptMediaType", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_PathMustStartWithVariable", resourceCulture); } } - internal static string ErrorHelper_InvalidAcceptMediaType { + /// + /// Looks up a localized string similar to The variable path '{0}' is invalid.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_VariableNotFound { get { - return ResourceManager.GetString("ErrorHelper_InvalidAcceptMediaType", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_VariableNotFound", resourceCulture); } } - internal static string ErrorHelper_TypeNotFound { + /// + /// Looks up a localized string similar to The variable structure is invalid.. + /// + internal static string ThrowHelper_HttpMultipartMiddleware_VariableStructureInvalid { get { - return ResourceManager.GetString("ErrorHelper_TypeNotFound", resourceCulture); + return ResourceManager.GetString("ThrowHelper_HttpMultipartMiddleware_VariableStructureInvalid", resourceCulture); } } - internal static string ErrorHelper_InvalidTypeName { + /// + /// Looks up a localized string similar to The first path segment must be a key.. + /// + internal static string VariablePath_Parse_FirstSegmentMustBeKey { get { - return ResourceManager.GetString("ErrorHelper_InvalidTypeName", resourceCulture); + return ResourceManager.GetString("VariablePath_Parse_FirstSegmentMustBeKey", resourceCulture); } } - internal static string ErrorHelper_TypeNameIsEmpty { + /// + /// Looks up a localized string similar to Session ended.. + /// + internal static string WebSocketSession_SessionEnded { get { - return ResourceManager.GetString("ErrorHelper_TypeNameIsEmpty", resourceCulture); + return ResourceManager.GetString("WebSocketSession_SessionEnded", resourceCulture); } } } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.resx b/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.resx index 2dd303237b8..bce235bb668 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.resx +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Properties/AspNetCoreResources.resx @@ -115,7 +115,7 @@ Message cannot be null or empty. - None of the proved accept header media types is supported. + None of the `Accept` header values is supported. The execution result kind is not supported. diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs index 780faf93b48..6bdcab283af 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Serialization/DefaultHttpResponseFormatter.cs @@ -379,9 +379,8 @@ protected virtual void OnWriteResponseHeaders( { var length = acceptMediaTypes.Length; - // if the request does not specify the accept header then we will - // use the `application/graphql-response+json` response content-type, - // which is the new response content-type. + // There is no Accept header present, so the server is allowed + // to select what makes the most sense for the response. if (length == 0) { if (result.Kind is SingleResult) @@ -414,14 +413,14 @@ protected virtual void OnWriteResponseHeaders( ref var start = ref MemoryMarshal.GetArrayDataReference(acceptMediaTypes); - // if we just have one accept header we will try to determine which formatter to take. - // we should only be unable to find a match if there was a previous validation skipped. + // If we just have one Accept header value we will try to determine which formatter to take. + // We should only be unable to find a match if there was a previous validation skipped. if (length == 1) { var mediaType = start; if (resultKind is ResultKind.Single && - mediaType.Kind is ApplicationGraphQL or AllApplication or All) + mediaType.Kind is ApplicationGraphQL) { return _graphqlResponseFormat; } @@ -432,6 +431,12 @@ protected virtual void OnWriteResponseHeaders( return _legacyFormat; } + if (resultKind is ResultKind.Single && + mediaType.Kind is AllApplication or All) + { + return _defaultFormat; + } + if (resultKind is ResultKind.Stream or ResultKind.Single && mediaType.Kind is MultiPartMixed or AllMultiPart or All) { @@ -446,7 +451,7 @@ protected virtual void OnWriteResponseHeaders( return null; } - // if we have more than one specified accept media-type we will try to find the best for + // If we have more than one specified Accept header value we will try to find the best for // our GraphQL result. ref var end = ref Unsafe.Add(ref start, length); FormatInfo? possibleFormat = null; @@ -454,9 +459,9 @@ protected virtual void OnWriteResponseHeaders( while (Unsafe.IsAddressLessThan(ref start, ref end)) { if (resultKind is ResultKind.Single && - start.Kind is ApplicationGraphQL or AllApplication or All) + start.Kind is AllApplication or All) { - return _graphqlResponseFormat; + possibleFormat = _defaultFormat; } if (resultKind is ResultKind.Single && @@ -468,6 +473,12 @@ protected virtual void OnWriteResponseHeaders( possibleFormat = _legacyFormat; } + if (resultKind is ResultKind.Single && + start.Kind is ApplicationGraphQL) + { + return _graphqlResponseFormat; + } + if (resultKind is ResultKind.Stream or ResultKind.Single && start.Kind is MultiPartMixed or AllMultiPart or All) { diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/GraphQLOverHttpSpecTests.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/GraphQLOverHttpSpecTests.cs index f7200d7655b..ae759c8a671 100644 --- a/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/GraphQLOverHttpSpecTests.cs +++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Tests/GraphQLOverHttpSpecTests.cs @@ -1,10 +1,13 @@ -#if NET6_0_OR_GREATER +using System.Net; using System.Net.Http.Json; using CookieCrumble; using HotChocolate.AspNetCore.Serialization; using HotChocolate.AspNetCore.Tests.Utilities; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; using static System.Net.Http.HttpCompletionOption; +using static System.Net.HttpStatusCode; +using static HotChocolate.AspNetCore.HttpTransportVersion; namespace HotChocolate.AspNetCore; @@ -15,58 +18,22 @@ public class GraphQLOverHttpSpecTests : ServerTestBase public GraphQLOverHttpSpecTests(TestServerFactory serverFactory) : base(serverFactory) { } - /// - /// This request does not specify a accept header. - /// expected response content-type: graphql-response+json - /// expected status code: 200 - /// - [Fact] - public async Task Legacy_Query_No_Streams_1() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typename }" }) - }; - - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 - --------------------------> - Status Code: OK - --------------------------> - {""data"":{""__typename"":""Query""}}"); - } - - /// - /// This request does not specify a accept header and spec is set to legacy. - /// expected response content-type: graphql-response+json - /// expected status code: 200 - /// - [Fact] - public async Task Legacy_Query_No_Streams_Legacy_Response() + [Theory] + [InlineData(null, Latest, ContentType.GraphQLResponse)] + [InlineData(null, Legacy, ContentType.Json)] + [InlineData("*/*", Latest, ContentType.GraphQLResponse)] + [InlineData("*/*", Legacy, ContentType.Json)] + [InlineData("application/*", Latest, ContentType.GraphQLResponse)] + [InlineData("application/*", Legacy, ContentType.Json)] + [InlineData(ContentType.Json, Latest, ContentType.Json)] + [InlineData(ContentType.Json, Legacy, ContentType.Json)] + [InlineData(ContentType.GraphQLResponse, Latest, ContentType.GraphQLResponse)] + [InlineData(ContentType.GraphQLResponse, Legacy, ContentType.GraphQLResponse)] + public async Task SingleResult_Success(string? acceptHeader, HttpTransportVersion transportVersion, + string expectedContentType) { // arrange - var server = CreateStarWarsServer( - configureServices: s => s.AddHttpResponseFormatter( - new HttpResponseFormatterOptions - { - HttpTransportVersion = HttpTransportVersion.Legacy - })); - var client = server.CreateClient(); + var client = GetClient(transportVersion); // act using var request = new HttpRequestMessage(HttpMethod.Post, _url) @@ -74,279 +41,26 @@ public async Task Legacy_Query_No_Streams_Legacy_Response() Content = JsonContent.Create( new ClientQueryRequest { Query = "{ __typename }" }) }; + AddAcceptHeader(request, acceptHeader); using var response = await client.SendAsync(request); // assert - // expected response content-type: application/json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/json; charset=utf-8 - --------------------------> - Status Code: OK - --------------------------> - {""data"":{""__typename"":""Query""}}"); - } - - /// - /// This request does not specify a accept header. - /// expected response content-type: application/json - /// expected status code: 200 - /// - [Fact] - public async Task Query_No_Body() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = new ByteArrayContent(Array.Empty()) - { - Headers = { ContentType = new("application/json") { CharSet = "utf-8" } } - } - }; - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 - --------------------------> - Status Code: BadRequest - --------------------------> - {""errors"":[{""message"":""The GraphQL request is empty."",""extensions"":{""code"":""HC0012""}}]}"); - } - - /// - /// This request does not specify a accept header and has a syntax error. - /// expected response content-type: application/json - /// expected status code: 200 - /// - [Fact] - public async Task Legacy_Query_No_Streams_2() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typ$ename }" }) - }; - - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 - --------------------------> - Status Code: BadRequest - --------------------------> - {""errors"":[{""message"":""Expected a \u0060Name\u0060-token, but found a " + - @"\u0060Dollar\u0060-token."",""locations"":[{""line"":1,""column"":8}]," + - @"""extensions"":{""code"":""HC0011""}}]}"); - } - - /// - /// This request does not specify a accept header. - /// expected response content-type: application/json - /// expected status code: 200 - /// - [Fact] - public async Task Legacy_Query_No_Streams_3() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __type name }" }) - }; - - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 - --------------------------> - Status Code: BadRequest - --------------------------> - {""errors"":[{""message"":""\u0060__type\u0060 is an object, interface or " + - "union type field. Leaf selections on objects, interfaces, and unions without " + - @"subfields are disallowed."",""locations"":[{""line"":1,""column"":3}]," + - @"""extensions"":{""declaringType"":""Query"",""field"":""__type""," + - @"""type"":""__Type"",""responseName"":""__type""," + - @"""specifiedBy"":""http://spec.graphql.org/October2021/#sec-Field-Selections-" + - @"on-Objects-Interfaces-and-Unions-Types""}},{""message"":""The field \u0060name" + - @"\u0060 does not exist on the type \u0060Query\u0060."",""locations"":[{" + - @"""line"":1,""column"":10}],""extensions"":{""type"":""Query""," + - @"""field"":""name"",""responseName"":""name"",""specifiedBy"":" + - @"""http://spec.graphql.org/October2021/#sec-Field-Selections-on-Objects-" + - @"Interfaces-and-Unions-Types""}},{""message"":""The argument \u0060name\u0060 " + - @"is required."",""locations"":[{""line"":1,""column"":3}],""extensions"":{" + - @"""type"":""Query"",""field"":""__type"",""argument"":""name""," + - @"""specifiedBy"":""http://spec.graphql.org/October2021/#sec-Required-Arguments""" + - "}}]}"); - } - - /// - /// This request does not specify a accept header. - /// expected response content-type: multipart/mixed - /// expected status code: 200 - /// - [Fact] - public async Task Legacy_With_Stream_1() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }) - }; - - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: multipart/mixed - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Cache-Control: no-cache - Content-Type: multipart/mixed; boundary=""-"" - --------------------------> - Status Code: OK - --------------------------> - - --- - Content-Type: application/json; charset=utf-8 - - {""data"":{},""hasNext"":true} - --- - Content-Type: application/json; charset=utf-8 - - {""incremental"":[{""data"":{""__typename"":""Query""}," + - @"""path"":[]}],""hasNext"":false} - ----- - "); - } - - /// - /// This request specifies the application/graphql-response+json accept header. - /// expected response content-type: application/graphql-response+json - /// expected status code: 200 - /// - [Fact] - public async Task New_Query_No_Streams_1() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typename }" }), - Headers = { { "Accept", ContentType.GraphQLResponse } } - }; - - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/graphql-response+json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 - --------------------------> - Status Code: OK - --------------------------> - {""data"":{""__typename"":""Query""}}"); - } - - /// - /// This request specifies the application/json accept header. - /// expected response content-type: application/json - /// expected status code: 200 - /// - [Fact] - public async Task New_Query_No_Streams_2() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typename }" }), - Headers = { { "Accept", ContentType.Json } } - }; - - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/json - // expected status code: 200 Snapshot .Create() .Add(response) .MatchInline( - @"Headers: - Content-Type: application/json; charset=utf-8 + @$"Headers: + Content-Type: {expectedContentType} --------------------------> Status Code: OK --------------------------> - {""data"":{""__typename"":""Query""}}"); + " + + @"{""data"":{""__typename"":""Query""}}"); } - /// - /// This request specifies the multipart/mixed accept header. - /// expected response content-type: multipart/mixed - /// expected status code: 200 - /// [Fact] - public async Task New_Query_No_Streams_3() + public async Task SingleResult_MultipartMixedAcceptHeader() { // arrange var server = CreateStarWarsServer(); @@ -366,8 +80,6 @@ public async Task New_Query_No_Streams_3() using var response = await client.SendAsync(request); // assert - // expected response content-type: multipart/mixed - // expected status code: 200 Snapshot .Create() .Add(response) @@ -386,14 +98,13 @@ public async Task New_Query_No_Streams_3() "); } - /// - /// This request specifies the application/graphql-response+json and - /// the multipart/mixed content type as accept header value. - /// expected response content-type: application/graphql-response+json - /// expected status code: 200 - /// - [Fact] - public async Task New_Query_No_Streams_4() + [Theory] + [InlineData("application/graphql-response+json; charset=utf-8, multipart/mixed; charset=utf-8")] + [InlineData("application/graphql-response+json, multipart/mixed")] + [InlineData("multipart/mixed,application/graphql-response+json")] + [InlineData("text/event-stream, multipart/mixed,application/json, application/graphql-response+json")] + [InlineData("application/graphql-response+json; charset=utf-8, application/json; charset=utf-8")] + public async Task SingleResult_ApplicationGraphQLIsPreferred(string acceptHeader) { // arrange var server = CreateStarWarsServer(); @@ -403,62 +114,14 @@ public async Task New_Query_No_Streams_4() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typename }" }), - Headers = - { - { - "Accept", - new[] - { - ContentType.GraphQLResponse, - $"{ContentType.Types.MultiPart}/{ContentType.SubTypes.Mixed}" - } - } - } + new ClientQueryRequest { Query = "{ __typename }" }) }; - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/graphql-response+json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 - --------------------------> - Status Code: OK - --------------------------> - {""data"":{""__typename"":""Query""}}"); - } - - /// - /// This request specifies the */* accept header. - /// expected response content-type: application/json; charset=utf-8 - /// expected status code: 200 - /// - [Fact] - public async Task New_Query_No_Streams_5() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typename }" }), - Headers = { { "Accept", "*/*" } } - }; + request.Headers.TryAddWithoutValidation("Accept", acceptHeader); using var response = await client.SendAsync(request); // assert - // expected response content-type: application/json; charset=utf-8 - // expected status code: 200 Snapshot .Create() .Add(response) @@ -471,120 +134,132 @@ public async Task New_Query_No_Streams_5() {""data"":{""__typename"":""Query""}}"); } - /// - /// This request specifies the application/* accept header. - /// expected response content-type: application/graphql-response+json; charset=utf-8 - /// expected status code: 200 - /// - [Fact] - public async Task New_Query_No_Streams_6() + [Theory] + [InlineData(null, Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData(null, Legacy, OK, ContentType.Json)] + [InlineData("*/*", Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData("*/*", Legacy, OK, ContentType.Json)] + [InlineData("application/*", Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData("application/*", Legacy, OK, ContentType.Json)] + [InlineData(ContentType.Json, Latest, OK, ContentType.Json)] + [InlineData(ContentType.Json, Legacy, OK, ContentType.Json)] + [InlineData(ContentType.GraphQLResponse, Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData(ContentType.GraphQLResponse, Legacy, BadRequest, ContentType.GraphQLResponse)] + public async Task Query_No_Body(string? acceptHeader, HttpTransportVersion transportVersion, + HttpStatusCode expectedStatusCode, string expectedContentType) { // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); + var client = GetClient(transportVersion); // act using var request = new HttpRequestMessage(HttpMethod.Post, _url) { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typename }" }), - Headers = { { "Accept", "application/*" } } + Content = new ByteArrayContent(Array.Empty()) + { + Headers = { ContentType = new("application/json") { CharSet = "utf-8" } } + } }; + AddAcceptHeader(request, acceptHeader); using var response = await client.SendAsync(request); // assert - // expected response content-type: application/graphql-response+json; charset=utf-8 - // expected status code: 200 Snapshot .Create() .Add(response) .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 + @$"Headers: + Content-Type: {expectedContentType} --------------------------> - Status Code: OK + Status Code: {expectedStatusCode} --------------------------> - {""data"":{""__typename"":""Query""}}"); + " + + @"{""errors"":[{""message"":""The GraphQL request is empty."",""extensions"":{""code"":""HC0012""}}]}"); } - /// - /// This request does not specify a application/graphql-response+json accept header and - /// has a syntax error. - /// expected response content-type: application/graphql-response+json - /// expected status code: 400 - /// - [Fact] - public async Task New_Query_No_Streams_7() + [Theory] + [InlineData(null, Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData(null, Legacy, OK, ContentType.Json)] + [InlineData("*/*", Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData("*/*", Legacy, OK, ContentType.Json)] + [InlineData("application/*", Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData("application/*", Legacy, OK, ContentType.Json)] + [InlineData(ContentType.Json, Latest, OK, ContentType.Json)] + [InlineData(ContentType.Json, Legacy, OK, ContentType.Json)] + [InlineData(ContentType.GraphQLResponse, Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData(ContentType.GraphQLResponse, Legacy, BadRequest, ContentType.GraphQLResponse)] + public async Task ValidationError(string? acceptHeader, HttpTransportVersion transportVersion, + HttpStatusCode expectedStatusCode, string expectedContentType) { // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); + var client = GetClient(transportVersion); // act using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typ$ename }" }), - Headers = { { "Accept", ContentType.GraphQLResponse } } + new ClientQueryRequest { Query = "{ __typ$ename }" }) }; + AddAcceptHeader(request, acceptHeader); using var response = await client.SendAsync(request); // assert - // expected response content-type: application/graphql-response+json - // expected status code: 400 Snapshot .Create() .Add(response) .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 + @$"Headers: + Content-Type: {expectedContentType} --------------------------> - Status Code: BadRequest + Status Code: {expectedStatusCode} --------------------------> - {""errors"":[{""message"":""Expected a \u0060Name\u0060-token, but found a " + + " + + @"{""errors"":[{""message"":""Expected a \u0060Name\u0060-token, but found a " + @"\u0060Dollar\u0060-token."",""locations"":[{""line"":1,""column"":8}]," + @"""extensions"":{""code"":""HC0011""}}]}"); } - /// - /// This request does not specify a application/graphql-response+json accept header and - /// has a syntax error. - /// expected response content-type: application/graphql-response+json - /// expected status code: 400 - /// - [Fact] - public async Task New_Query_No_Streams_8() + [Theory] + [InlineData(null, Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData(null, Legacy, OK, ContentType.Json)] + [InlineData("*/*", Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData("*/*", Legacy, OK, ContentType.Json)] + [InlineData("application/*", Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData("application/*", Legacy, OK, ContentType.Json)] + [InlineData(ContentType.Json, Latest, OK, ContentType.Json)] + [InlineData(ContentType.Json, Legacy, OK, ContentType.Json)] + [InlineData(ContentType.GraphQLResponse, Latest, BadRequest, ContentType.GraphQLResponse)] + [InlineData(ContentType.GraphQLResponse, Legacy, BadRequest, ContentType.GraphQLResponse)] + public async Task ValidationError2(string? acceptHeader, HttpTransportVersion transportVersion, + HttpStatusCode expectedStatusCode, string expectedContentType) { // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); + var client = GetClient(transportVersion); // act using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __type name }" }), - Headers = { { "Accept", ContentType.GraphQLResponse } } + new ClientQueryRequest { Query = "{ __type name }" }) }; + AddAcceptHeader(request, acceptHeader); using var response = await client.SendAsync(request); // assert - // expected response content-type: application/graphql-response+json - // expected status code: 400 Snapshot .Create() .Add(response) .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 + @$"Headers: + Content-Type: {expectedContentType} --------------------------> - Status Code: BadRequest + Status Code: {expectedStatusCode} --------------------------> - {""errors"":[{""message"":""\u0060__type\u0060 is an object, interface or " + - "union type field. Leaf selections on objects, interfaces, and unions without " + + " + + @"{""errors"":[{""message"":""\u0060__type\u0060 is an object, interface or " + + @"union type field. Leaf selections on objects, interfaces, and unions without " + @"subfields are disallowed."",""locations"":[{""line"":1,""column"":3}]," + @"""extensions"":{""declaringType"":""Query"",""field"":""__type""," + @"""type"":""__Type"",""responseName"":""__type""," + @@ -601,62 +276,8 @@ public async Task New_Query_No_Streams_8() "}}]}"); } - /// - /// This request specifies the text/event-stream, multipart/mixed, application/json and - /// application/graphql-response+json content types as accept header value. - /// expected response content-type: application/graphql-response+json - /// expected status code: 200 - /// [Fact] - public async Task New_Query_No_Streams_9() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typename }" }), - Headers = - { - { - "Accept", - new[] - { - ContentType.EventStream, - $"{ContentType.Types.MultiPart}/{ContentType.SubTypes.Mixed}", - ContentType.Json, ContentType.GraphQLResponse, - } - } - } - }; - - using var response = await client.SendAsync(request); - - // assert - // expected response content-type: application/graphql-response+json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 - --------------------------> - Status Code: OK - --------------------------> - {""data"":{""__typename"":""Query""}}"); - } - - /// - /// This request specifies the application/unsupported content types as accept header value. - /// expected response content-type: application/graphql-response+json - /// expected status code: 400 - /// - [Fact] - public async Task New_Query_No_Streams_10() + public async Task UnsupportedAcceptHeaderValue() { // arrange var server = CreateStarWarsServer(); @@ -674,8 +295,6 @@ public async Task New_Query_No_Streams_10() using var response = await client.SendAsync(request); // assert - // expected response content-type: application/graphql-response+json - // expected status code: 400 Snapshot .Create() .Add(response) @@ -690,13 +309,8 @@ public async Task New_Query_No_Streams_10() @"""code"":""HC0064""}}]}"); } - /// - /// This request specifies the application/unsupported content types as accept header value. - /// expected response content-type: application/graphql-response+json - /// expected status code: 206 - /// [Fact] - public async Task New_Query_No_Streams_12() + public async Task UnsupportedApplicationAcceptHeaderValue() { // arrange var server = CreateStarWarsServer(); @@ -714,8 +328,6 @@ public async Task New_Query_No_Streams_12() using var response = await client.SendAsync(request); // assert - // expected response content-type: application/graphql-response+json - // expected status code: 206 Snapshot .Create() .Add(response) @@ -725,18 +337,18 @@ public async Task New_Query_No_Streams_12() --------------------------> Status Code: NotAcceptable --------------------------> - {""errors"":[{""message"":""None of the proved accept header media types " + - @"is supported."",""extensions"":{""code"":""HC0063""}}]}"); + {""errors"":[{""message"":""None of the \u0060Accept\u0060 header values is supported.""," + + @"""extensions"":{""code"":""HC0063""}}]}"); } - /// - /// This request specifies the application/graphql-response+json; charset=utf-8, - /// multipart/mixed; charset=utf-8 content types as accept header value. - /// expected response content-type: application/graphql-response+json - /// expected status code: 200 - /// - [Fact] - public async Task New_Query_No_Streams_13() + [Theory] + [InlineData(null)] + [InlineData("*/*")] + [InlineData("multipart/mixed")] + [InlineData("multipart/*")] + [InlineData("application/graphql-response+json, multipart/mixed")] + [InlineData("text/event-stream, multipart/mixed")] + public async Task DeferredQuery_Multipart(string? acceptHeader) { // arrange var server = CreateStarWarsServer(); @@ -746,66 +358,13 @@ public async Task New_Query_No_Streams_13() using var request = new HttpRequestMessage(HttpMethod.Post, _url) { Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ __typename }" }) + new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }) }; - - request.Headers.TryAddWithoutValidation( - "Accept", - "application/graphql-response+json; charset=utf-8, multipart/mixed; charset=utf-8"); + AddAcceptHeader(request, acceptHeader); using var response = await client.SendAsync(request); // assert - // expected response content-type: application/graphql-response+json - // expected status code: 200 - Snapshot - .Create() - .Add(response) - .MatchInline( - @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 - --------------------------> - Status Code: OK - --------------------------> - {""data"":{""__typename"":""Query""}}"); - } - - /// - /// This request specifies the application/graphql-response+json and - /// the multipart/mixed content type as accept header value. - /// expected response content-type: multipart/mixed - /// expected status code: 200 - /// - [Fact] - public async Task New_Query_With_Streams_1() - { - // arrange - var server = CreateStarWarsServer(); - var client = server.CreateClient(); - - // act - using var request = new HttpRequestMessage(HttpMethod.Post, _url) - { - Content = JsonContent.Create( - new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }), - Headers = - { - { - "Accept", - new[] - { - ContentType.GraphQLResponse, - $"{ContentType.Types.MultiPart}/{ContentType.SubTypes.Mixed}" - } - } - } - }; - - using var response = await client.SendAsync(request, ResponseHeadersRead); - - // assert - // expected response content-type: multipart/mixed - // expected status code: 200 Snapshot .Create() .Add(response) @@ -830,14 +389,10 @@ public async Task New_Query_With_Streams_1() "); } - /// - /// This request specifies the application/graphql-response+json - /// content type as accept header value. - /// expected response content-type: application/graphql-response+json - /// expected status code: 405 - /// - [Fact] - public async Task New_Query_With_Streams_2() + [Theory] + [InlineData("text/event-stream")] + [InlineData("application/graphql-response+json, text/event-stream")] + public async Task DeferredQuery_EventStream(string acceptHeader) { // arrange var server = CreateStarWarsServer(); @@ -848,35 +403,36 @@ public async Task New_Query_With_Streams_2() { Content = JsonContent.Create( new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }), - Headers = { { "Accept", new[] { ContentType.GraphQLResponse } } } + Headers = { { "Accept", acceptHeader } } }; using var response = await client.SendAsync(request, ResponseHeadersRead); // assert - // expected response content-type: application/graphql-response+json - // expected status code: 405 - // we are rejecting the request since we have a streamed result and - // the user requests a json payload. Snapshot .Create() .Add(response) .MatchInline( @"Headers: - Content-Type: application/graphql-response+json; charset=utf-8 + Cache-Control: no-cache + Content-Type: text/event-stream; charset=utf-8 --------------------------> - Status Code: MethodNotAllowed + Status Code: OK --------------------------> - {""errors"":[{""message"":""The specified operation kind is not allowed.""}]}"); + event: next + data: {""data"":{},""hasNext"":true} + + event: next + data: {""incremental"":[{""data"":{""__typename"":""Query""}," + + @"""path"":[]}],""hasNext"":false} + + event: complete + + "); } - /// - /// This request specifies the text/event-stream content type as accept header value. - /// expected response content-type: text/event-stream - /// expected status code: 200 - /// [Fact] - public async Task New_Query_With_Streams_3() + public async Task DefferedQuery_NoStreamableAcceptHeader() { // arrange var server = CreateStarWarsServer(); @@ -887,34 +443,43 @@ public async Task New_Query_With_Streams_3() { Content = JsonContent.Create( new ClientQueryRequest { Query = "{ ... @defer { __typename } }" }), - Headers = { { "Accept", new[] { ContentType.EventStream } } } + Headers = { { "Accept", ContentType.GraphQLResponse } } }; using var response = await client.SendAsync(request, ResponseHeadersRead); // assert - // expected response content-type: text/event-stream - // expected status code: 200 + // we are rejecting the request since we have a streamed result and + // the user requests a json payload. Snapshot .Create() .Add(response) .MatchInline( @"Headers: - Cache-Control: no-cache - Content-Type: text/event-stream; charset=utf-8 + Content-Type: application/graphql-response+json; charset=utf-8 --------------------------> - Status Code: OK + Status Code: MethodNotAllowed --------------------------> - event: next - data: {""data"":{},""hasNext"":true} + {""errors"":[{""message"":""The specified operation kind is not allowed.""}]}"); + } - event: next - data: {""incremental"":[{""data"":{""__typename"":""Query""}," + - @"""path"":[]}],""hasNext"":false} + private HttpClient GetClient(HttpTransportVersion serverTransportVersion) + { + var server = CreateStarWarsServer( + configureServices: s => s.AddHttpResponseFormatter( + new HttpResponseFormatterOptions + { + HttpTransportVersion = serverTransportVersion + })); - event: complete + return server.CreateClient(); + } - "); + private void AddAcceptHeader(HttpRequestMessage request, string? acceptHeader) + { + if (acceptHeader != null) + { + request.Headers.Add(HeaderNames.Accept, acceptHeader); + } } } -#endif