Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions packages/wobe-graphql-apollo/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,181 @@ import getPort from 'get-port'
import { WobeGraphqlApolloPlugin } from '.'

describe('Wobe GraphQL Apollo plugin', () => {
it('should reject GET requests by default', async () => {
const port = await getPort()

const wobe = new Wobe()

await wobe.usePlugin(
await WobeGraphqlApolloPlugin({
options: {
typeDefs: `#graphql
type Query {
hello: String
}
`,
resolvers: {
Query: {
hello: () => 'Hello from Apollo!',
},
},
},
}),
)

wobe.listen(port)

const res = await fetch(
`http://127.0.0.1:${port}/graphql?query=${encodeURIComponent(`
query { hello }
`)}`,
)

expect(res.status).toBeGreaterThanOrEqual(400)

wobe.stop()
})

it('should allow GET requests when explicitly enabled', async () => {
const port = await getPort()

const wobe = new Wobe()

await wobe.usePlugin(
await WobeGraphqlApolloPlugin({
allowGetRequests: true,
options: {
typeDefs: `#graphql
type Query {
hello: String
}
`,
resolvers: {
Query: {
hello: () => 'Hello from Apollo!',
},
},
},
}),
)

wobe.listen(port)

const res = await fetch(
`http://127.0.0.1:${port}/graphql?query=${encodeURIComponent(`
query { hello }
`)}`,
)

expect(res.status).toBe(200)
expect(await res.json()).toEqual({
data: { hello: 'Hello from Apollo!' },
})

wobe.stop()
})

it('should disable introspection and landing page in production by default', async () => {
const port = await getPort()

const wobe = new Wobe()

await wobe.usePlugin(
await WobeGraphqlApolloPlugin({
isProduction: true,
allowGetRequests: true,
options: {
typeDefs: `#graphql
type Query {
hello: String
}
`,
resolvers: {
Query: {
hello: () => 'Hello from Apollo!',
},
},
},
}),
)

wobe.listen(port)

const resLanding = await fetch(`http://127.0.0.1:${port}/graphql`)
expect(resLanding.status).toBeGreaterThanOrEqual(400)

const res = await fetch(`http://127.0.0.1:${port}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query IntrospectionQuery {
__schema { queryType { name } }
}
`,
}),
})

const body = await res.json()

expect(res.status).toBe(400)
expect(body.errors?.[0]?.message?.toLowerCase()).toContain(
'introspection',
)

wobe.stop()
})

it('should allow introspection when explicitly enabled in production', async () => {
const port = await getPort()

const wobe = new Wobe()

await wobe.usePlugin(
await WobeGraphqlApolloPlugin({
isProduction: true,
allowIntrospection: true,
options: {
typeDefs: `#graphql
type Query {
hello: String
}
`,
resolvers: {
Query: {
hello: () => 'Hello from Apollo!',
},
},
},
}),
)

wobe.listen(port)

const res = await fetch(`http://127.0.0.1:${port}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query IntrospectionQuery {
__schema { queryType { name } }
}
`,
}),
})

const body = await res.json()

expect(res.status).toBe(200)
expect(body.data?.__schema?.queryType?.name).toBeDefined()

wobe.stop()
})

it('should have custom wobe context in graphql context with record', async () => {
const port = await getPort()

Expand Down
53 changes: 31 additions & 22 deletions packages/wobe-graphql-apollo/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { ApolloServer, type ApolloServerOptions } from '@apollo/server'
import {
ApolloServerPluginLandingPageLocalDefault,
ApolloServerPluginLandingPageProductionDefault,
} from '@apollo/server/plugin/landingPage/default'
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default'
import type {
Wobe,
MaybePromise,
Expand All @@ -22,6 +19,9 @@ export interface GraphQLApolloPluginOptions {
resolve: () => Promise<Response>,
res: WobeResponse,
) => Promise<Response>
allowGetRequests?: boolean
isProduction?: boolean
allowIntrospection?: boolean
}

export const WobeGraphqlApolloPlugin = async ({
Expand All @@ -30,23 +30,30 @@ export const WobeGraphqlApolloPlugin = async ({
graphqlMiddleware,
context: apolloContext,
isProduction,
allowGetRequests = false,
allowIntrospection,
}: {
options: ApolloServerOptions<any>
graphqlEndpoint?: string
context?: GraphQLApolloContext
isProduction?: boolean
} & GraphQLApolloPluginOptions): Promise<WobePlugin> => {
const introspection =
options.introspection ??
(allowIntrospection === true ? true : isProduction ? false : true)

const server = new ApolloServer({
...options,
introspection,
plugins: [
...(options?.plugins || []),
isProduction
? ApolloServerPluginLandingPageProductionDefault({
footer: false,
})
: ApolloServerPluginLandingPageLocalDefault({
footer: false,
}),
...(isProduction
? []
: [
ApolloServerPluginLandingPageLocalDefault({
footer: false,
}),
]),
],
})

Expand Down Expand Up @@ -108,20 +115,22 @@ export const WobeGraphqlApolloPlugin = async ({
}, context.res)
}

wobe.get(graphqlEndpoint, async (context) => {
const response = await getResponse(context)
if (allowGetRequests) {
wobe.get(graphqlEndpoint, async (context) => {
const response = await getResponse(context)

for (const [key, value] of context.res.headers.entries()) {
if (key === 'set-cookie') {
response.headers.append('set-cookie', value)
continue
}
for (const [key, value] of context.res.headers.entries()) {
if (key === 'set-cookie') {
response.headers.append('set-cookie', value)
continue
}

response.headers.set(key, value)
}
response.headers.set(key, value)
}

return response
})
return response
})
}

wobe.post(graphqlEndpoint, async (context) => {
const response = await getResponse(context)
Expand Down
Loading