public async Task ProductionDefaults_CamelCasedTypeNames() { var builder = new TestServerBuilder() .AddGraphType <FanController>(); builder.AddGraphQL(o => { o.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(typeNameStrategy: GraphNameFormatStrategy.CamelCase); }); var server = builder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText("{ __type(name: \"fanItem\"){ name, kind} }"); var result = await server.RenderResult(queryBuilder).ConfigureAwait(false); var expectedResult = @" { ""data"" : { ""__type"": { ""name"" : ""fanItem"", ""kind"" : ""OBJECT"", } } }"; CommonAssertions.AreEqualJsonStrings(expectedResult, result); }
public async Task ProductionDefaults_UpperCaseFieldNames() { var builder = new TestServerBuilder() .AddGraphType <FanController>(); builder.AddGraphQL(o => { o.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(fieldNameStrategy: GraphNameFormatStrategy.UpperCase); }); var server = builder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText("{ RETRIEVEFAN(NAME: \"bob\"){ ID, NAME, FANSPEED} }"); var result = await server.RenderResult(queryBuilder).ConfigureAwait(false); var expectedResult = @" { ""data"" : { ""RETRIEVEFAN"": { ""ID"" : 1, ""NAME"" : ""bob"", ""FANSPEED"" : ""MEDIUM"" } } }"; CommonAssertions.AreEqualJsonStrings(expectedResult, result); }
public async Task SchemaBuilder_AddMiddleware_AsFunctionRegistration_AppearsInPipeline() { int counter = 0; var serverBuilder = new TestServerBuilder <CandleSchema>(); var builder = serverBuilder.AddGraphQL <CandleSchema>(options => { options.AddGraphType <CandleController>(); }); builder.FieldExecutionPipeline.AddMiddleware((req, next, token) => { counter++; return(next(req, token)); }); var server = serverBuilder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText("{candles{ candle(id: 18){ name }}}"); var result = await server.ExecuteQuery(queryBuilder); Assert.IsTrue(result.Data != null); // resolution of fields "queryType_candles", "candle" and "name" // means the middleware should be called 3x Assert.AreEqual(3, counter); }
public async Task ProductionDefaults_LowerCaseEnumValues() { var builder = new TestServerBuilder() .AddGraphType <FanController>(); builder.AddGraphQL(o => { o.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(enumValueStrategy: GraphNameFormatStrategy.LowerCase); }); var server = builder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText("{ retrieveFan(name: \"bob\"){ id, name, fanSpeed} }"); var result = await server.RenderResult(queryBuilder).ConfigureAwait(false); var expectedResult = @" { ""data"" : { ""retrieveFan"": { ""id"" : 1, ""name"" : ""bob"", ""fanSpeed"" : ""medium"" } } }"; CommonAssertions.AreEqualJsonStrings(expectedResult, result); }
public async Task ExceptionOnMessage_RendersAsExpected() { var fixedDate = DateTimeOffset.UtcNow.UtcDateTime; var builder = new TestServerBuilder() .AddGraphType <SimpleExecutionController>(); builder.AddGraphQL(o => { o.ResponseOptions.TimeStampLocalizer = (_) => fixedDate; }); var server = builder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText("query Operation1{ simple { throwsException } }"); var response = await server.ExecuteQuery(queryBuilder); var writer = new DefaultResponseWriter <GraphSchema>(server.Schema); var result = await this.WriteResponse(writer, response); var exceptionStackTrace = JsonEncodedText.Encode(response.Messages[0].Exception.StackTrace, JavaScriptEncoder.UnsafeRelaxedJsonEscaping).ToString(); var expectedData = @" { ""errors"": [ { ""message"": ""Operation failed."", ""locations"": [ { ""line"": 1, ""column"": 31 } ], ""path"": [ ""simple"", ""throwsException"" ], ""extensions"": { ""code"": ""UNHANDLED_EXCEPTION"", ""timestamp"": """ + fixedDate.ToRfc3339String() + @""", ""severity"": ""CRITICAL"", ""exception"": { ""type"": ""InvalidOperationException"", ""message"": ""This is an invalid message"", ""stacktrace"": """ + exceptionStackTrace + @""", } } } ] }"; CommonAssertions.AreEqualJsonStrings( expectedData, result); }
private TestServer <GraphSchema> MakeServer(int?allowedMaxDepth = null, float?allowedMaxComplexity = null) { var builder = new TestServerBuilder() .AddGraphType <ComplexityTestObject>() .AddGraphType <NestedComplexityTestObject>() .AddGraphType <TripleNestedComplexityObject>() .AddGraphType <ComplexityTestController>(); builder.AddGraphQL(o => { o.ExecutionOptions.MaxQueryDepth = allowedMaxDepth; o.ExecutionOptions.MaxQueryComplexity = allowedMaxComplexity; }); return(builder.Build()); }
public async Task MetaDataOnMessage_RendersAsExpected() { var fixedDate = DateTimeOffset.UtcNow.UtcDateTime; var builder = new TestServerBuilder() .AddGraphType <SimpleExecutionController>(); builder.AddGraphQL(o => { o.ResponseOptions.TimeStampLocalizer = (_) => fixedDate; }); var server = builder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText("query Operation1{ simple { customMessage } }"); var response = await server.ExecuteQuery(queryBuilder); var writer = new DefaultResponseWriter <GraphSchema>(server.Schema); var result = await this.WriteResponse(writer, response); var expectedData = @" { ""errors"": [ { ""message"": ""fail text"", ""extensions"": { ""code"": ""fail code"", ""timestamp"": """ + fixedDate.ToRfc3339String() + @""", ""severity"": ""CRITICAL"", ""metaData"" : { ""customKey1"": ""customValue1"" } } } ] }"; // no errors collection generated because no errors occured. CommonAssertions.AreEqualJsonStrings( expectedData, result); }
public async Task DefaultWriteOperation_InvalidSyntax_RendersJsonWithError() { var fixedDate = DateTimeOffset.UtcNow.UtcDateTime; var serverBuilder = new TestServerBuilder() .AddGraphType <SimpleExecutionController>(); serverBuilder.AddGraphQL(o => { o.ResponseOptions.TimeStampLocalizer = (_) => fixedDate; }); var server = serverBuilder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText("query Operation1{ simple \n {{ simpleQueryMethod { property1} } }"); var response = await server.ExecuteQuery(queryBuilder); var writer = new DefaultResponseWriter <GraphSchema>(server.Schema); var result = await this.WriteResponse(writer, response); var expectedData = @" { ""errors"": [ { ""message"": ""Invalid query. Expected 'NameToken' but received 'ControlToken'"", ""locations"": [ { ""line"": 2, ""column"": 3 }], ""extensions"": { ""code"": ""SYNTAX_ERROR"", ""timestamp"": """ + fixedDate.ToRfc3339String() + @""", ""severity"": ""CRITICAL"" } }] }"; // no errors collection generated because no errors occured. CommonAssertions.AreEqualJsonStrings( expectedData, result); }
private GraphTypeCreationResult MakeGraphType(Type type, TypeKind kind, TemplateDeclarationRequirements?requirements = null) { var builder = new TestServerBuilder(TestOptions.UseCodeDeclaredNames); if (requirements.HasValue) { builder.AddGraphQL(o => { o.DeclarationOptions.FieldDeclarationRequirements = requirements.Value; }); } var typeMaker = new DefaultGraphTypeMakerProvider(); var testServer = builder.Build(); var maker = typeMaker.CreateTypeMaker(testServer.Schema, kind); return(maker.CreateGraphType(type)); }
public async Task WithAttachedQueryCache_RendersPlanToCache() { var keyManager = new DefaultQueryPlanCacheKeyManager(new GraphQLParser()); var cacheInstance = new MemoryCache(nameof(WithAttachedQueryCache_RendersPlanToCache)); var cache = new DefaultQueryPlanCacheProvider(cacheInstance); var builder = new TestServerBuilder() .AddGraphType <SimpleExecutionController>(); builder.AddSingleton <IGraphQueryPlanCacheProvider>(cache); builder.AddSingleton <IGraphQueryPlanCacheKeyManager>(keyManager); // configure an absolute expriation of a few seconds to ensure the plan remains in cache // long enough to be fetched by tis expected key builder.AddGraphQL(o => { o.CacheOptions.TimeToLiveInMilliseconds = 10000; }); var server = builder.Build(); var queryText = "query { simple{ simpleQueryMethod { property1} } }"; var expectedCacheKey = keyManager.CreateKey <GraphSchema>(queryText); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText(queryText); var result = await server.ExecuteQuery(queryBuilder); Assert.IsNotNull(result); Assert.AreEqual(1, cache.Count); // ensure we can pull the plan directly from the cache instance var cachedPlan = cacheInstance.GetCacheItem(expectedCacheKey); Assert.IsNotNull(cachedPlan); // attempt eviction through the provider await cache.EvictAsync(expectedCacheKey); // ensure underlying cache instance was evicted cachedPlan = cacheInstance.GetCacheItem(expectedCacheKey); Assert.IsNull(cachedPlan); }
public void Parse_EnumWithUndeclaredValues_WhenConfigRequiresDeclaration_DoesntIncludeUndeclared_InGraphType() { var template = TemplateHelper.CreateEnumTemplate <EnumWithUndeclaredValues>(); var builder = new TestServerBuilder(); builder.AddGraphQL(o => { o.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.EnumValue; }); var server = builder.Build(); var graphType = server.CreateGraphType(template.ObjectType, TypeKind.ENUM).GraphType as IEnumGraphType; Assert.AreEqual(2, graphType.Values.Count); Assert.IsTrue(graphType.Values.ContainsKey("DECLAREDVALUE1")); Assert.IsTrue(graphType.Values.ContainsKey("VALUE_AWESOME")); }
public void CreateGraphType_ParsesAsExpected() { var builder = new TestServerBuilder(); builder.AddGraphQL(o => { o.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(enumValueStrategy: GraphNameFormatStrategy.NoChanges); }); var server = builder.Build(); var template = TemplateHelper.CreateEnumTemplate <EnumWithDescriptionOnValues>(); var type = server.CreateGraphType(typeof(EnumWithDescriptionOnValues), TypeKind.ENUM).GraphType as IEnumGraphType; Assert.IsNotNull(type); Assert.AreEqual(template.Name, type.Name); Assert.IsTrue(type is EnumGraphType); Assert.AreEqual(4, ((EnumGraphType)type).Values.Count); }
public void Parse_EnumWithUndeclaredValues_WhenConfigDoesNotRequireDeclaration_DoesIncludeUndeclared_InGraphType() { var builder = new TestServerBuilder() .AddGraphType <EnumWithUndeclaredValues>(); builder.AddGraphQL(o => { o.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.None; }); var server = builder.Build(); var graphType = server.CreateGraphType(typeof(EnumWithUndeclaredValues), TypeKind.ENUM).GraphType as IEnumGraphType; Assert.AreEqual(3, graphType.Values.Count); Assert.IsTrue(graphType.Values.ContainsKey("DECLAREDVALUE1")); Assert.IsTrue(graphType.Values.ContainsKey("VALUE_AWESOME")); Assert.IsFalse(graphType.Values.ContainsKey("DECLAREDVALUE2")); Assert.IsTrue(graphType.Values.ContainsKey("UNDECLAREDVALUE1")); }
public async Task RunnerTimeout_ErrorMessagesAreSetCorrect() { var serverBuilder = new TestServerBuilder() .AddGraphType <SimpleExecutionController>(); serverBuilder.AddGraphQL(o => { o.ExecutionOptions.QueryTimeout = TimeSpan.FromMilliseconds(15); }); var server = serverBuilder.Build(); var builder = server.CreateQueryContextBuilder() .AddQueryText("query { simple { timedOutMethod } }"); var result = await server.ExecuteQuery(builder); Assert.AreEqual(1, result.Messages.Count); Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages.Severity); Assert.AreEqual(Constants.ErrorCodes.OPERATION_CANCELED, result.Messages[0].Code); }
public async Task SchemaBuilder_AddMiddleware_AsTypeRegistration_AppearsInPipeline() { var serverBuilder = new TestServerBuilder <CandleSchema>(); var schemaBuilder = serverBuilder.AddGraphQL <CandleSchema>(options => { options.AddGraphType <CandleController>(); }); schemaBuilder.FieldExecutionPipeline.AddMiddleware <CandleMiddleware>(ServiceLifetime.Singleton, "Candle middleware"); var server = serverBuilder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText("{candles{ candle(id: 18){ name }}}"); var result = await server.ExecuteQuery(queryBuilder); Assert.IsTrue(result.Data != null); // resolution of fields "queryType_candles", "candle" and "name" // means the middleware should be called 3x Assert.AreEqual(3, CandleMiddleware.Counter); }
public async Task EnsureProperStringEscapement(string testValue, string expectedValue) { var fixedDate = DateTimeOffset.UtcNow.UtcDateTime; var builder = new TestServerBuilder() .AddGraphType <SimpleExecutionController>(); builder.AddGraphQL(o => { o.ResponseOptions.TimeStampLocalizer = (_) => fixedDate; }); var server = builder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); var dic = new ResponseFieldSet(); dic.Add("testKey", new ResponseSingleValue(testValue)); var mockResult = new Mock <IGraphOperationResult>(); mockResult.Setup(x => x.Data).Returns(dic); mockResult.Setup(x => x.Messages).Returns(new GraphMessageCollection()); mockResult.Setup(x => x.Request).Returns(queryBuilder.OperationRequest); var writer = new DefaultResponseWriter <GraphSchema>(server.Schema); var result = await this.WriteResponse(writer, mockResult.Object); var expected = @" { ""data"" : { ""testKey"" : """ + expectedValue + @""", } }"; CommonAssertions.AreEqualJsonStrings(expected, result); }
public async Task WhenAnObjectReturnedImplmentsTheInterface_ButIsntInTheSchema_DenyIt() { var serverBuilder = new TestServerBuilder() .AddGraphType <ConcreteObjectA>() .AddGraphType <ConcreteObjectB>() .AddGraphType <InterfaceExtensionController>(); serverBuilder.AddGraphQL(o => { o.ResponseOptions.MessageSeverityLevel = GraphMessageSeverity.Critical; }); var server = serverBuilder.Build(); var builder = server.CreateQueryContextBuilder(); builder.AddQueryText(@" { multiObjectsWithC { firstName fullName } }"); // 'multiObjectsWithC' returns 3 objects of type A, B, C and all three implement the interface // but ONLY A and B are declared on the schema. // Since the schema doesnt know about object C it has no way to deal with the // field resolution and results in an error var result = await server.ExecuteQuery(builder); Assert.AreEqual(1, result.Messages.Count); var message = result.Messages[0]; Assert.AreEqual(Constants.ErrorCodes.EXECUTION_ERROR, message.Code); Assert.IsTrue(message.Exception.Message.Contains(nameof(ConcreteObjectC))); }
public async Task FieldOnConcreteTypes_ButNotOnSharedInterface_WhenInterfaceIsReturned_ButTypeConditionedFragmentsAreUsed_IsAllowed() { var serverBuilder = new TestServerBuilder() .AddGraphType <ConcreteObjectA>() .AddGraphType <ConcreteObjectB>() .AddGraphType <InterfaceExtensionController>(); serverBuilder.AddGraphQL(o => { o.ResponseOptions.MessageSeverityLevel = GraphMessageSeverity.Critical; }); // this query should include un-navigable routes // which may report being unprocessable as an info level warning (its expected) // disregard them var server = serverBuilder.Build(); var builder = server.CreateQueryContextBuilder(); builder.AddQueryText(@" { multiObjects { fullName ... on ConcreteObjectA { middleName } ... on ConcreteObjectB { title } } }"); // 'MiddleName' is declared on ObjectA and and 'Title' ObjectB but neither are declared on the interface // respective objects should still return their data. var result = await server.RenderResult(builder); // output combines declared methods on concrete objects, firstName, and the interface extension, fullName var expectedOutput = @" { ""data"": { ""multiObjects"": [ { ""fullName"": ""0A_prop1 0A_prop2"", ""middleName"" : ""0A_prop3"" }, { ""fullName"": ""1A_prop1 1A_prop2"", ""middleName"" : ""1A_prop3"" }, { ""fullName"": ""0B_prop1 0B_prop2"", ""title"" : ""0B_prop3"" }, { ""fullName"": ""1B_prop1 1B_prop2"", ""title"" : ""1B_prop3"" } ] } }"; CommonAssertions.AreEqualJsonStrings(expectedOutput, result); }
public async Task EndToEndIntegrationTest() { var builder = new TestServerBuilder(); builder.AddScoped <IMusicService, MusicService>(); builder.AddSingleton <IMusicRepository, MusicRepository>(); builder.AddGraphType <MusicController>(); builder.AddGraphQL(o => { o.ExecutionOptions.AwaitEachRequestedField = true; }); var server = builder.Build(); // Test the following areas // ----------------------------------- // queries // input values: scalars // batch child query ("records" is a batch list extension method) // root level controller actions (note no nesting under the declared "music" field of the controller for the "artists" field) var expected = LoadOutputFile("RetrieveArtistsAndRecords.json"); var queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText( @"query { artists(searchText: ""queen"") { id name records { id name genre { id name } } } }"); var result1 = await server.RenderResult(queryBuilder); CommonAssertions.AreEqualJsonStrings(expected, result1, "(1): " + result1); // Test the following areas // ----------------------------------- // virtual routing (the "music" field is a virtual field declared on the controller) // mutations // input values: scalars // batch sybling query ("company" is a batch sybling extension method) expected = LoadOutputFile("CreateNewArtist.json"); queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText( @"mutation { music{ createArtist(artistName: ""EXO"", recordCompanyId: 4) { id name company { id name } } }}"); var result2 = await server.RenderResult(queryBuilder); CommonAssertions.AreEqualJsonStrings(expected, result2, "(2): " + result2); // Test the following areas // ----------------------------------- // virtual routing (the "music" field is a virtual field declared on the controller) // mutations // input values: complex objects // unicode characters expected = LoadOutputFile("CreateRecord.json"); queryBuilder = server.CreateQueryContextBuilder(); queryBuilder.AddQueryText( @"mutation { music{ createRecord(artist: {id: 10, name: ""EXO"", recordCompanyId: 4}, genre: {id:2, name: ""pop""}, songName:""다이아몬드"") { id artistId genre { id name } name } }}"); var result3 = await server.RenderResult(queryBuilder); CommonAssertions.AreEqualJsonStrings(expected, result3, "(3): " + result3); }
public async Task StandardExcution_OutputTest() { var serverBuilder = new TestServerBuilder(TestOptions.IncludeMetrics); serverBuilder.AddGraphType <SimpleExecutionController>(); serverBuilder.AddGraphQL(o => { o.ResponseOptions.ExposeMetrics = true; }); var server = serverBuilder.Build(); var builder = server.CreateQueryContextBuilder() .AddQueryText("query Operation1{ simple { simpleQueryMethod { property1} } }") .AddOperationName("Operation1"); var metrics = new ApolloTracingMetricsV1(server.Schema); builder.AddMetrics(metrics); var result = await server.RenderResult(builder); var parsePhase = metrics.PhaseEntries[ApolloExecutionPhase.PARSING]; var validationPhase = metrics.PhaseEntries[ApolloExecutionPhase.VALIDATION]; var simple = metrics.ResolverEntries.First(x => x.Key.Field.Name == "simple").Value; var queryMethod = metrics.ResolverEntries.First(x => x.Key.Field.Name == "simpleQueryMethod").Value; var property1 = metrics.ResolverEntries.First(x => x.Key.Field.Name == "property1").Value; var expectedResult = @" { ""data"": { ""simple"": { ""simpleQueryMethod"": { ""property1"": ""default string"" } } }, ""extensions"": { ""tracing"": { ""version"": 1, ""startTime"": ""[StartTime]"", ""endTime"": ""[EndTime]"", ""duration"": [Duration], ""parsing"": { ""startOffset"": [ParsingOffset], ""duration"": [ParsingDuration] }, ""validation"": { ""startOffset"":[ValidationOffset], ""duration"": [ValidationDuration] }, ""execution"": { ""resolvers"": [ { ""path"": [""simple""], ""fieldName"": ""simple"", ""parentType"" : ""Query"", ""returnType"": ""Query_Simple"", ""startOffset"": [simpleStartOffset], ""duration"": [simpleDuration] }, { ""path"": [""simple"", ""simpleQueryMethod""], ""fieldName"": ""simpleQueryMethod"", ""parentType"": ""Query_Simple"", ""returnType"": ""TwoPropertyObject"", ""startOffset"": [SimpleQueryMethodOffset], ""duration"": [SimpleQueryMethodDuration] }, { ""path"": [""simple"", ""simpleQueryMethod"", ""property1""], ""fieldName"": ""property1"", ""parentType"": ""TwoPropertyObject"", ""returnType"": ""String"", ""startOffset"": [Property1Offset], ""duration"": [Property1Duration] } ] } } } }"; expectedResult = expectedResult.Replace("[StartTime]", metrics.StartDate.ToRfc3339String()) .Replace("[EndTime]", metrics.StartDate.AddTicks(metrics.TotalTicks).ToRfc3339String()) .Replace("[Duration]", metrics.TotalTicks.ToString()) .Replace("[ParsingOffset]", parsePhase.StartOffsetNanoseconds.ToString()) .Replace("[ParsingDuration]", parsePhase.DurationNanoSeconds.ToString()) .Replace("[ValidationOffset]", validationPhase.StartOffsetNanoseconds.ToString()) .Replace("[ValidationDuration]", validationPhase.DurationNanoSeconds.ToString()) .Replace("[simpleStartOffset]", simple.StartOffsetNanoseconds.ToString()) .Replace("[simpleDuration]", simple.DurationNanoSeconds.ToString()) .Replace("[SimpleQueryMethodOffset]", queryMethod.StartOffsetNanoseconds.ToString()) .Replace("[SimpleQueryMethodDuration]", queryMethod.DurationNanoSeconds.ToString()) .Replace("[Property1Offset]", property1.StartOffsetNanoseconds.ToString()) .Replace("[Property1Duration]", property1.DurationNanoSeconds.ToString()); CommonAssertions.AreEqualJsonStrings(expectedResult, result); }