Skip to content

Commit

Permalink
test(graphql): add test on anonymous operation warning (#1693)
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito authored Aug 18, 2023
1 parent 6239a0f commit bb02e57
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 4 deletions.
3 changes: 1 addition & 2 deletions src/core/handlers/GraphQLHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ export class GraphQLHandler extends RequestHandler<
devUtils.warn(`\
Failed to intercept a GraphQL request at "${request.method} ${publicUrl}": anonymous GraphQL operations are not supported.
Consider naming this operation or using "graphql.operation()" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation\
`)
Consider naming this operation or using "graphql.operation()" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation`)
return false
}

Expand Down
15 changes: 13 additions & 2 deletions src/core/utils/request/onUnhandledRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import jsLevenshtein from '@bundled-es-modules/js-levenshtein'
import { RequestHandler, HttpHandler, GraphQLHandler } from '../..'
import {
ParsedGraphQLQuery,
ParsedGraphQLRequest,
parseGraphQLRequest,
} from '../internal/parseGraphQLRequest'
import { getPublicUrlFromRequest } from './getPublicUrlFromRequest'
Expand Down Expand Up @@ -144,6 +145,7 @@ export async function onUnhandledRequest(
const parsedGraphQLQuery = await parseGraphQLRequest(request).catch(
() => null,
)
const publicUrl = getPublicUrlFromRequest(request)

function generateHandlerSuggestion(): string {
/**
Expand All @@ -169,10 +171,19 @@ export async function onUnhandledRequest(
: ''
}

function getGraphQLRequestHeader(
parsedGraphQLRequest: ParsedGraphQLRequest<any>,
): string {
if (!parsedGraphQLRequest?.operationName) {
return `anonymous ${parsedGraphQLRequest?.operationType} (${request.method} ${publicUrl})`
}

return `${parsedGraphQLRequest.operationType} ${parsedGraphQLRequest.operationName} (${request.method} ${publicUrl})`
}

function generateUnhandledRequestMessage(): string {
const publicUrl = getPublicUrlFromRequest(request)
const requestHeader = parsedGraphQLQuery
? `${parsedGraphQLQuery.operationType} ${parsedGraphQLQuery.operationName} (${request.method} ${publicUrl})`
? getGraphQLRequestHeader(parsedGraphQLQuery)
: `${request.method} ${publicUrl}`
const handlerSuggestion = generateHandlerSuggestion()

Expand Down
12 changes: 12 additions & 0 deletions test/browser/graphql-api/anonymous-operation.mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { graphql, HttpResponse } from 'msw'
import { setupWorker } from 'msw/browser'

const worker = setupWorker()
worker.start()

// @ts-ignore
window.msw = {
worker,
graphql,
HttpResponse,
}
210 changes: 210 additions & 0 deletions test/browser/graphql-api/anonymous-operation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { HttpServer } from '@open-draft/test-server/http'
import { test, expect } from '../playwright.extend'
import { gql } from '../../support/graphql'
import { waitFor } from '../../support/waitFor'

declare namespace window {
export const msw: {
worker: import('msw/browser').SetupWorkerApi
graphql: typeof import('msw').graphql
HttpResponse: typeof import('msw').HttpResponse
}
}

const httpServer = new HttpServer((app) => {
app.post('/graphql', (req, res) => {
res.json({
data: {
user: {
id: 'abc-123',
},
},
})
})
})

test.beforeAll(async () => {
await httpServer.listen()
})

test.afterAll(async () => {
await httpServer.close()
})

test('does not warn on anonymous GraphQL operation when no GraphQL handlers are present', async ({
loadExample,
query,
spyOnConsole,
}) => {
await loadExample(require.resolve('./anonymous-operation.mocks.ts'))
const consoleSpy = spyOnConsole()

const endpointUrl = httpServer.http.url('/graphql')
const response = await query(endpointUrl, {
query: gql`
# Intentionally anonymous query.
query {
user {
id
}
}
`,
})

const json = await response.json()

// Must get the original server response.
expect(json).toEqual({
data: {
user: {
id: 'abc-123',
},
},
})

await waitFor(() => {
// Must print a generic unhandled GraphQL request warning.
// This has nothing to do with the operation being anonymous.
expect(consoleSpy.get('warning')).toEqual([
`\
[MSW] Warning: captured a request without a matching request handler:
• anonymous query (POST ${endpointUrl})
If you still wish to intercept this unhandled request, please create a request handler for it.
Read more: https://mswjs.io/docs/getting-started/mocks`,
])
})

// // Must print the warning because anonymous operations cannot be captured
// // using standard "graphql.query()" and "graphql.mutation()" handlers.
// await waitFor(() => {
// expect(consoleSpy.get('warning')).toEqual(
// expect.arrayContaining([
// `[MSW] Failed to intercept a GraphQL request at "POST ${endpointUrl}": anonymous GraphQL operations are not supported.

// Consider naming this operation or using "graphql.operation()" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation`,
// ]),
// )
// })
})

test('warns on handled anonymous GraphQL operation', async ({
loadExample,
query,
spyOnConsole,
page,
}) => {
await loadExample(require.resolve('./anonymous-operation.mocks.ts'))
const consoleSpy = spyOnConsole()

await page.evaluate(() => {
const { worker, graphql, HttpResponse } = window.msw

worker.use(
// This handler will have no effect on the anonymous operation performed.
graphql.query('IrrelevantQuery', () => {
return HttpResponse.json({
data: {
user: {
id: 'mocked-123',
},
},
})
}),
)
})

const endpointUrl = httpServer.http.url('/graphql')
const response = await query(endpointUrl, {
query: gql`
# Intentionally anonymous query.
# It will be handled in the "graphql.operation()" handler above.
query {
user {
id
}
}
`,
})

const json = await response.json()

// Must get the original response because the "graphql.query()"
// handler won't match an anonymous GraphQL operation.
expect(json).toEqual({
data: {
user: {
id: 'abc-123',
},
},
})

// Must print the warning because an anonymous operation has been performed.
await waitFor(() => {
expect(consoleSpy.get('warning')).toEqual(
expect.arrayContaining([
`[MSW] Failed to intercept a GraphQL request at "POST ${endpointUrl}": anonymous GraphQL operations are not supported.
Consider naming this operation or using "graphql.operation()" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation`,
]),
)
})
})

test('does not print a warning on anonymous GraphQL operation handled by "graphql.operation()"', async ({
loadExample,
spyOnConsole,
page,
query,
}) => {
await loadExample(require.resolve('./anonymous-operation.mocks.ts'))
const consoleSpy = spyOnConsole()

await page.evaluate(() => {
const { worker, graphql, HttpResponse } = window.msw

worker.use(
// This handler will match ANY anonymous GraphQL operation.
// It's a good idea to include some matching logic to differentiate
// between those operations. We're omitting it for testing purposes.
graphql.operation(() => {
return HttpResponse.json({
data: {
user: {
id: 'mocked-123',
},
},
})
}),
)
})

const endpointUrl = httpServer.http.url('/graphql')
const response = await query(endpointUrl, {
query: gql`
# Intentionally anonymous query.
# It will be handled in the "graphql.operation()" handler above.
query {
user {
id
}
}
`,
})

const json = await response.json()

// Must get the mocked response.
expect(json).toEqual({
data: {
user: {
id: 'mocked-123',
},
},
})

// Must not print any warnings because a permissive "graphql.operation()"
// handler was used to capture and mock the anonymous GraphQL operation.
expect(consoleSpy.get('warning')).toBeUndefined()
})
108 changes: 108 additions & 0 deletions test/node/graphql-api/anonymous-operations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @jest-environment node
*/
import fetch from 'node-fetch'
import { HttpServer } from '@open-draft/test-server/http'
import { HttpResponse, graphql } from 'msw'
import { setupServer } from 'msw/node'

const httpServer = new HttpServer((app) => {
app.post('/graphql', (req, res) => {
res.json({
data: {
user: { id: 'abc-123' },
},
})
})
})

const server = setupServer(graphql.query('GetUser', () => {}))

beforeAll(async () => {
server.listen()
await httpServer.listen()
jest.spyOn(console, 'warn').mockImplementation(() => {})
})

afterEach(() => {
server.resetHandlers()
jest.resetAllMocks()
})

afterAll(async () => {
jest.restoreAllMocks()
server.close()
await httpServer.close()
})

test('warns on unhandled anonymous GraphQL operations', async () => {
const endpointUrl = httpServer.http.url('/graphql')
const response = await fetch(endpointUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query {
user {
id
}
}
`,
}),
})
const json = await response.json()

// Must receive the original server response.
expect(json).toEqual({
data: { user: { id: 'abc-123' } },
})

// Must print a warning about the anonymous GraphQL operation.
expect(console.warn).toHaveBeenCalledWith(`\
[MSW] Failed to intercept a GraphQL request at "POST ${endpointUrl}": anonymous GraphQL operations are not supported.
Consider naming this operation or using "graphql.operation()" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation`)
})

test('does not print a warning when using anonymous operation with "graphql.operation()"', async () => {
server.use(
graphql.operation(async ({ query, variables }) => {
return HttpResponse.json({
data: {
pets: [{ name: 'Tom' }, { name: 'Jerry' }],
},
})
}),
)

const endpointUrl = httpServer.http.url('/graphql')
const response = await fetch(endpointUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query {
pets {
name
}
}
`,
}),
})
const json = await response.json()

// Must get the mocked response.
expect(json).toEqual({
data: {
pets: [{ name: 'Tom' }, { name: 'Jerry' }],
},
})

// Must print no warnings: operation is handled and doesn't
// have to be named since we're using "graphql.operation()".
expect(console.warn).not.toHaveBeenCalled()
})

0 comments on commit bb02e57

Please sign in to comment.