graphql-tools: Schema extensions with interfaces fails in v7

Description

As you can see in the tests suite, schema extensions with interfaces fails in v7 when we use complexe fragments. I wrote this regression test “Schema extensions with interfaces -> should resolve with 2 fragments” to show you a failure example. I also added you some successful examples with simple queries with interfaces, complexe queries without interface and type merging. I hope this will help you 😉

Maybe related to https://github.com/ardatan/graphql-tools/issues/2157?

Tests suite

import { batchDelegateToSchema } from '@graphql-tools/batch-delegate'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { stitchSchemas } from '@graphql-tools/stitch'
import { graphql } from 'graphql'

const expectedData = [
  {
    network: { id: '57', domains: [{ id: '60', name: 'network57.com' }] },
  },
]

describe('Schema extensions with interfaces', () => {
  const networkSchema = makeExecutableSchema({
    typeDefs: `
      interface Domain {
        id: ID!
        name: String!
      }
      type Domain1 implements Domain {
        id: ID!
        name: String!
      }
      type Domain2 implements Domain {
        id: ID!
        name: String!
        extra: String!
      }
      type Network {
        id: ID!
        domains: [Domain!]!
      }
      type Query {
        networks(ids: [ID!]!): [Network!]!
      }
    `,
    resolvers: {
      Domain: {
        __resolveType() {
          return 'Domain1'
        },
      },
      Query: {
        networks: (root, { ids }) =>
          ids.map((id) => ({ id, domains: [{ id: Number(id) + 3, name: `network${id}.com` }] })),
      },
    },
  })

  const postsSchema = makeExecutableSchema({
    typeDefs: `
      type Network {
        id: ID!
      }
      type Post {
        id: ID!
        network: Network!
      }
      type Query {
        posts(ids: [ID!]!): [Post]!
      }
    `,
    resolvers: {
      Query: {
        posts: (root, { ids }) =>
          ids.map((id) => ({
            id,
            network: { id: Number(id) + 2 },
          })),
      },
    },
  })

  const gatewaySchema = stitchSchemas({
    subschemas: [{ schema: networkSchema }, { schema: postsSchema }],
    resolvers: {
      Post: {
        network: {
          selectionSet: '{ network { id } }',
          resolve(parent, _args, context, info) {
            return batchDelegateToSchema({
              key: parent.network.id,
              argsFromKeys: (ids) => ({ ids }),
              context,
              fieldName: 'networks',
              info,
              operation: 'query',
              schema: networkSchema,
            })
          },
        },
      },
    },
  })

  it('should resolve with no fragments', async () => {
    const { data } = await graphql(
      gatewaySchema,
      `
        query {
          posts(ids: [55]) {
            network {
              id
              domains {
                id
                name
              }
            }
          }
        }
      `,
    )

    expect(data.posts).toEqual(expectedData)
  })

  it('should resolve with 1 fragment', async () => {
    const { data } = await graphql(
      gatewaySchema,
      `
        query {
          posts(ids: [55]) {
            network {
              domains {
                name
              }
            }
            ...F2
          }
        }

        fragment F2 on Post {
          network {
            id
            domains {
              id
            }
          }
        }
      `,
    )

    expect(data.posts).toEqual(expectedData)
  })

  // Fails with `Cannot return null for non-nullable field Domain1.id` because batchDelegateToSchema returns
  // { domains: [{ name: 'network57.com', __typename: 'Domain1' }], id: '57' }
  it('should resolve with 2 fragments', async () => {
    const { data } = await graphql(
      gatewaySchema,
      `
        query {
          posts(ids: [55]) {
            network {
              domains {
                name
              }
            }
            ...F2
          }
        }

        fragment F2 on Post {
          network {
            ...F3
          }
        }

        fragment F3 on Network {
          id
          domains {
            id
          }
        }
      `,
    )

    expect(data.posts).toEqual(expectedData)
  })
})

describe('Schema extensions with no interfaces', () => {
  const networkSchema = makeExecutableSchema({
    typeDefs: `
      type Domain {
        id: ID!
        name: String!
      }
      type Network {
        id: ID!
        domains: [Domain!]!
      }
      type Query {
        networks(ids: [ID!]!): [Network!]!
      }
    `,
    resolvers: {
      Query: {
        networks: (root, { ids }) =>
          ids.map((id) => ({ id, domains: [{ id: Number(id) + 3, name: `network${id}.com` }] })),
      },
    },
  })

  const postsSchema = makeExecutableSchema({
    typeDefs: `
      type Network {
        id: ID!
      }
      type Post {
        id: ID!
        network: Network!
      }
      type Query {
        posts(ids: [ID!]!): [Post]!
      }
    `,
    resolvers: {
      Query: {
        posts: (root, { ids }) =>
          ids.map((id) => ({
            id,
            network: { id: Number(id) + 2 },
          })),
      },
    },
  })

  const gatewaySchema = stitchSchemas({
    subschemas: [{ schema: networkSchema }, { schema: postsSchema }],
    resolvers: {
      Post: {
        network: {
          selectionSet: '{ network { id } }',
          resolve(parent, _args, context, info) {
            return batchDelegateToSchema({
              key: parent.network.id,
              argsFromKeys: (ids) => ({ ids }),
              context,
              fieldName: 'networks',
              info,
              operation: 'query',
              schema: networkSchema,
            })
          },
        },
      },
    },
  })

  it('should resolve with 2 fragments', async () => {
    const { data } = await graphql(
      gatewaySchema,
      `
        query {
          posts(ids: [55]) {
            network {
              domains {
                name
              }
            }
            ...F2
          }
        }

        fragment F2 on Post {
          network {
            ...F3
          }
        }

        fragment F3 on Network {
          id
          domains {
            id
          }
        }
      `,
    )

    expect(data.posts).toEqual(expectedData)
  })
})

describe('Type merging with interfaces', () => {
  const networkSchema = makeExecutableSchema({
    typeDefs: `
      interface Domain {
        id: ID!
        name: String!
      }
      type Domain1 implements Domain {
        id: ID!
        name: String!
      }
      type Domain2 implements Domain {
        id: ID!
        name: String!
        extra: String!
      }
      type Network {
        id: ID!
        domains: [Domain!]!
      }
      type Query {
        networks(ids: [ID!]!): [Network!]!
      }
    `,
    resolvers: {
      Domain: {
        __resolveType() {
          return 'Domain1'
        },
      },
      Query: {
        networks: (root, { ids }) =>
          ids.map((id) => ({ id, domains: [{ id: Number(id) + 3, name: `network${id}.com` }] })),
      },
    },
  })

  const postsSchema = makeExecutableSchema({
    typeDefs: `
      type Network {
        id: ID!
      }
      type Post {
        id: ID!
        network: Network!
      }
      type Query {
        posts(ids: [ID!]!): [Post]!
      }
    `,
    resolvers: {
      Query: {
        posts: (root, { ids }) =>
          ids.map((id) => ({
            id,
            network: { id: Number(id) + 2 },
          })),
      },
    },
  })

  const gatewaySchema = stitchSchemas({
    subschemas: [
      {
        schema: networkSchema,
        merge: {
          Network: {
            selectionSet: '{ id }',
            fieldName: 'networks',
            key: ({ id }) => id,
            argsFromKeys: (ids) => ({ ids }),
          },
        },
      },
      {
        schema: postsSchema,
      },
    ],
  })

  it('should resolve with 2 fragments', async () => {
    const { data } = await graphql(
      gatewaySchema,
      `
        query {
          posts(ids: [55]) {
            network {
              domains {
                name
              }
            }
            ...F2
          }
        }

        fragment F2 on Post {
          network {
            ...F3
          }
        }

        fragment F3 on Network {
          id
          domains {
            id
          }
        }
      `,
    )

    expect(data.posts).toEqual(expectedData)
  })
})

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (2 by maintainers)

Commits related to this issue

Most upvoted comments

Not sure if that’s the cause per se will have time to dig in hopefully later this week