Exemple #1
0
        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);
        }
Exemple #2
0
        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);
        }
Exemple #3
0
        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);
        }
Exemple #4
0
        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);
        }
Exemple #6
0
        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);
        }
Exemple #9
0
        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));
        }
Exemple #10
0
        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);
        }
Exemple #11
0
        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"));
        }
Exemple #12
0
        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);
        }
Exemple #13
0
        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"));
        }
Exemple #14
0
        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);
        }
Exemple #15
0
        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);
        }
Exemple #17
0
        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)));
        }
Exemple #18
0
        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);
        }
Exemple #19
0
        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);
        }
Exemple #20
0
        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);
        }