public async Task Test_Input_Nulls() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; string result; TestEnv.LogTestDescr("Nullable args are optional by definition; calling method with all (nullable) args missing"); query = @" query { echo: echoInputValuesWithNulls() }"; resp = await ExecuteAsync(query); result = resp.Data.GetValue <string>("echo"); Assert.AreEqual("|||||", result, "Result mismatch"); TestEnv.LogTestDescr("value coersion."); query = @" query { echo: echoInputValuesWithNulls(boolVal: true, longVal: 123, doubleVal: 23, strVal: null, kindVal: KIND_ONE, flags: [FLAG_ONE]) }"; resp = await ExecuteAsync(query); result = resp.Data.GetValue <string>("echo"); Assert.AreEqual("True|123|23||KindOne|FlagOne", result, "Result mismatch"); }
public async Task Test_FragmentsInline() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TestEnv.LogTestDescr("Testing inline fragment"); query = @" query { getSomeNamedObjects { name ... on OtherThing { idStr } } } "; // we just testing it won't fail on idStr resp = await ExecuteAsync(query); var list = resp.Data.GetValue <IList>("getSomeNamedObjects"); Assert.IsTrue(list.Count > 0); }
public void Test_Misc_AsyncMethods() { TestEnv.LogTestMethodStart(); TestEnv.LogTestDescr(@" async calls, verifying that stack unwinds when resolver code awaits."); // We will fire async request, we call it synchonously without await, and it will return Task. // The target method awaits a static shared int to become positive; // once we get incomplete task from the method, we set the value to current seconds, wait for task to complete, // and check the returned value. // We verify that async method unwinds the call stack all the way when it awaits for a slow event. var req = new GraphQLRequest() { Query = @" query { v: waitForPositiveValueAsync }" }; // The query returns whatever is in the ThingsBizApp.WaitValue static field, but it waits until value is > 0 ThingsResolvers.WaitValue = -1; var task = TestEnv.ThingsServer.ExecuteAsync(req); Assert.IsFalse(task.IsCompleted, "expected pending task"); // Once we set the field, the task will be completed and result returned. var testValue = ThingsResolvers.WaitValue = AppTime.Now.TimeOfDay.Seconds; task.Wait(); var retValue = (int)task.Result.Data["v"]; Assert.AreEqual(testValue, retValue, "Returned value does not match test value."); }
public static async Task <GraphQLResponse> ExecuteAsync(string query, IDictionary <string, object> variables = null, bool throwOnError = true) { GraphQLResponse resp = null; try { var req = new GraphQLRequest() { Query = query, Variables = variables }; resp = await ThingsServer.ExecuteAsync(req); if (!resp.IsSuccess()) { var errText = resp.GetErrorsAsText(); Debug.WriteLine("Errors: \r\n" + errText); } } catch (Exception ex) { TestEnv.LogException(query, ex); throw; } if (resp != null && resp.Errors != null && resp.Errors.Count > 0 && throwOnError) { throw new Exception($"Request failed: {resp.Errors[0].Message}"); } return(resp); }
public async Task Test_Model_CustomScalars() { TestEnv.LogTestMethodStart(); TestEnv.LogTestDescr(@" custom scalars: Uuid, Decimal"); var query = @" query { res: echoCustomScalars(dec: -12345.78, uuid: 'e675af6b-a421-43ef-98f4-e155df7ab8f6') }"; var resp = await ExecuteAsync(query); var result = (string)resp.Data["res"]; var expected = "-12345.78|e675af6b-a421-43ef-98f4-e155df7ab8f6"; Assert.AreEqual(expected, result, "Result mismatch"); TestEnv.LogTestDescr(@" custom scalars: Date, Time"); query = @" query { res: echoDateTimeScalars(dt: '2020/05/31', date: '2020/06/15', time: '11:22:33') }"; resp = await ExecuteAsync(query); result = (string)resp.Data["res"]; expected = "2020-05-31|2020-06-15|11:22:33"; Assert.AreEqual(expected, result, "Result mismatch"); }
public async Task Test_Introspection_Misc() { TestEnv.LogTestMethodStart(); TestEnv.LogTestDescr(@" misc introspection queries."); var introQuery = @" query { ThingType: __type(name: ""Thing"") { name fields { name, type { displayName } # displayName is our extension to spec } } typeKind: __type(name: ""__TypeKind"") { name enumValues (includeDeprecated: true) {name} } } "; var resp = await ExecuteAsync(introQuery); // just check it goes ok TestEnv.LogTestDescr(@" introspection queries, checking isDeprecated and and deprecationReason fields."); introQuery = @" query introQuery { inputObjType: __type (name: ""InputObj"") { name inputFields { name isDeprecated deprecationReason } } } "; resp = await ExecuteAsync(introQuery); var inpObj = resp.Data["inputObjType"]; Assert.IsNotNull(inpObj, "Expected input obj type"); TestEnv.LogTestDescr(@" Introspection, querying all __schema fields"); introQuery = @" query introQuery { __schema { queryType { name fields { name } } mutationType { name fields { name } } subscriptionType { name fields { name } } types { name } directives { name } } } "; resp = await ExecuteAsync(introQuery); var typeList = resp.GetValue <IList>("__schema.types"); Assert.IsTrue(typeList.Count > 5, "Expected types"); } //method
public async Task Test_Out_Lists() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TestEnv.LogTestDescr(@" Returning plain list of randoms"); query = @" query { getThing(id: 1) { randoms(count: 5) } }"; resp = await ExecuteAsync(query); var randArr = resp.GetValue <int[]>("getThing.randoms"); Assert.AreEqual(5, randArr.Length, "Expected array of 5 randoms"); TestEnv.LogTestDescr(@" lists of lists."); query = @" query { res: getIntListRank2() }"; resp = await ExecuteAsync(query); // returns [ [3,2,1], [6, 5, 4] ] var intArr = resp.GetValue <int[][]>("res"); Assert.AreEqual(2, intArr.Length, "Expected array of 2 elems"); Assert.AreEqual(3, intArr[0].Length, "Expected array of 3 elems"); TestEnv.LogTestDescr(@" list of object types."); query = @" query { res: getThingsList() { name } }"; resp = await ExecuteAsync(query); var objArr = resp.GetValue <object[]>("res"); Assert.IsNotNull(objArr); TestEnv.LogTestDescr(@" list of lists of object types."); query = @" query { res: getThingsListRank2() { name kind } }"; resp = await ExecuteAsync(query); var objArr2 = resp.GetValue <object[]>("res"); Assert.AreEqual(2, objArr2.Length, "Expected array of 2 elems"); var childArr = objArr2[0] as object[]; Assert.AreEqual(2, childArr.Length, "Expected child array of 2 elems"); }
public async Task Test_Introspection_GraphiqlQuery() { TestEnv.LogTestMethodStart(); TestEnv.LogTestDescr(@" testing Graphiql introspection query."); var query = _graphiqlIntroQuery; var resp = await ExecuteAsync(query); Assert.AreEqual(0, resp.Errors.Count); }
public void Test_Model_SchemaGenerateParse() { TestEnv.LogTestMethodStart(); TestEnv.LogTestDescr(@" schema doc generator; generating schema and parsing it, verifying syntactic correctness. See schema saved in the _thingsApiSchema.txt file in bin folder. "); var schemaDoc = TestEnv.ThingsServer.Model.SchemaDoc; // Try parsing the schema doc var parser = TestEnv.ThingsServer.Grammar.CreateSchemaParser(); var schemaParseTree = parser.Parse(schemaDoc); Assert.IsFalse(schemaParseTree.HasErrors(), "expected no schema parsing errors."); }
public async Task Test_Fragments_Inline() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TestEnv.LogTestDescr("query on union with fragment with conditional-on-type inline fragments "); query = @" query { uList: getThingsUnionList() { ...UnionFields } } fragment UnionFields on ThingsUnion { __typename name ... on Thing { nextThing { nextName: name } } ... on OtherThing { idStr } }"; resp = await ExecuteAsync(query); var nextName = resp.GetValue <string>("uList/#0/nextThing/nextName"); Assert.IsNotNull(nextName, "Expected nextName"); var idStr = resp.GetValue <string>("uList/#1/idStr"); Assert.IsNotNull(idStr, "Expected idStr"); TestEnv.LogTestDescr("Testing inline fragment on interface output"); query = @" query { getSomeNamedObjects { name ... on OtherThing { idStr } } } "; // we just testing it won't fail on idStr resp = await ExecuteAsync(query); var list = resp.Data.GetValue <IList>("getSomeNamedObjects"); Assert.IsTrue(list.Count > 0); }
public async Task Test_Misc_Exceptions() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TestEnv.LogTestDescr(@"handling 'unexpected' exceptions in resolver methods."); // exception in resolver query = @" # exception in resolver and in field reader query { # Three queries will be executed in parallel. All with throw, expect 3 exc th1: things { id name otherThings { getNameOrThrowAsync # async resolver method } } th2: things { id name otherThings { getNameOrThrow # sync resolver method } } th3: things { id name otherThings { nameOrThrow # this is plain property, it throws on some objects } } }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(3, resp.Errors.Count, "Expected 3 error(s)"); var expected = new List <string>() { "Exception thrown by NameOrThrow.", "Exception thrown by GetNameOrThrowAsync.", "Exception thrown by GetNameOrThrow." }; Assert.IsTrue(expected.Contains(resp.Errors[0].Message), "error message missing."); Assert.IsTrue(expected.Contains(resp.Errors[1].Message), "error message missing."); Assert.IsTrue(expected.Contains(resp.Errors[2].Message), "error message missing."); }
public async Task Test_Input_FlagsEnum() { TestEnv.LogTestMethodStart(); var enumValues = Enum.GetValues(typeof(ThingKind)); TestEnv.LogTestDescr("passing flags enum value as literal array."); var query = @" query { echoFlags(flags: [FLAG_ONE FLAG_THREE]) }"; var resp = await ExecuteAsync(query); var theFlags = resp.GetValue <string[]>("echoFlags"); var theFlagsStr = string.Join(",", theFlags); Assert.AreEqual("FLAG_ONE,FLAG_THREE", theFlagsStr, "Invalid flags value"); TestEnv.LogTestDescr("passing empty array as flags enum value."); query = @" query { echoFlags(flags: []) }"; resp = await ExecuteAsync(query); theFlags = resp.GetValue <string[]>("echoFlags"); theFlagsStr = string.Join(",", theFlags); Assert.AreEqual("", theFlagsStr, "Expected empty flags value"); TestEnv.LogTestDescr("passing null as flags enum value; should return empty array."); query = @" query { echoFlags(flags: null) }"; resp = await ExecuteAsync(query); theFlags = resp.GetValue <string[]>("echoFlags"); theFlagsStr = string.Join(",", theFlags); Assert.AreEqual("", theFlagsStr, "Expected empty flags value"); TestEnv.LogTestDescr(" verify flags enum value passed to resolver."); // Test - verify value that arrives to resolver query = @" query { echoFlagsStr(flags: [FLAG_TWO FLAG_THREE]) } # returns c# value.ToString() "; resp = await ExecuteAsync(query); theFlagsStr = resp.GetValue <string>("echoFlagsStr"); Assert.AreEqual("FlagTwo,FlagThree", theFlagsStr, "Invalid returned flags value"); TestEnv.LogTestDescr(" testing flags enum field on object. ApiThing.theFlags is [Flags] enum; the value returned should be list of strings."); query = @" query { thing: getThing(id:1) { id name kind theFlags } }"; resp = await ExecuteAsync(query); theFlags = resp.GetValue <string[]>("thing.theFlags"); theFlagsStr = string.Join(",", theFlags); Assert.AreEqual("FLAG_ONE,FLAG_THREE", theFlagsStr, "Invalid enum array property"); }
public async Task Test_RequestTiming() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; query = @" query myQuery { # these two operations will be executed in parallel th1: things { name, nextThing { name }, kind, theFlags } th2: things { id, name, nextThing { name }, kind, theFlags } }"; TestEnv.LogTestDescr("Run 1: warm-up, query not in cache, resolver method might need JITed, so the call might be relatively slow, 10 ms or more"); resp = await ExecuteAsync(query); var th1 = resp.GetValue <IList>("th1"); Assert.AreEqual(3, th1.Count, "Invalid thing count"); TestEnv.LogTestDescr("Run 2, same query with disabled query cache, so query should be parsed again, but now with path warmed up - should be 1 ms or less."); var reqCache = TestEnv.ThingsServer.RequestCache; try { reqCache.Enabled = false; resp = await ExecuteAsync(query); } finally { reqCache.Enabled = true; } th1 = resp.GetValue <IList>("th1"); Assert.AreEqual(3, th1.Count, "Invalid thing count"); TestEnv.LogTestDescr("Run 3: all warmed up, parsed query is retrieved from cache, not parsed. Should be really fast."); resp = await ExecuteAsync(query); th1 = resp.GetValue <IList>("th1"); Assert.AreEqual(3, th1.Count, "Invalid thing count"); }
public async Task Test_Misc_MultiTypeMapping() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TestEnv.LogTestDescr(@" mapping Thing entity to another GraphQL type ThingX."); query = @" query { thingsX { idX nameX kindX } }"; resp = await ExecuteAsync(query); var resList = resp.GetValue <IList>("thingsX"); Assert.AreEqual(3, resList.Count, "Expected 3 objects"); }
public async Task Test_Misc_Unions() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TestEnv.LogTestDescr(@" handling Union return type."); query = @" query { # return type is a list of Union of Thing|OtherThing uList: getThingsUnionList() { __typename name } }"; resp = await ExecuteAsync(query); var unionList = resp.GetValue <IList>("uList"); Assert.AreEqual(2, unionList.Count, "Expected 2 objects in union list"); }
public async Task Test_Misc_Interfaces() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TestEnv.LogTestDescr(@" handling Interface return types."); query = @" query { # return type is [NamedObj], NamedObj is interface list: getSomeNamedObjects() { __typename, name } }"; resp = await ExecuteAsync(query); var resList = resp.GetValue <IList>("list"); Assert.AreEqual(2, resList.Count, "Expected 2 objects"); }
public async Task Test_Input_Lists() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TDict vars; TestEnv.LogTestDescr("passing a list as an argument (list as literal)."); query = @" query { res: echoIntArray(intVals: [3,2,1]) }"; resp = await ExecuteAsync(query); var result = resp.Data.GetValue <string>("res"); Assert.AreEqual("3,2,1", result, "Result mismatch"); TestEnv.LogTestDescr("passing an enum array in argument (mapped to c# enum with [Flags] attr)."); query = @" query { res: echoEnumArray(flagVals: [FLAG_ONE, FLAG_TWO]) # returns flagVals.ToString() }"; resp = await ExecuteAsync(query); result = resp.Data.GetValue <string>("res"); Assert.AreEqual("FlagOne,FlagTwo", result, "Result mismatch"); TestEnv.LogTestDescr("passing a list int[][], value from literal and variables."); query = @" query ($one: Int) { res: echoIntListRank2(values: [ [3, 2, $one], [6, 5, 4] ] ) }"; vars = new TDict() { { "one", 1 } }; resp = await ExecuteAsync(query, vars); var str = resp.GetValue <string>("res"); Assert.AreEqual("3,2,1,6,5,4", str, "Result mismatch"); }
public async Task Test_Errors_ExcConvert() { TestEnv.LogTestMethodStart(); string query; GraphQLError err; GraphQLResponse resp; TestEnv.LogTestDescr("converting aggregate exc into multiple errors in the response."); query = @" query { throwAggrExc }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(3, resp.Errors.Count, "Expected 3 errors"); var allErrors = string.Join(",", resp.Errors.Select(e => e.Message)); Assert.AreEqual("Error1,Error2,Error3", allErrors, "Invalid error messsages"); }
public async Task Test_Errors_Syntax() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; GraphQLError err; TestEnv.LogTestDescr("syntax error, invalid character."); query = @" query myQuery { things { name, nextThing { ? name }, } }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 error"); err = resp.Errors[0]; Assert.IsTrue(err.Message.StartsWith("Query parsing failed: Invalid character: '?'."), "Invalid error message"); var loc = err.Locations[0]; Assert.AreEqual(5, loc.Line, "Invalid error loc line"); Assert.AreEqual(19, loc.Column, "Invalid error loc column"); TestEnv.LogTestDescr("syntax error, unbalanced braces."); query = @" query myQuery { things { name ] # error, should be right brace '}' here } }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 error"); err = resp.Errors[0]; Assert.AreEqual("Query parsing failed: Unmatched closing brace ']'.", err.Message); loc = err.Locations[0]; Assert.AreEqual(3, loc.Line, "Invalid error loc line"); Assert.AreEqual(18, loc.Column, "Invalid error loc column"); }
public async Task Test_Input_Validation() { TestEnv.LogTestMethodStart(); string query; TDict vars; GraphQLResponse resp; TestEnv.LogTestDescr("validation of input values in resolver code, posting response errors and aborting the request."); query = @" mutation ($id: Int!, $newName: String!) { th: mutateThingWithValidation(id: $id, newName: $newName) { id, name } }"; vars = new TDict() { { "id", -1 }, { "newName", "Name Tooo Loooooooooooooooooooooong" } }; resp = await ExecuteAsync(query, vars, throwOnError : false); Assert.AreEqual(2, resp.Errors.Count, "expected errors"); Assert.AreEqual("Id value may not be negative.", resp.Errors[0].Message); Assert.AreEqual("newName too long, max size = 10.", resp.Errors[1].Message); TestEnv.LogTestDescr("same resolver method, different variable values, happy path - all values are OK."); var newName2 = "Name2_"; vars = new TDict() { { "id", 2 }, { "newName", newName2 } }; resp = await ExecuteAsync(query, vars, throwOnError : false); Assert.AreEqual(0, resp.Errors.Count, "expected no errors"); var thing2 = ThingsApp.Instance.Things.First(t => t.Id == 2); Assert.AreEqual(newName2, thing2.Name); // undo the change thing2.Name = "Name2"; }
public void Test_Utils_DoubleBufferCache() { TestEnv.LogTestMethodStart(); var cache = new DoubleBufferCache <string, string>(capacity: 20, evictionTimeSec: 10); // fill out 10 items for (int i = 0; i < 10; i++) { cache.Add($"Key-{i}", $"Value-{i}"); } // read 20 items, 10 must be in cache var hits = 0; var misses = 0; for (int i = 0; i < 20; i++) { if (cache.TryLookup($"Key-{i}", out _)) { hits++; } else { misses++; } } // check that we have 10 hits and 10 misses Assert.AreEqual(10, hits, "Invalid hit count"); Assert.AreEqual(10, misses, "Invalid miss count"); // move time forward, and add item that is already there. this will force swapping buffers // and finalizing metrics record AppTime.SetOffset(TimeSpan.FromSeconds(30)); cache.Add("Key-0", "Value-0"); // check metrics var metrics = cache.GetMetrics(); Assert.AreEqual(10, metrics.ItemCount); Assert.AreEqual(20, metrics.ReadCount); Assert.AreEqual(10, metrics.MissCount); AppTime.ClearOffset(); }
public async Task Test_Misc_Mutations() { TestEnv.LogTestMethodStart(); TestEnv.LogTestDescr(@"mutations."); var mutReq = @" mutation myMut { mutateThing(id:1, newName: ""NewName1"") { id name } }"; var resp = await ExecuteAsync(mutReq); var newName = resp.GetValue <string>("mutateThing.name"); Assert.AreEqual("NewName1", newName, "new name mismatch"); var th1 = ThingsApp.Instance.Things.First(t => t.Id == 1); Assert.AreEqual("NewName1", th1.Name, "new name mismatch"); // undo the change th1.Name = "Name1"; }
public async Task Test_Misc_IncludeSkip() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TDict vars; TestEnv.LogTestDescr(@" @include directive with arg value from variable."); query = @" query myQuery ($all: Boolean!) { thing: getThing(id:1) { id name kind @include(if: $all) } }"; vars = new TDict() { { "all", true } }; resp = await ExecuteAsync(query, vars); var thingScope = resp.GetValue <IDict>("thing"); var hasKind = thingScope.ContainsKey("kind"); Assert.IsTrue(hasKind, "Expected kind field."); TestEnv.LogTestDescr(@" same, with $all=false."); // Now $all=false vars["all"] = false; resp = await ExecuteAsync(query, vars); thingScope = resp.GetValue <IDict>("thing"); hasKind = thingScope.ContainsKey("kind"); Assert.IsFalse(hasKind, "Expected no kind field."); }
public async Task Test_Input_InputTypes() { TestEnv.LogTestMethodStart(); string query; TestEnv.LogTestDescr("input values of various types."); query = @" query { res: echoInputValues(boolVal: true, intVal: 123, floatVal: 23.45, strVal: ""abc"", kindVal: KIND_ONE) }"; var resp = await ExecuteAsync(query); var result = (string)resp.Data["res"]; Assert.AreEqual("True|123|23.45|abc|KindOne", result, "Result mismatch"); TestEnv.LogTestDescr("nullable input values of various types."); query = @" query { res: echoInputValuesWithNulls(boolVal: true, longVal: 123, doubleVal: 23.45, strVal: null, kindVal: KIND_ONE, flags: [FLAG_ONE]) }"; resp = await ExecuteAsync(query); result = (string)resp.Data["res"]; Assert.AreEqual("True|123|23.45||KindOne|FlagOne", result, "Result mismatch"); TestEnv.LogTestDescr("hex notation for integer literals (0xFF)."); query = @" query { res: echoInputValuesWithNulls(boolVal: true, longVal: 0xFF, doubleVal: 23.45, strVal: null, kindVal: KIND_ONE, flags: [FLAG_ONE]) }"; resp = await ExecuteAsync(query); result = (string)resp.Data["res"]; Assert.AreEqual("True|255|23.45||KindOne|FlagOne", result, "Result mismatch"); }
public async Task Test_BugFixes() { TestEnv.LogTestMethodStart(); string query; GraphQLResponse resp; TestEnv.LogTestDescr(@"big decimal input values "); decimal decInp = 12345678901234567890m; query = @" query { dv: decTimesTwo(dec: decInp) }".Replace("decInp", decInp.ToString()); resp = await ExecuteAsync(query); var res = (decimal)resp.Data["dv"]; Assert.AreEqual(decInp * 2, res, "dec * 2 does not match."); TestEnv.LogTestDescr(@"big decimal input values, now with fractions "); decInp = 12345678901234567890.987m; query = @" query { dv: decTimesTwo(dec: decInp) }".Replace("decInp", decInp.ToString()); resp = await ExecuteAsync(query); res = (decimal)resp.Data["dv"]; Assert.AreEqual(decInp * 2, res, "dec * 2 does not match."); TestEnv.LogTestDescr(@"Repro issue #169 in VITA."); // reported in VITA: https://github.com/rivantsov/vita/issues/169 query = @" query { things() { id name otherThingWrapped { otherThingName otherThing { name} } # testing fix for interface entities mapping intfThing { id name tag} } }"; resp = await ExecuteAsync(query); var things = resp.Data["things"]; Assert.IsNotNull(things, "Expected result"); // bug, issue #8; resolver returning null on non-null field should cause error // getInvalidthing resolver returns Thing object with Name==null, but Name is String!; // should be an error. This is server failure, normally details not reported to client // but in this case server explains some details - I think it is safe, and helpful query = @" query { getInvalidThing {id name} } "; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected error"); var errMsg = resp.Errors[0].Message; Assert.AreEqual("Server error: resolver for non-nullable field 'name' returned null.", errMsg); }
public async Task Test_Input_Variables() { TestEnv.LogTestMethodStart(); string query; TDict vars; GraphQLResponse resp; string echoResp; TestEnv.LogTestDescr("missing variable value."); query = @" query myQuery($longVal: Long!) { echo: echoInputValuesWithNulls (longVal: $longVal) }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 error(s)"); Assert.AreEqual("Value for required variable longVal is not provided.", resp.Errors[0].Message); TestEnv.LogTestDescr("unknown variable used in argument."); query = @" query myQuery($longVal: Long) { echo: echoInputValuesWithNulls (longVal: $longValXYZ) }"; vars = new TDict() { { "longVal", 654321 } }; resp = await ExecuteAsync(query, vars, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 error(s)"); Assert.AreEqual("Variable longValXYZ not defined.", resp.Errors[0].Message); TestEnv.LogTestDescr("variable value type mismatch - sending string value for a long var."); query = @" query myQuery($longVal: Long) { echo: echoInputValuesWithNulls () }"; vars = new TDict() { { "longVal", "abc" } }; resp = await ExecuteAsync(query, vars, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "expected 1 error"); Assert.AreEqual("Variable $longVal: failed to convert value 'abc' to type Long: Invalid Long value: 'abc'", resp.Errors[0].Message); TestEnv.LogTestDescr("string -> enum conversion; enum input values are sent as strings."); query = @" query myQuery($kindVal: ThingKind) { echo: echoInputValuesWithNulls (kindVal: $kindVal) }"; vars = new TDict() { { "kindVal", "KIND_ONE" } }; resp = await ExecuteAsync(query, vars); echoResp = resp.GetValue <string>("echo"); Assert.AreEqual("||||KindOne|", echoResp); TestEnv.LogTestDescr("int -> long? automatic conversion."); query = @" query myQuery($longVal: Long) { echo: echoInputValuesWithNulls (longVal: $longVal) }"; vars = new TDict() { { "longVal", 654321 } }; resp = await ExecuteAsync(query, vars); echoResp = resp.GetValue <string>("echo"); Assert.AreEqual("|654321||||", echoResp); TestEnv.LogTestDescr("multiple input variables of various types, happy path."); query = @" query myQuery($boolVal: Boolean, $longVal: Long, $doubleVal: Double, $strVal: String, $kindVal: ThingKind, $flags: [TheFlags!]) { echo: echoInputValuesWithNulls (boolVal: $boolVal, longVal: $longVal, doubleVal: $doubleVal, strVal: $strVal, kindVal: $kindVal, flags: $flags ) }"; vars = new TDict() { { "boolVal", true }, { "longVal", 654321 }, { "doubleVal", 543.21 }, { "kindVal", "KIND_ONE" }, { "flags", new string[] { "FLAG_ONE", "FLAG_TWO" } }, { "strVal", "SomeString" } }; resp = await ExecuteAsync(query, vars); echoResp = resp.GetValue <string>("echo"); Assert.AreEqual("True|654321|543.21|SomeString|KindOne|FlagOne, FlagTwo", echoResp); //this is InputObj.ToString() }
public async Task Test_Misc_Batching() { TestEnv.LogTestMethodStart(); TestEnv.LogTestDescr(@"batching (aka Data Loader). Case 1 - simple field on child object (mainOtherThing). Notice # of resolver calls - 2; without batching it would be 4 = 1 (things) + 3 (mainOtherThing). "); var query = @" query { things() { id name mainOtherThing # this field is implemented by resolver method { name } } }"; var resp = await ExecuteAsync(query); var things = resp.Data["things"]; Assert.IsNotNull(things, "Expected result"); var thingsList = (IList)things; Assert.AreEqual(3, thingsList.Count); // check that resp contains certain string deep in object tree var otherThing1Name = resp.GetValue <string>("things.#1.mainOtherThing.name"); Assert.AreEqual("Other-2-a", otherThing1Name, "Missing specific string in the output."); // Without batching the resolver call count would be 4 - one for top query 'things()', // +3 calls for mainOtherThing for each parent Thing. // The mainOtherThing(method getMainOtherThing) is using batching; on the first call // the resolver covers for all future calls from parent objects; // it posts the values through fieldContext. // So executer calls the getMainOtherThing() resolver only once. Total number of calls is 2. var callCount = TestEnv.LastRequestContext.Metrics.ResolverCallCount; Assert.AreEqual(2, callCount, "Expected call count 2."); TestEnv.LogTestDescr(@"batching, Case 2 - list field on child object (otherThings). notice # of resolver calls - 2"); var query2 = @" query { things() { id name otherThings() { name } } }"; var resp2 = await ExecuteAsync(query2); var tn = resp2.GetValue <string>("things.#0.name"); Assert.IsNotNull(tn, "Expected result"); // check that resp contains certain string deep in object tree var otherThingName = resp2.GetValue <string>("things.#0.otherThings.#1.name"); Assert.AreEqual("Other-1-b", otherThingName, "Missing specific string in the output."); callCount = TestEnv.LastRequestContext.Metrics.ResolverCallCount; Assert.AreEqual(2, callCount, "Expected call count 2."); }
public async Task Test_Input_FieldArgs() { TestEnv.LogTestMethodStart(); string query; TDict vars; GraphQLResponse resp; GraphQLError err; TestEnv.LogTestDescr("error - invalid arg names."); query = @" query { echo: echoInputValuesWithNulls(boolValXYZ: true, longValXYZ: 123, doubleVal: 23.45) }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.IsTrue(resp.Errors.Count == 2, "Expected errors"); err = resp.Errors[0]; Assert.AreEqual("Field(dir) echoInputValuesWithNulls: argument boolValXYZ not defined.", err.Message); Assert.AreEqual(2, err.Path.Count, "Expected 2 elem path"); Assert.IsTrue(err.Locations != null && err.Locations.Count > 0, "Expected error location."); TestEnv.LogTestDescr("error - args referring to vars of wrong type."); query = @" query ($str: String) { echo: echoInputValuesWithNulls(longVal: $str) }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.IsTrue(resp.Errors.Count == 1, "Expected errors"); err = resp.Errors[0]; Assert.AreEqual("Incompatible types: variable $str cannot be converted to type 'Long'", err.Message); TestEnv.LogTestDescr("error - invalid arg name for a directive."); query = @" query { things { name @include(ifXYZ: true) } }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.IsTrue(resp.Errors.Count == 1, "Expected 1 error(s)"); Assert.AreEqual("Field(dir) @include: argument ifXYZ not defined.", resp.Errors[0].Message); TestEnv.LogTestDescr("error - passing wrong value types to field args using a literal."); query = @" query myQuery() { echo: echoInputValuesWithNulls (longVal: ""abc"") }"; vars = new TDict() { }; resp = await ExecuteAsync(query, vars, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 errors"); err = resp.Errors[0]; Assert.AreEqual("Invalid long value: '\"abc\"'", err.Message); TestEnv.LogTestDescr("error - passing wrong value types to field args, using bad value in a variable."); query = @" query myQuery($longVal: Long, $doubleVal: Double) { echo: echoInputValuesWithNulls (longVal: $longVal, doubleVal: $doubleVal) }"; vars = new TDict() { { "longVal", true }, { "doubleVal", "abc" } }; resp = await ExecuteAsync(query, vars, throwOnError : false); Assert.AreEqual(2, resp.Errors.Count, "Expected 2 errors"); TestEnv.LogTestDescr("error - passing wrong value types to field args, using variables; test multiple errors."); query = @" query myQuery($boolVal: Boolean, $longVal: Long, $doubleVal: Double, $strVal: String, $kindVal: ThingKind, $flags: [TheFlags!]) { # totally wrong match of args, expected 3 errors echo: echoInputValuesWithNulls (boolVal: $longVal, longVal: $doubleVal, doubleVal: $strVal ) }"; vars = new TDict() { { "boolVal", true }, { "longVal", 654321 }, { "doubleVal", 543.21 }, { "kindVal", "KIND_ONE" }, { "flags", new string[] { "FLAG_ONE", "FLAG_TWO" } }, { "strVal", "SomeString" } }; resp = await ExecuteAsync(query, vars, throwOnError : false); Assert.AreEqual(3, resp.Errors.Count, "Expected 3 error(s)"); }
public async Task Test_Input_InputObjects() { string query; GraphQLResponse resp; string result; TDict vars; TestEnv.LogTestMethodStart(); TestEnv.LogTestDescr("literal input object as argument."); query = @" query { res: echoInputObj(inpObj: {id: 1, name: ""abc"", num: 0}) # returns inpObj.ToString() }"; resp = await ExecuteAsync(query); result = (string)resp.Data["res"]; Assert.AreEqual("id:1,name:abc,num:0", result, "Result mismatch"); TestEnv.LogTestDescr("literal input object as argument, with some of its properties set from variables."); query = @" query myQuery($id : int) { res: echoInputObj(inpObj: {id: $id, name: ""abc"", num:456}) }"; vars = new TDict() { { "id", 1 } }; resp = await ExecuteAsync(query, vars); result = (string)resp.Data["res"]; Assert.AreEqual("id:1,name:abc,num:456", result, "Result mismatch"); TestEnv.LogTestDescr("literal input object as argument; variable value ($id) is not provided and is assigned from default."); query = @" query myQuery($num: Int!, $name: String!, $id: Int = 123) { echoInputObj (inpObj: {id: $id, num: $num, name: $name}) }"; vars = new TDict(); vars["num"] = 456; vars["name"] = "SomeName"; resp = await ExecuteAsync(query, vars); var echoInpObj2 = resp.GetValue <string>("echoInputObj"); Assert.AreEqual("id:123,name:SomeName,num:456", echoInpObj2); //this is InputObj.ToString() TestEnv.LogTestDescr("complex input object in a variable."); query = @" query myQuery($inpObj: InputObj!) { echoInputObj (inpObj: $inpObj) }"; vars = new TDict(); // we cannot use InputObj here, serializer will send first-cap prop names and request will fail vars["inpObj"] = new InputObj() { Id = 123, Num = 456, Name = "SomeName" }; resp = await ExecuteAsync(query, vars); var echoInpObj = resp.GetValue <string>("echoInputObj"); Assert.AreEqual("id:123,name:SomeName,num:456", echoInpObj); //this is InputObj.ToString() }
public async Task Test_Errors_InvalidQuery() { TestEnv.LogTestMethodStart(); string query; GraphQLError err; GraphQLResponse resp; TestEnv.LogTestDescr("error - unknown selection field."); query = @" query myQuery { things { name, nextThing { name, unknownField }, } }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 error"); err = resp.Errors[0]; Assert.IsTrue(err.Message.StartsWith("Field 'unknownField' not found"), "Invalid error message"); TestEnv.LogTestDescr("error - object-type field must have a selection subset."); // error - query = @" query myQuery { getThing(id: 1) { name, nextThing # error - selection subset is missing } }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 error"); err = resp.Errors[0]; var errPath = err.Path.ToCommaText(); Assert.AreEqual("getThing,nextThing", errPath, "Invalid error path"); Assert.AreEqual($"Field 'nextThing' of type 'Thing' must have a selection subset.", err.Message); TestEnv.LogTestDescr("error - scalar, enum fields may not have a selection subset."); query = @" query { things { name { abc } } }"; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 error"); err = resp.Errors[0]; Assert.AreEqual("Field 'name' of type 'String' may not have a selection subset.", err.Message); TestEnv.LogTestDescr("error - default (anonymous) query may not be combined with other operations."); query = @" # this is a default query { things { name { abc }, def } } # this is another query query { things { name { abc }, def } } "; resp = await ExecuteAsync(query, throwOnError : false); Assert.AreEqual(1, resp.Errors.Count, "Expected 1 error"); var errMsg = resp.Errors[0].Message; var expected = "If the request contains a default (anonymous) query, it cannot contain any other operations."; Assert.AreEqual(expected, errMsg); }