Repository: newrelic/newrelic-node-apollo-server-plugin (js)

Issue:
Spread Fragment Support
## Is your feature request related to a problem? Please describe.

When you send a request like
```graphql
fragment UserCard on User {
  name
  image
}
query GetUser {
  user(id: "xxx") {
    ... UserCard
  }
}
```
, you would expect `query/GetUser/user` but in fact `/undefined/UserCard` is actually

## Feature Description
A clear and concise description of the feature you want or need.

If the query definition is first and the fragment second, it will work.

```graphql
# This works
query GetUser {
  user(id: "xxx") {
    ... UserCard
  }
}
fragment UserCard on User {
  name
  image
}
```

## Describe Alternatives
A clear and concise description of any alternative solutions or features you've considered. Are there examples you could link us to?

In this section, the first item of `definitions` is retrieved, but it is correct to use `responseContext.operation`.

https://github.com/newrelic/newrelic-node-apollo-server-plugin/blob/cdefc994786d3932b8dab6c91350b446d0d32d8e/lib/create-plugin.js#L267


## Additional context
Add any other context here.

## Priority
Please help us better understand this feature request by choosing a priority from the following options:
[Nice to Have, Really Want, Must Have, Blocker]


Pull request description:
added support for fragment definitions before query
<!--
Thank you for submitting a Pull Request.

This code is leveraged to monitor critical services. Please consider the following:
* Tests are required.
* Performance matters.
* Features that are specific to just your app are unlikely to make it in.

Please fill out the relevant sections as follows:
* Proposed Release Notes: Bulleted list of recommended release notes for the change(s).
* Links: Any relevant links for the change.
* Details: In-depth description of changes, other technical notes, etc.
-->

## Proposed Release Notes
 * Fixed transaction naming and operation segments when a fragment is defined before query.

## Links
Closes #175

## Details
We operated on the assumption before that the operation definition was always first in a document.  This PR fixes that by finding the operation segment within a document.  It also adds two test cases that prove the fix works.  The one test where the fragment was after query always worked but the one where the fragment was first failed.


Patch:
diff --git a/lib/create-plugin.js b/lib/create-plugin.js
index 901183d..32f17f0 100644
--- a/lib/create-plugin.js
+++ b/lib/create-plugin.js
@@ -263,8 +263,19 @@ function isTopLevelField(fieldInfo) {
   return result
 }
 
+/**
+ * fragments could be defined for a given operation.  This iterates over the definitions
+ * to find the operation definition to avoid issues with naming
+ * see: https://github.com/newrelic/newrelic-node-apollo-server-plugin/issues/175
+ *
+ * @param {Array} definitions
+ */
+function findOperationDefinition(definitions) {
+  return definitions.find((definition) => definition.kind === 'OperationDefinition')
+}
+
 function getDetailsFromDocument(responseContext) {
-  const [definition] = responseContext.document.definitions
+  const definition = findOperationDefinition(responseContext.document.definitions)
 
   const pathAndArgs = getDeepestPathAndQueryArguments(definition)
 


Test patch:
diff --git a/tests/integration/metrics.test.js b/tests/integration/metrics.test.js
index ce8831f..8b27e06 100644
--- a/tests/integration/metrics.test.js
+++ b/tests/integration/metrics.test.js
@@ -202,4 +202,78 @@ function createMetricsTests(t) {
       t.end()
     })
   })
+
+  t.test('named query with fragment, query first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }
+    fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', () => {
+      const operationPart = `query/${expectedName}/${path}`
+
+      t.metrics([
+        `${OPERATION_PREFIX}/${operationPart}`,
+        `${RESOLVE_PREFIX}/library`,
+        `${RESOLVE_PREFIX}/books`,
+        `${RESOLVE_PREFIX}/author`
+      ])
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
+  t.test('named query with fragment, fragment first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }
+    query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', () => {
+      const operationPart = `query/${expectedName}/${path}`
+
+      t.metrics([
+        `${OPERATION_PREFIX}/${operationPart}`,
+        `${RESOLVE_PREFIX}/library`,
+        `${RESOLVE_PREFIX}/books`,
+        `${RESOLVE_PREFIX}/author`
+      ])
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
 }
diff --git a/tests/unit/create-plugin.test.js b/tests/unit/create-plugin.test.js
index 29291da..c360f82 100644
--- a/tests/unit/create-plugin.test.js
+++ b/tests/unit/create-plugin.test.js
@@ -43,6 +43,7 @@ tap.test('createPlugin edge cases', (t) => {
       document: {
         definitions: [
           {
+            kind: 'OperationDefinition',
             selectionSet: {
               selections: []
             }
diff --git a/tests/versioned/apollo-server-fastify/segments.test.js b/tests/versioned/apollo-server-fastify/segments.test.js
index c947e0a..5eaf343 100644
--- a/tests/versioned/apollo-server-fastify/segments.test.js
+++ b/tests/versioned/apollo-server-fastify/segments.test.js
@@ -461,6 +461,138 @@ function createFastifySegmentsTests(t, frameworkName) {
     })
   })
 
+  t.test('named query with fragment, query first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }
+    fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: GQL_MIDDLEWARE_SEGMENT_PATTERN,
+              children: [
+                {
+                  name: `${OPERATION_PREFIX}/${operationPart}`,
+                  children: [
+                    {
+                      name: `${RESOLVE_PREFIX}/library`,
+                      children: [
+                        {
+                          name: 'timers.setTimeout',
+                          children: [
+                            {
+                              name: 'Callback: <anonymous>'
+                            }
+                          ]
+                        }
+                      ]
+                    },
+                    { name: `${RESOLVE_PREFIX}/library.books` },
+                    { name: `${RESOLVE_PREFIX}/library.books.title` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
+  t.test('named query with fragment, fragment first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }
+    query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: GQL_MIDDLEWARE_SEGMENT_PATTERN,
+              children: [
+                {
+                  name: `${OPERATION_PREFIX}/${operationPart}`,
+                  children: [
+                    {
+                      name: `${RESOLVE_PREFIX}/library`,
+                      children: [
+                        {
+                          name: 'timers.setTimeout',
+                          children: [
+                            {
+                              name: 'Callback: <anonymous>'
+                            }
+                          ]
+                        }
+                      ]
+                    },
+                    { name: `${RESOLVE_PREFIX}/library.books` },
+                    { name: `${RESOLVE_PREFIX}/library.books.title` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
   t.test('batch query should include segments for nested queries', (t) => {
     const { helper, serverUrl } = t.context
 
diff --git a/tests/versioned/apollo-server-hapi/segments.test.js b/tests/versioned/apollo-server-hapi/segments.test.js
index 8eaba8c..160e573 100644
--- a/tests/versioned/apollo-server-hapi/segments.test.js
+++ b/tests/versioned/apollo-server-hapi/segments.test.js
@@ -455,6 +455,138 @@ function createHapiSegmentsTests(t, frameworkName) {
     })
   })
 
+  t.test('named query with fragment, query first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }
+    fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: 'Nodejs/Middleware/Hapi/handler//gql',
+              children: [
+                {
+                  name: `${OPERATION_PREFIX}/${operationPart}`,
+                  children: [
+                    {
+                      name: `${RESOLVE_PREFIX}/library`,
+                      children: [
+                        {
+                          name: 'timers.setTimeout',
+                          children: [
+                            {
+                              name: 'Callback: <anonymous>'
+                            }
+                          ]
+                        }
+                      ]
+                    },
+                    { name: `${RESOLVE_PREFIX}/library.books` },
+                    { name: `${RESOLVE_PREFIX}/library.books.title` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
+  t.test('named query with fragment, fragment first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }
+    query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: 'Nodejs/Middleware/Hapi/handler//gql',
+              children: [
+                {
+                  name: `${OPERATION_PREFIX}/${operationPart}`,
+                  children: [
+                    {
+                      name: `${RESOLVE_PREFIX}/library`,
+                      children: [
+                        {
+                          name: 'timers.setTimeout',
+                          children: [
+                            {
+                              name: 'Callback: <anonymous>'
+                            }
+                          ]
+                        }
+                      ]
+                    },
+                    { name: `${RESOLVE_PREFIX}/library.books` },
+                    { name: `${RESOLVE_PREFIX}/library.books.title` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
   t.test('batch query should include segments for nested queries', (t) => {
     const { helper, serverUrl } = t.context
 
diff --git a/tests/versioned/apollo-server-koa/segments.test.js b/tests/versioned/apollo-server-koa/segments.test.js
index 457c8e1..50b644d 100644
--- a/tests/versioned/apollo-server-koa/segments.test.js
+++ b/tests/versioned/apollo-server-koa/segments.test.js
@@ -455,6 +455,138 @@ function createKoaSegmentsTests(t, frameworkName) {
     })
   })
 
+  t.test('named query with fragment, query first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }
+    fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: 'Nodejs/Middleware/Koa',
+              children: [
+                {
+                  name: `${OPERATION_PREFIX}/${operationPart}`,
+                  children: [
+                    {
+                      name: `${RESOLVE_PREFIX}/library`,
+                      children: [
+                        {
+                          name: 'timers.setTimeout',
+                          children: [
+                            {
+                              name: 'Callback: <anonymous>'
+                            }
+                          ]
+                        }
+                      ]
+                    },
+                    { name: `${RESOLVE_PREFIX}/library.books` },
+                    { name: `${RESOLVE_PREFIX}/library.books.title` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
+  t.test('named query with fragment, fragment first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }
+    query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: 'Nodejs/Middleware/Koa',
+              children: [
+                {
+                  name: `${OPERATION_PREFIX}/${operationPart}`,
+                  children: [
+                    {
+                      name: `${RESOLVE_PREFIX}/library`,
+                      children: [
+                        {
+                          name: 'timers.setTimeout',
+                          children: [
+                            {
+                              name: 'Callback: <anonymous>'
+                            }
+                          ]
+                        }
+                      ]
+                    },
+                    { name: `${RESOLVE_PREFIX}/library.books` },
+                    { name: `${RESOLVE_PREFIX}/library.books.title` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author` },
+                    { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
   t.test('batch query should include segments for nested queries', (t) => {
     const { helper, serverUrl } = t.context
 
diff --git a/tests/versioned/apollo-server-lambda/segments.test.js b/tests/versioned/apollo-server-lambda/segments.test.js
index f5434b0..dbffea6 100644
--- a/tests/versioned/apollo-server-lambda/segments.test.js
+++ b/tests/versioned/apollo-server-lambda/segments.test.js
@@ -471,6 +471,134 @@ function createLambdaSegmentsTests(t, frameworkName) {
     })
   })
 
+  t.test('named query with fragment, query first', (t) => {
+    const { helper, patchedHandler, stubContext, modVersion } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }
+    fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: `${OPERATION_PREFIX}/${operationPart}`,
+              children: [
+                {
+                  name: `${RESOLVE_PREFIX}/library`,
+                  children: [
+                    {
+                      name: 'timers.setTimeout',
+                      children: [
+                        {
+                          name: 'Callback: <anonymous>'
+                        }
+                      ]
+                    }
+                  ]
+                },
+                { name: `${RESOLVE_PREFIX}/library.books` },
+                { name: `${RESOLVE_PREFIX}/library.books.title` },
+                { name: `${RESOLVE_PREFIX}/library.books.author` },
+                { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQueryAssertResult({
+      handler: patchedHandler,
+      query,
+      context: stubContext,
+      t,
+      modVersion
+    })
+  })
+
+  t.test('named query with fragment, fragment first', (t) => {
+    const { helper, patchedHandler, stubContext, modVersion } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }
+    query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: `${OPERATION_PREFIX}/${operationPart}`,
+              children: [
+                {
+                  name: `${RESOLVE_PREFIX}/library`,
+                  children: [
+                    {
+                      name: 'timers.setTimeout',
+                      children: [
+                        {
+                          name: 'Callback: <anonymous>'
+                        }
+                      ]
+                    }
+                  ]
+                },
+                { name: `${RESOLVE_PREFIX}/library.books` },
+                { name: `${RESOLVE_PREFIX}/library.books.title` },
+                { name: `${RESOLVE_PREFIX}/library.books.author` },
+                { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQueryAssertResult({
+      handler: patchedHandler,
+      query,
+      context: stubContext,
+      t,
+      modVersion
+    })
+  })
+
   t.test('batch query should include segments for nested queries', (t) => {
     const { helper, patchedHandler, stubContext, modVersion } = t.context
 
diff --git a/tests/versioned/express-segments-default-tests.js b/tests/versioned/express-segments-default-tests.js
index 08e7978..fb8f602 100644
--- a/tests/versioned/express-segments-default-tests.js
+++ b/tests/versioned/express-segments-default-tests.js
@@ -547,6 +547,144 @@ function createSegmentsTests(t, frameworkName) {
     })
   })
 
+  t.test('named query with fragment, query first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }
+    fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: 'Expressjs/Router: /',
+              children: [
+                {
+                  name: 'Nodejs/Middleware/Expressjs/<anonymous>',
+                  children: [
+                    {
+                      name: `${OPERATION_PREFIX}/${operationPart}`,
+                      children: [
+                        {
+                          name: `${RESOLVE_PREFIX}/library`,
+                          children: [
+                            {
+                              name: 'timers.setTimeout',
+                              children: [
+                                {
+                                  name: 'Callback: <anonymous>'
+                                }
+                              ]
+                            }
+                          ]
+                        },
+                        { name: `${RESOLVE_PREFIX}/library.books` },
+                        { name: `${RESOLVE_PREFIX}/library.books.author` }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
+  t.test('named query with fragment, fragment first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }
+    query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: 'Expressjs/Router: /',
+              children: [
+                {
+                  name: 'Nodejs/Middleware/Expressjs/<anonymous>',
+                  children: [
+                    {
+                      name: `${OPERATION_PREFIX}/${operationPart}`,
+                      children: [
+                        {
+                          name: `${RESOLVE_PREFIX}/library`,
+                          children: [
+                            {
+                              name: 'timers.setTimeout',
+                              children: [
+                                {
+                                  name: 'Callback: <anonymous>'
+                                }
+                              ]
+                            }
+                          ]
+                        },
+                        { name: `${RESOLVE_PREFIX}/library.books` },
+                        { name: `${RESOLVE_PREFIX}/library.books.author` }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
   t.test('batch query should include segments for nested queries', (t) => {
     const { helper, serverUrl } = t.context
 
diff --git a/tests/versioned/express-segments-scalar-tests.js b/tests/versioned/express-segments-scalar-tests.js
index 3697936..52a8a34 100644
--- a/tests/versioned/express-segments-scalar-tests.js
+++ b/tests/versioned/express-segments-scalar-tests.js
@@ -498,6 +498,148 @@ function createSegmentsTests(t, frameworkName) {
     })
   })
 
+  t.test('named query with fragment, query first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }
+    fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: 'Expressjs/Router: /',
+              children: [
+                {
+                  name: 'Nodejs/Middleware/Expressjs/<anonymous>',
+                  children: [
+                    {
+                      name: `${OPERATION_PREFIX}/${operationPart}`,
+                      children: [
+                        {
+                          name: `${RESOLVE_PREFIX}/library`,
+                          children: [
+                            {
+                              name: 'timers.setTimeout',
+                              children: [
+                                {
+                                  name: 'Callback: <anonymous>'
+                                }
+                              ]
+                            }
+                          ]
+                        },
+                        { name: `${RESOLVE_PREFIX}/library.books` },
+                        { name: `${RESOLVE_PREFIX}/library.books.title` },
+                        { name: `${RESOLVE_PREFIX}/library.books.author` },
+                        { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
+  t.test('named query with fragment, fragment first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }
+    query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      const operationPart = `query/${expectedName}/${path}`
+      const expectedSegments = [
+        {
+          name: `${TRANSACTION_PREFIX}//${operationPart}`,
+          children: [
+            {
+              name: 'Expressjs/Router: /',
+              children: [
+                {
+                  name: 'Nodejs/Middleware/Expressjs/<anonymous>',
+                  children: [
+                    {
+                      name: `${OPERATION_PREFIX}/${operationPart}`,
+                      children: [
+                        {
+                          name: `${RESOLVE_PREFIX}/library`,
+                          children: [
+                            {
+                              name: 'timers.setTimeout',
+                              children: [
+                                {
+                                  name: 'Callback: <anonymous>'
+                                }
+                              ]
+                            }
+                          ]
+                        },
+                        { name: `${RESOLVE_PREFIX}/library.books` },
+                        { name: `${RESOLVE_PREFIX}/library.books.title` },
+                        { name: `${RESOLVE_PREFIX}/library.books.author` },
+                        { name: `${RESOLVE_PREFIX}/library.books.author.name` }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            }
+          ]
+        }
+      ]
+
+      t.segments(transaction.trace.root, expectedSegments)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
   t.test('batch query should include segments for nested queries', (t) => {
     const { helper, serverUrl } = t.context
 
diff --git a/tests/versioned/transaction-naming-tests.js b/tests/versioned/transaction-naming-tests.js
index f950733..e897440 100644
--- a/tests/versioned/transaction-naming-tests.js
+++ b/tests/versioned/transaction-naming-tests.js
@@ -420,6 +420,66 @@ function createTransactionTests(t, frameworkName) {
     })
   })
 
+  t.test('named query with fragment, query first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }
+    fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      t.equal(transaction.name, `${EXPECTED_PREFIX}//query/${expectedName}/${path}`)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
+  t.test('named query with fragment, fragment first', (t) => {
+    const { helper, serverUrl } = t.context
+
+    const expectedName = 'GetBookForLibrary'
+    const query = `fragment LibraryBook on Book {
+      title
+      author {
+        name
+      }
+    }
+    query ${expectedName} {
+      library(branch: "downtown") {
+        books {
+          ... LibraryBook
+        }
+      }
+    }`
+
+    const path = 'library.books.LibraryBook'
+
+    helper.agent.on('transactionFinished', (transaction) => {
+      t.equal(transaction.name, `${EXPECTED_PREFIX}//query/${expectedName}/${path}`)
+    })
+
+    executeQuery(serverUrl, query, (err) => {
+      t.error(err)
+      t.end()
+    })
+  })
+
   // there will be no document/AST nor resolved operation
   t.test('if the query cannot be parsed, should be named /*', (t) => {
     const { helper, serverUrl } = t.context