Repository: mcollina/fastify-gql
Pull request #279 (2020-09-08 17:31:18)

Use the correct prefix for the ide


Patch:
diff --git a/index.d.ts b/index.d.ts
index 5588c6d..b3c71e5 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -213,6 +213,30 @@ export interface FastifyGQLCommonOptions {
    * receive an array of responses within a single request.
    */
   allowBatchedQueries?: boolean;
+
+  /**
+   * Settings for GraphQL Playground. These settings only apply if `graphiql` parameter is set to 'playground'.
+   * The most current GraphQL Playground code is loaded via CDN, so new configuration settings may be available.
+   * See https://github.com/prisma-labs/graphql-playground#usage for the most up-to-date list.
+   */
+  playgroundSettings?: {
+    ['editor.cursorShape']: 'line' | 'block' | 'underline';
+    ['editor.fontFamily']: string;
+    ['editor.fontSize']: number;
+    ['editor.reuseHeaders']: boolean;
+    ['editor.theme']: 'dark' | 'light';
+    ['general.betaUpdates']: boolean;
+    ['prettier.printWidth']: number;
+    ['prettier.tabWidth']: number;
+    ['prettier.useTabs']: boolean;
+    ['request.credentials']: 'omit' | 'include' | 'same-origin';
+    ['schema.disableComments']: boolean;
+    ['schema.polling.enable']: boolean;
+    ['schema.polling.endpointFilter']: string;
+    ['schema.polling.interval']: number;
+    ['tracing.hideTracingResponse']: boolean;
+    ['tracing.tracingSupported']: boolean;
+  };
 }
 
 export type FastifyGQLOptions = FastifyGQLCommonOptions & (FastifyGQLGatewayOptions | FastifyGQLSchemaOptions)
diff --git a/lib/gateway/make-resolver.js b/lib/gateway/make-resolver.js
index 4449568..e2e903e 100644
--- a/lib/gateway/make-resolver.js
+++ b/lib/gateway/make-resolver.js
@@ -292,7 +292,7 @@ function collectFragmentsToInclude (usedFragments, fragments, service, schema) {
     })
 
     const fragmentsInSelections = getFragmentNamesInSelection(selections).filter(fragmentName => !visitedFragments.has(fragmentName))
-    result.push(...collectFragmentsToInclude(fragmentsInSelections, fragments, service))
+    result.push(...collectFragmentsToInclude(fragmentsInSelections, fragments, service, schema))
   }
 
   return result
@@ -330,6 +330,9 @@ function makeResolver ({ service, createOperation, transformData, isQuery, isRef
       fragments,
       schema
     } = info
+
+    if (isReference && !parent[fieldName]) return null
+
     const resolverKey = generatePathKey(info.path).join('.').replace(/\d/g, '_IDX_')
     const { reply, __currentQuery, lruGatewayResolvers, pubsub } = context
 
diff --git a/lib/routes.js b/lib/routes.js
index f652819..251ccdf 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -301,7 +301,7 @@ module.exports = async function (app, opts) {
       app.get('/graphiql/config.js', (req, reply) => {
         reply
           .header('Content-Type', 'application/javascript')
-          .send(`window.GRAPHQL_ENDPOINT = '${opts.prefix || ''}${graphqlPath}'`)
+          .send(`window.GRAPHQL_ENDPOINT = '${app.prefix}${graphqlPath}'`)
       })
     }
     if (opts.ide === 'playground') {
@@ -313,8 +313,8 @@ module.exports = async function (app, opts) {
           .header('Content-Type', 'application/javascript')
           .send(`window.addEventListener('load', function(event) {
           GraphQLPlayground.init(document.getElementById('root'), {
-            subscriptionEndpoint: '${opts.prefix || ''}${graphqlPath}',
-            endpoint: '${opts.prefix || ''}${graphqlPath}',
+            subscriptionEndpoint: '${app.prefix}${graphqlPath}',
+            endpoint: '${app.prefix}${graphqlPath}',
             settings: ${JSON.stringify(opts.ideSettings)},
           });
         });`)


Test patch:
diff --git a/test/gateway/schema.js b/test/gateway/schema.js
index caa6242..e43560f 100644
--- a/test/gateway/schema.js
+++ b/test/gateway/schema.js
@@ -203,6 +203,10 @@ test('It builds the gateway schema correctly', async (t) => {
     pid
     title
     content
+    ...AuthorFragment
+  }
+  
+  fragment AuthorFragment on Post {
     author {
       ...UserFragment
     }
@@ -404,3 +408,136 @@ test('It support variable inside nested arguments', async (t) => {
     }
   })
 })
+
+test('Should not throw on nullable reference', async (t) => {
+  const topPosts = [
+    {
+      id: 1,
+      title: 'test',
+      content: 'test'
+    },
+    {
+      id: 2,
+      title: 'test2',
+      content: 'test2',
+      authorId: 1
+    }
+  ]
+
+  const users = [
+    {
+      id: 1,
+      name: 'toto'
+    }
+  ]
+
+  const postServicePort = await createService(t, `
+    extend type Query {
+      topPosts: [Post]
+    }
+
+    type Post @key(fields: "id") {
+      id: ID!
+      title: String
+      content: String
+      author: User
+    }
+
+    extend type User @key(fields: "id") {
+      id: ID! @external
+    }
+  `, {
+    Post: {
+      author: async (root) => {
+        if (root.authorId) {
+          return { __typename: 'User', id: root.authorId }
+        }
+      }
+    },
+    Query: {
+      topPosts: async () => {
+        return topPosts
+      }
+    }
+  })
+
+  const userServicePort = await createService(t, `
+    type User @key(fields: "id") {
+      id: ID!
+      name: String
+    }
+  `, {
+    User: {
+      __resolveReference: async (reference) => {
+        if (reference.id) {
+          return users.find(u => u.id === parseInt(reference.id))
+        }
+      }
+    }
+  })
+
+  const gateway = Fastify()
+  t.tearDown(() => {
+    gateway.close()
+  })
+  gateway.register(GQL, {
+    gateway: {
+      services: [
+        {
+          name: 'post',
+          url: `http://localhost:${postServicePort}/graphql`
+        },
+        {
+          name: 'user',
+          url: `http://localhost:${userServicePort}/graphql`
+        }
+      ]
+    }
+  })
+
+  await gateway.listen(0)
+
+  const query = `
+  {
+    topPosts{
+      id
+      title
+      content
+      author {
+        id
+        name
+      }
+    }
+  }`
+
+  const res = await gateway.inject({
+    method: 'POST',
+    headers: {
+      'content-type': 'application/json'
+    },
+    url: '/graphql',
+    body: JSON.stringify({ query })
+  })
+
+  t.deepEqual(JSON.parse(res.body), {
+    data: {
+      topPosts: [
+        {
+          id: 1,
+          title: 'test',
+          content: 'test',
+          author: null
+        },
+        {
+          id: 2,
+          title: 'test2',
+          content: 'test2',
+          author: {
+            id: 1,
+            name: 'toto'
+          }
+        }
+      ]
+    }
+  })
+})
diff --git a/test/routes.js b/test/routes.js
index 81b6bd6..6263030 100644
--- a/test/routes.js
+++ b/test/routes.js
@@ -1733,6 +1733,54 @@ test('if ide is graphiql, serve config.js with the correct endpoint', async (t)
   t.matchSnapshot(res.body)
 })
 
+test('if ide is graphiql with a prefix, serve config.js with the correct endpoint', async (t) => {
+  const app = Fastify()
+  const schema = `
+    type Query {
+      add(x: Int, y: Int): Int
+    }
+  `
+  app.register(GQL, {
+    ide: 'graphiql',
+    path: '/app/graphql',
+    prefix: '/something',
+    schema
+  })
+
+  const res = await app.inject({
+    method: 'GET',
+    url: '/something/graphiql/config.js'
+  })
+  t.strictEqual(res.statusCode, 200)
+  t.strictEqual(res.headers['content-type'], 'application/javascript')
+  t.strictEqual(res.body.toString(), 'window.GRAPHQL_ENDPOINT = \'/something/app/graphql\'')
+})
+
+test('if ide is graphiql with a prefix from a wrapping plugin, serve config.js with the correct endpoint', async (t) => {
+  const app = Fastify()
+  const schema = `
+    type Query {
+      add(x: Int, y: Int): Int
+    }
+  `
+
+  app.register(async (app) => {
+    app.register(GQL, {
+      ide: 'graphiql',
+      path: '/app/graphql',
+      schema
+    })
+  }, { prefix: '/something' })
+
+  const res = await app.inject({
+    method: 'GET',
+    url: '/something/graphiql/config.js'
+  })
+  t.strictEqual(res.statusCode, 200)
+  t.strictEqual(res.headers['content-type'], 'application/javascript')
+  t.strictEqual(res.body.toString(), 'window.GRAPHQL_ENDPOINT = \'/something/app/graphql\'')
+})
+
 test('if ide is playground, and playgroundSettings is set, serve init.js with playground editor options ', async (t) => {
   const app = Fastify()
   const schema = `