Repository: mercurius-js/mercurius
Pull request #986 (2023-04-25 18:00:11)

fix mutation over websocket/subscription
Fixes issue where mutations are not allowed over web sockets (subscriptions).

See: https://github.com/mercurius-js/mercurius/discussions/977



Patch:
diff --git a/index.js b/index.js
index 67e85f3..171b7fa 100644
--- a/index.js
+++ b/index.js
@@ -471,7 +471,7 @@ const plugin = fp(async function (app, opts) {
       document = cached.document
     }
 
-    if (reply && reply.request.raw.method === 'GET') {
+    if (reply && reply.request.raw.method === 'GET' && !reply.request.ws) {
       // let's validate we cannot do mutations here
       const operationAST = getOperationAST(document, operationName)
       if (operationAST.operation !== 'query') {


Test patch:
diff --git a/tap-snapshots/test/routes.js.test.cjs b/tap-snapshots/test/routes.js.test.cjs
index 91a38f5..90cce74 100644
--- a/tap-snapshots/test/routes.js.test.cjs
+++ b/tap-snapshots/test/routes.js.test.cjs
@@ -27,6 +27,17 @@ exports['test/routes.js TAP GET return 200 on resolver error > must match snapsh
 }
 `
 
+exports['test/routes.js TAP HTTP mutation with GET errors > must match snapshot 1'] = `
+{
+  "data": null,
+  "errors": [
+    {
+      "message": "Operation cannot be performed via a GET request"
+    }
+  ]
+}
+`
+
 exports['test/routes.js TAP POST return 200 on resolver error > must match snapshot 1'] = `
 {
   "data": {
@@ -71,17 +82,6 @@ window.GRAPHQL_ENDPOINT = '/app/graphql';
 window.GRAPHIQL_PLUGIN_LIST = []
 `
 
-exports['test/routes.js TAP mutation with GET errors > must match snapshot 1'] = `
-{
-  "data": null,
-  "errors": [
-    {
-      "message": "Operation cannot be performed via a GET request"
-    }
-  ]
-}
-`
-
 exports['test/routes.js TAP server should return 200 on graphql errors (if field can be null) > must match snapshot 1'] = `
 {
   "data": {
diff --git a/test/routes.js b/test/routes.js
index b6366ba..73a6a16 100644
--- a/test/routes.js
+++ b/test/routes.js
@@ -756,7 +756,7 @@ test('mutation with POST application/graphql', async (t) => {
   t.equal(msg, 'hello world')
 })
 
-test('mutation with GET errors', async (t) => {
+test('HTTP mutation with GET errors', async (t) => {
   const app = Fastify()
   const schema = `
     type Mutation {
@@ -789,6 +789,61 @@ test('mutation with GET errors', async (t) => {
   t.matchSnapshot(JSON.stringify(JSON.parse(res.body), null, 2))
 })
 
+test('websocket mutation with GET allowed', async (t) => {
+  const app = Fastify()
+
+  // Simulate fastify-websocket logic
+  app.addHook('onRequest', (request, reply, done) => {
+    request.ws = true
+    done()
+  })
+
+  const schema = `
+    type Mutation {
+      setMessage(message: String): String
+    }
+    type Query {
+      getMessage: String
+    }
+  `
+
+  let msg = 'hello'
+  const resolvers = {
+    setMessage: async ({ message }) => {
+      msg = message
+      return message
+    },
+    async getMessage () {
+      return msg
+    }
+  }
+
+  app.register(GQL, {
+    schema,
+    resolvers
+  })
+
+  const query = querystring.stringify({
+    query: 'mutation { setMessage(message: "hello world") }'
+  })
+
+  const res = await app.inject({
+    headers: {
+      connection: 'upgrade',
+      upgrade: 'websocket'
+    },
+    method: 'GET',
+    url: '/graphql?' + query
+  })
+
+  t.same(JSON.parse(res.body), {
+    data: {
+      setMessage: 'hello world'
+    }
+  })
+  t.equal(msg, 'hello world')
+})
+
 test('POST should support null variables', async (t) => {
   const app = Fastify()
   const schema = `