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);
        }
Exemple #3
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.");
        }
Exemple #4
0
        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");
        }
Exemple #5
0
        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");
        }
Exemple #7
0
        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);
        }
Exemple #8
0
        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);
        }
Exemple #10
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.");
        }
Exemple #11
0
        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_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");
        }
Exemple #13
0
        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");
        }
Exemple #14
0
        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");
        }
Exemple #15
0
        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");
        }
Exemple #18
0
        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_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";
        }
Exemple #21
0
        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";
        }
Exemple #22
0
        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_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_Fragments_Errors()
        {
            TestEnv.LogTestMethodStart();
            string          query;
            GraphQLResponse resp;

            TestEnv.LogTestDescr("error - invalid (unknown) field in a fragment ");
            query = @"
query myQuery { 
  getThing(id: 1) {
    ...Fr1 
  }
}
fragment Fr1 on Thing {
    nameX
    ...Fr2
}
fragment Fr2 on NamedObj {
    name2
}
";
            // errors:
            //   Fragment Fr1: field nameX not defined on type Thing.
            //   Fragment Fr2: field name2 not defined on type NamedObj.
            resp = await ExecuteAsync(query, throwOnError : false);

            Assert.AreEqual(2, resp.Errors.Count, "Expected 2 errors.");

            TestEnv.LogTestDescr("error - self-ref and circular ref fragments; circular refs are invalid at top level.");
            query = @"
query myQuery { 
  getThing(id: 1) {
    ...Fr1 
  }
}
fragment Fr1 on Thing {
    name
    ...Fr2
}
fragment Fr2 on Thing {
    name
    ...Fr1
}
";
            resp  = await ExecuteAsync(query, throwOnError : false);

            Assert.AreEqual(2, resp.Errors.Count, "Expected 2 errors.");
            var isSelfRef = resp.Errors.All(err => err.Message.Contains("self-referencing"));

            Assert.IsTrue(isSelfRef, "Expected 'fragment self-referencing' errors only.");

            TestEnv.LogTestDescr("self-referencing fragment; self-ref and circular refs are OK when inside selection subsets.");
            query = @"
query myQuery { 
  getThing(id: 1) {
    ...ThingDetails 
  }
}
fragment ThingDetails on Thing {
    name
    nextThing {
      ...ThingDetails
    }
}
";
            //      Assert.IsTrue(false, "Fix this: self-ref fragment crashes the test process.");
            resp = await ExecuteAsync(query, throwOnError : false);

            Assert.AreEqual(0, resp.Errors.Count, "Expected no errors.");

            TestEnv.LogTestDescr("using self-referencing fragment to unfold the type definition");
            query = @"
#                      querying list of fields of an input type; print out field name, type
query {
  __type (name: ""InputObjWithList"") {
    name
    kind
    inputFields {
      name
      type {
        ...TypeDetails
      }
    }
  }
}
fragment TypeDetails on __Type {
    displayName      # this is our extension of the spec
    kind
    ofType {
      ...TypeDetails
    }
}
";
            resp  = await ExecuteAsync(query, throwOnError : false);

            Assert.AreEqual(0, resp.Errors.Count, "Expected no errors.");
        }
Exemple #27
0
        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_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);
        }
        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()
        }