diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b74041..f626228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,17 @@ and this project adheres to ## [Unreleased] +## [v1.2.2] - 2023-12-21 + +### Fixed + +- fix: support multiple nodes in private queries + ## [v1.2.1] - 2023-12-15 ### Fixed -- feat: support unions when building private queries +- fix: support unions when building private queries ## [v1.2.0] - 2023-11-01 diff --git a/package.json b/package.json index 10859f8..a65a120 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@wayfair/gqmock", - "version": "1.2.1", + "version": "1.2.2", "description": "GQMock - GraphQL Mocking Service", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/__fixtures__/schema.graphql b/src/__fixtures__/schema.graphql index 8058fba..3ec54aa 100644 --- a/src/__fixtures__/schema.graphql +++ b/src/__fixtures__/schema.graphql @@ -105,6 +105,25 @@ type Query { item: Item random: RandomThing items(type: String): [Item] + itemConnection(type: String): ItemConnection +} + +type ItemConnection { + totalCount: Int + edges: [ItemEdge] + pageInfo: PageInfo +} + +type PageInfo { + hasNextPage: Boolean + hasPreviousPage: Boolean + startCursor: String + endCursor: String +} + +type ItemEdge { + node: Item + cursor: String } enum SomeEnum { diff --git a/src/utilities/__tests__/buildPrivateTypeQuery.ts b/src/utilities/__tests__/buildPrivateTypeQuery.ts index 235bc34..80ec87b 100644 --- a/src/utilities/__tests__/buildPrivateTypeQuery.ts +++ b/src/utilities/__tests__/buildPrivateTypeQuery.ts @@ -8,11 +8,14 @@ const schema = fs.readFileSync( ); describe('buildPrivateTypeQuery', function () { - let apolloServerManager; + let apolloServerManager: ApolloServerManager; beforeAll(() => { apolloServerManager = new ApolloServerManager(); - apolloServerManager.createApolloServer(schema, {}); + apolloServerManager.createApolloServer(schema, { + subgraph: false, + fakerConfig: {}, + }); }); it('should build a query for the correct interface inline fragment', () => { @@ -334,4 +337,107 @@ describe('buildPrivateTypeQuery', function () { }) ).toBe(expectedQuery); }); + + it('should select the deeply nested union of all fields in each selection set', () => { + const query = ` + fragment randomFragment_itemConnection on ItemConnection { + totalCount + edges { + node { + id + type + } + } + } + + fragment itemConnection_query on Query { + itemConnection { + edges { + cursor + node { + id + ... on ItemOne { + id + someField1: String + } + } + } + ...randomFragment_itemConnection + } + } + + query itemsQuery { + ...itemConnection_query + }`; + + const expectedItemConnectionQuery = `query gqmock_privateQuery { + gqmock_ItemConnection { + edges { + cursor + node { + id + ... on ItemOne { + id + someField1: String + __typename + } + __typename + } + __typename + } + totalCount + edges { + node { + id + type + __typename + } + __typename + } + __typename + } + __typename +}`; + + expect( + buildPrivateTypeQuery({ + query: apolloServerManager.expandFragments(query), + typeName: 'ItemConnection', + operationName: 'itemsQuery', + rollingKey: 'data.itemConnection', + apolloServerManager, + }) + ).toBe(expectedItemConnectionQuery); + + const expectEdgesQuery = `query gqmock_privateQuery { + gqmock_ItemEdge { + cursor + node { + id + ... on ItemOne { + id + someField1: String + __typename + } + __typename + } + node { + id + type + __typename + } + __typename + } + __typename +}`; + expect( + buildPrivateTypeQuery({ + query: apolloServerManager.expandFragments(query), + typeName: 'ItemEdge', + operationName: 'itemsQuery', + rollingKey: 'data.itemConnection.edges', + apolloServerManager, + }) + ).toBe(expectEdgesQuery); + }); }); diff --git a/src/utilities/buildPrivateTypeQuery.ts b/src/utilities/buildPrivateTypeQuery.ts index e3577cb..78cc3d9 100644 --- a/src/utilities/buildPrivateTypeQuery.ts +++ b/src/utilities/buildPrivateTypeQuery.ts @@ -1,6 +1,7 @@ import { ASTNode, FieldNode, + FragmentSpreadNode, GraphQLSchema, InlineFragmentNode, Kind, @@ -74,11 +75,14 @@ export default function ({ }, ]; - let isFound = false; + const nodesFound: (FieldNode | InlineFragmentNode | FragmentSpreadNode)[] = + []; + while (nodesToVisit.length) { let {_node} = nodesToVisit[0]; const {currentKeys} = nodesToVisit[0]; const key = currentKeys[0]; + if (_node && 'selectionSet' in _node) { for (const selection of _node.selectionSet?.selections || []) { if (keyMatchesFieldNode(selection, key)) { @@ -92,11 +96,9 @@ export default function ({ }); } else if (currentKeys.length === 1) { _node = selection; - isFound = true; + nodesFound.push(_node); } - break; } else if (selection.kind === Kind.INLINE_FRAGMENT) { - let isInlineFragmentSelectionFound = false; for (const inlineFragmentSelection of selection.selectionSet .selections) { if (keyMatchesFieldNode(inlineFragmentSelection, key)) { @@ -111,9 +113,8 @@ export default function ({ }); } else if (currentKeys.length === 1) { _node = inlineFragmentSelection; - isFound = true; + nodesFound.push(_node); } - isInlineFragmentSelectionFound = true; } else if ( inlineFragmentSelection.kind !== Kind.FIELD && 'selectionSet' in inlineFragmentSelection @@ -123,21 +124,23 @@ export default function ({ currentKeys, }); } - - if (isInlineFragmentSelectionFound) { - break; - } - } - if (isInlineFragmentSelectionFound) { - break; } } } } nodesToVisit.shift(); - if (isFound) { - node = _node; + if (nodesFound.length > 0) { + node = nodesFound[0]; + + for (let i = 1; i < nodesFound.length; i++) { + // @ts-expect-error we don't care + node.selectionSet.selections.push( + // @ts-expect-error we don't care + ...nodesFound[i].selectionSet.selections + ); + } + break; } }