Repository: mcollina/fastify-gql (js)

Issue:
Slow test: test/gateway/pollingInterval.js
This suitte is really slow: https://github.com/mcollina/fastify-gql/blob/master/test/gateway/pollingInterval.js takes up to 8 seconds on my machine. We should speed it up

cc @iviaks

Pull request description:
Fix :: Gateway :: Slow test: test/gateway/pollingInterval.js
Closes #237 

Patch:
diff --git a/index.js b/index.js
index ce8aa2a..af29577 100644
--- a/index.js
+++ b/index.js
@@ -33,6 +33,7 @@ const { ErrorWithProps, defaultErrorFormatter } = require('./lib/errors')
 const persistedQueryDefaults = require('./lib/persistedQueryDefaults')
 
 const kLoaders = Symbol('fastify-gql.loaders')
+const kFactory = Symbol('fastify-gql.loadersFactory')
 
 function buildCache (opts) {
   if (Object.prototype.hasOwnProperty.call(opts, 'cache')) {
@@ -216,7 +217,12 @@ const plugin = fp(async function (app, opts) {
   app.decorateReply(graphqlCtx, null)
 
   app.decorateReply('graphql', function (source, context, variables, operationName) {
-    return app.graphql(source, Object.assign({ reply: this }, context), variables, operationName)
+    context = Object.assign({ reply: this, app }, context)
+    if (app[kFactory]) {
+      this[kLoaders] = factory.create(context)
+    }
+
+    return app.graphql(source, context, variables, operationName)
   })
 
   app.decorate('graphql', fastifyGraphQl)
@@ -301,9 +307,7 @@ const plugin = fp(async function (app, opts) {
     if (!factory) {
       factory = new Factory()
       app.decorateReply(kLoaders)
-      app.addHook('onRequest', async function (req, reply) {
-        reply[kLoaders] = factory.create({ req, reply, app })
-      })
+      app.decorate(kFactory, factory)
     }
 
     function defineLoader (name) {
diff --git a/lib/queryDepth.js b/lib/queryDepth.js
index 279a5c7..4dd7cc8 100644
--- a/lib/queryDepth.js
+++ b/lib/queryDepth.js
@@ -24,7 +24,7 @@ function queryDepth (definitions, queryDepthLimit) {
     for (const query of Object.keys(queryDepth)) {
       const totalDepth = queryDepth[query]
       if (totalDepth > queryDepthLimit) {
-        const queryDepthError = new Error(`${query} query exceeds the query depth limit of ${queryDepthLimit}`)
+        const queryDepthError = new Error(`${query} query depth (${totalDepth}) exceeds the query depth limit of ${queryDepthLimit}`)
         errors.push(queryDepthError)
       }
     }
@@ -33,13 +33,10 @@ function queryDepth (definitions, queryDepthLimit) {
   return errors
 }
 function determineDepth (node, current = 0) {
-  let result = current
   if (node.selectionSet) {
-    for (const selection of node.selectionSet.selections) {
-      result = determineDepth(selection, current++)
-    }
+    return Math.max(...node.selectionSet.selections.map((selection) => determineDepth(selection, current + 1)))
   }
-  return result
+  return current
 }
 function getQueriesAndMutations (definitions) {
   return definitions.reduce((map, definition) => {
diff --git a/package.json b/package.json
index 55c4ce7..59bc2a4 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
     "@types/ws": "^7.2.6",
     "@typescript-eslint/eslint-plugin": "^3.6.1",
     "@typescript-eslint/parser": "^3.6.1",
-    "autocannon": "^5.0.0",
+    "autocannon": "^6.0.0",
     "fastify": "^3.0.2",
     "graphql-tools": "^6.0.14",
     "pre-commit": "^1.2.2",


Test patch:
diff --git a/test/gateway/pollingInterval.js b/test/gateway/pollingInterval.js
index ed2d273..70cbc28 100644
--- a/test/gateway/pollingInterval.js
+++ b/test/gateway/pollingInterval.js
@@ -265,6 +265,11 @@ test("Polling schemas (if service is down, schema shouldn't be changed)", async
   const userService = Fastify()
   const gateway = Fastify()
 
+  t.tearDown(async () => {
+    await gateway.close()
+    await userService.close()
+  })
+
   userService.register(GQL, {
     schema: `
       extend type Query {
@@ -281,6 +286,7 @@ test("Polling schemas (if service is down, schema shouldn't be changed)", async
   })
 
   await userService.listen(0)
+  await clock.tickAsync()
 
   const userServicePort = userService.server.address().port
 
@@ -292,104 +298,111 @@ test("Polling schemas (if service is down, schema shouldn't be changed)", async
           url: `http://localhost:${userServicePort}/graphql`
         }
       ],
-      pollingInterval: 2000
+      pollingInterval: 500
     }
   })
 
   await gateway.listen(0)
+  await clock.tickAsync()
 
-  const res = await gateway.inject({
-    method: 'POST',
-    headers: {
-      'content-type': 'application/json'
-    },
-    url: '/graphql',
-    body: JSON.stringify({
-      query: `
-        query MainQuery {
-          me {
-            id
-            name
+  {
+    const { body } = await gateway.inject({
+      method: 'POST',
+      headers: {
+        'content-type': 'application/json'
+      },
+      url: '/graphql',
+      body: JSON.stringify({
+        query: `
+          query MainQuery {
+            me {
+              id
+              name
+            }
           }
-        }
-      `
+        `
+      })
     })
-  })
 
-  t.deepEqual(JSON.parse(res.body), {
-    data: {
-      me: {
-        id: 'u1',
-        name: 'John'
+    await clock.tickAsync()
+
+    t.deepEqual(JSON.parse(body), {
+      data: {
+        me: {
+          id: 'u1',
+          name: 'John'
+        }
       }
-    }
-  })
+    })
+  }
 
-  const res2 = await gateway.inject({
-    method: 'POST',
-    headers: {
-      'content-type': 'application/json'
-    },
-    url: '/graphql',
-    body: JSON.stringify({
-      query: `
-        query MainQuery {
-          me {
-            id
-            name
-            lastName
+  {
+    const { body } = await gateway.inject({
+      method: 'POST',
+      headers: {
+        'content-type': 'application/json'
+      },
+      url: '/graphql',
+      body: JSON.stringify({
+        query: `
+          query MainQuery {
+            me {
+              id
+              name
+              lastName
+            }
           }
-        }
-      `
+        `
+      })
     })
-  })
 
-  t.deepEqual(JSON.parse(res2.body), {
-    errors: [
-      {
-        message:
-          'Cannot query field "lastName" on type "User". Did you mean "name"?',
-        locations: [{ line: 6, column: 13 }]
-      }
-    ],
-    data: null
-  })
+    t.deepEqual(JSON.parse(body), {
+      errors: [
+        {
+          message:
+            'Cannot query field "lastName" on type "User". Did you mean "name"?',
+          locations: [{ line: 6, column: 15 }]
+        }
+      ],
+      data: null
+    })
+  }
 
   await userService.close()
+  await clock.tickAsync(500)
 
-  await clock.tickAsync(2000)
-
-  const res3 = await gateway.inject({
-    method: 'POST',
-    headers: {
-      'content-type': 'application/json'
-    },
-    url: '/graphql',
-    body: JSON.stringify({
-      query: `
-        query MainQuery {
-          me {
-            id
-            name
-            lastName
+  {
+    const { body } = await gateway.inject({
+      method: 'POST',
+      headers: {
+        'content-type': 'application/json'
+      },
+      url: '/graphql',
+      body: JSON.stringify({
+        query: `
+          query MainQuery {
+            me {
+              id
+              name
+              lastName
+            }
           }
-        }
-      `
+        `
+      })
     })
-  })
 
-  t.deepEqual(JSON.parse(res3.body), {
-    errors: [
-      {
-        message:
-          'Cannot query field "lastName" on type "User". Did you mean "name"?',
-        locations: [{ line: 6, column: 13 }]
-      }
-    ],
-    data: null
-  })
+    t.deepEqual(JSON.parse(body), {
+      errors: [
+        {
+          message:
+            'Cannot query field "lastName" on type "User". Did you mean "name"?',
+          locations: [{ line: 6, column: 15 }]
+        }
+      ],
+      data: null
+    })
+  }
 
-  await gateway.close()
   clock.uninstall()
 })
 
@@ -506,6 +519,7 @@ test('Polling schemas (if service is mandatory, exception should be thrown)', as
     })
   }
 
+  gateway.graphql.gateway.close()
   await userService.close()
 
   t.rejects(async () => {
diff --git a/test/loaders.js b/test/loaders.js
index 42c0caf..0d5c8e4 100644
--- a/test/loaders.js
+++ b/test/loaders.js
@@ -467,3 +467,79 @@ test('throw when persistedQueries is empty but onlyPersisted is true', async t =
 
   t.rejects(app.ready(), 'onlyPersisted is true but there are no persistedQueries')
 })
+
+test('loaders support custom context', async (t) => {
+  const app = Fastify()
+
+  const loaders = {
+    Dog: {
+      async owner (queries, { reply, test }) {
+        t.is(test, 'custom')
+        // note that the second entry for max is cached
+        t.deepEqual(queries, [{
+          obj: {
+            name: 'Max'
+          },
+          params: {}
+        }, {
+          obj: {
+            name: 'Charlie'
+          },
+          params: {}
+        }, {
+          obj: {
+            name: 'Buddy'
+          },
+          params: {}
+        }])
+        return queries.map(({ obj }) => owners[obj.name])
+      }
+    }
+  }
+
+  app.register(GQL, {
+    schema,
+    resolvers,
+    loaders,
+    context: (request, reply) => {
+      return {
+        test: 'custom'
+      }
+    }
+  })
+
+  const res = await app.inject({
+    method: 'POST',
+    url: '/graphql',
+    body: {
+      query
+    }
+  })
+
+  t.equal(res.statusCode, 200)
+  t.deepEqual(JSON.parse(res.body), {
+    data: {
+      dogs: [{
+        name: 'Max',
+        owner: {
+          name: 'Jennifer'
+        }
+      }, {
+        name: 'Charlie',
+        owner: {
+          name: 'Sarah'
+        }
+      }, {
+        name: 'Buddy',
+        owner: {
+          name: 'Tracy'
+        }
+      }, {
+        name: 'Max',
+        owner: {
+          name: 'Jennifer'
+        }
+      }]
+    }
+  })
+})
diff --git a/test/query-depth.js b/test/query-depth.js
index 5337209..151bd08 100644
--- a/test/query-depth.js
+++ b/test/query-depth.js
@@ -144,7 +144,7 @@ test('queryDepth - test total depth is within queryDepth parameter', async (t) =
   app.register(GQL, {
     schema,
     resolvers,
-    queryDepth: 5
+    queryDepth: 6
   })
 
   // needed so that graphql is defined
@@ -162,14 +162,14 @@ test('queryDepth - test total depth is over queryDepth parameter', async (t) =>
   app.register(GQL, {
     schema,
     resolvers,
-    queryDepth: 3
+    queryDepth: 5
   })
 
   // needed so that graphql is defined
   await app.ready()
 
   const err = new BadRequest()
-  const queryDepthError = new Error('unnamedQuery query exceeds the query depth limit of 3')
+  const queryDepthError = new Error('unnamedQuery query depth (6) exceeds the query depth limit of 5')
   err.errors = [queryDepthError]
 
   try {
@@ -303,6 +303,7 @@ test('queryDepth - ensure query depth is correctly calculated', async (t) => {
           {
             id: 1,
             name: 'Strawberry',
+            description: 'Lorem ipsum',
             seasons: [
               { name: 'Spring' },
               { name: 'Summer' }
@@ -356,6 +357,7 @@ test('queryDepth - ensure query depth is correctly calculated', async (t) => {
     flavors {
       id
       name
+      description
       nutrition {
         calories,
         description {
@@ -394,6 +396,7 @@ test('queryDepth - ensure query depth is correctly calculated', async (t) => {
         {
           id: '1',
           name: 'Strawberry',
+          description: 'Lorem ipsum',
           nutrition: {
             calories: 123,
             description: {