chore: don't emit test files not needed (#4)
parent
9da7fcd238
commit
d2fcbb005d
|
@ -1,573 +0,0 @@
|
|||
import {
|
||||
buildSchema,
|
||||
GraphQLSchema,
|
||||
GraphQLString,
|
||||
GraphQLObjectType,
|
||||
GraphQLNonNull,
|
||||
} from 'https://cdn.skypack.dev/graphql@16.5.0?dts'
|
||||
import { Hono } from 'https://raw.githubusercontent.com/honojs/hono/v2.0.2/deno_dist/mod.ts'
|
||||
import { errorMessages, graphqlServer } from './index.ts'
|
||||
|
||||
// Do not show `console.error` messages
|
||||
jest.spyOn(console, 'error').mockImplementation()
|
||||
|
||||
describe('errorMessages', () => {
|
||||
const messages = errorMessages(['message a', 'message b'])
|
||||
expect(messages).toEqual({
|
||||
errors: [
|
||||
{
|
||||
message: 'message a',
|
||||
},
|
||||
{
|
||||
message: 'message b',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
describe('GraphQL Middleware - Simple way', () => {
|
||||
// Construct a schema, using GraphQL schema language
|
||||
const schema = buildSchema(`
|
||||
type Query {
|
||||
hello: String
|
||||
}
|
||||
`)
|
||||
|
||||
// The root provides a resolver function for each API endpoint
|
||||
const rootValue = {
|
||||
hello: () => 'Hello world!',
|
||||
}
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlServer({
|
||||
schema,
|
||||
rootValue,
|
||||
})
|
||||
)
|
||||
|
||||
app.all('*', (c) => {
|
||||
c.header('foo', 'bar')
|
||||
return c.text('fallback')
|
||||
})
|
||||
|
||||
it('Should return GraphQL response', async () => {
|
||||
const query = 'query { hello }'
|
||||
const body = {
|
||||
query: query,
|
||||
}
|
||||
|
||||
const res = await app.request('http://localhost/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"hello":"Hello world!"}}')
|
||||
expect(res.headers.get('foo')).toBeNull() // GraphQL Server middleware should be Handler
|
||||
})
|
||||
})
|
||||
|
||||
const QueryRootType = new GraphQLObjectType({
|
||||
name: 'QueryRoot',
|
||||
fields: {
|
||||
test: {
|
||||
type: GraphQLString,
|
||||
args: {
|
||||
who: { type: GraphQLString },
|
||||
},
|
||||
resolve: (_root, args: { who?: string }) => 'Hello ' + (args.who ?? 'World'),
|
||||
},
|
||||
thrower: {
|
||||
type: GraphQLString,
|
||||
resolve() {
|
||||
throw new Error('Throws!')
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const TestSchema = new GraphQLSchema({
|
||||
query: QueryRootType,
|
||||
mutation: new GraphQLObjectType({
|
||||
name: 'MutationRoot',
|
||||
fields: {
|
||||
writeTest: {
|
||||
type: QueryRootType,
|
||||
resolve: () => ({}),
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
const urlString = (query?: Record<string, string>): string => {
|
||||
const base = 'http://localhost/graphql'
|
||||
if (!query) return base
|
||||
const queryString = new URLSearchParams(query).toString()
|
||||
return `${base}?${queryString}`
|
||||
}
|
||||
|
||||
describe('GraphQL Middleware - GET functionality', () => {
|
||||
const app = new Hono()
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlServer({
|
||||
schema: TestSchema,
|
||||
})
|
||||
)
|
||||
|
||||
it('Allows GET with variable values', async () => {
|
||||
const query = {
|
||||
query: 'query helloWho($who: String){ test(who: $who) }',
|
||||
variables: JSON.stringify({ who: 'Dolly' }),
|
||||
}
|
||||
|
||||
const res = await app.request(urlString(query), {
|
||||
method: 'GET',
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"test":"Hello Dolly"}}')
|
||||
})
|
||||
|
||||
it('Allows GET with operation name', async () => {
|
||||
const query = {
|
||||
query: `
|
||||
query helloYou { test(who: "You"), ...shared }
|
||||
query helloWorld { test(who: "World"), ...shared }
|
||||
query helloDolly { test(who: "Dolly"), ...shared }
|
||||
fragment shared on QueryRoot {
|
||||
shared: test(who: "Everyone")
|
||||
}
|
||||
`,
|
||||
operationName: 'helloWorld',
|
||||
}
|
||||
|
||||
const res = await app.request(urlString(query), {
|
||||
method: 'GET',
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
data: {
|
||||
test: 'Hello World',
|
||||
shared: 'Hello Everyone',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('Reports validation errors', async () => {
|
||||
const query = { query: '{ test, unknownOne, unknownTwo }' }
|
||||
const res = await app.request(urlString(query), {
|
||||
method: 'GET',
|
||||
})
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('Errors when missing operation name', async () => {
|
||||
const query = {
|
||||
query: `
|
||||
query TestQuery { test }
|
||||
mutation TestMutation { writeTest { test } }
|
||||
`,
|
||||
}
|
||||
const res = await app.request(urlString(query), {
|
||||
method: 'GET',
|
||||
})
|
||||
expect(res.status).toBe(500)
|
||||
})
|
||||
|
||||
it('Errors when sending a mutation via GET', async () => {
|
||||
const query = {
|
||||
query: 'mutation TestMutation { writeTest { test } }',
|
||||
}
|
||||
const res = await app.request(urlString(query), {
|
||||
method: 'GET',
|
||||
})
|
||||
expect(res.status).toBe(405)
|
||||
})
|
||||
|
||||
it('Errors when selecting a mutation within a GET', async () => {
|
||||
const query = {
|
||||
operationName: 'TestMutation',
|
||||
query: `
|
||||
query TestQuery { test }
|
||||
mutation TestMutation { writeTest { test } }
|
||||
`,
|
||||
}
|
||||
const res = await app.request(urlString(query), {
|
||||
method: 'GET',
|
||||
})
|
||||
expect(res.status).toBe(405)
|
||||
})
|
||||
|
||||
it('Allows a mutation to exist within a GET', async () => {
|
||||
const query = {
|
||||
operationName: 'TestQuery',
|
||||
query: `
|
||||
mutation TestMutation { writeTest { test } }
|
||||
query TestQuery { test }
|
||||
`,
|
||||
}
|
||||
const res = await app.request(urlString(query), {
|
||||
method: 'GET',
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
data: {
|
||||
test: 'Hello World',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GraphQL Middleware - POST functionality', () => {
|
||||
const app = new Hono()
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlServer({
|
||||
schema: TestSchema,
|
||||
})
|
||||
)
|
||||
|
||||
it('Allows POST with JSON encoding', async () => {
|
||||
const query = { query: '{test}' }
|
||||
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(query),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"test":"Hello World"}}')
|
||||
})
|
||||
|
||||
it('Allows sending a mutation via POST', async () => {
|
||||
const query = { query: 'mutation TestMutation { writeTest { test } }' }
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(query),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"writeTest":{"test":"Hello World"}}}')
|
||||
})
|
||||
|
||||
it('Allows POST with url encoding', async () => {
|
||||
const query = {
|
||||
query: '{test}',
|
||||
}
|
||||
const res = await app.request(urlString(query), {
|
||||
method: 'POST',
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"test":"Hello World"}}')
|
||||
})
|
||||
|
||||
it('Supports POST JSON query with string variables', async () => {
|
||||
const query = {
|
||||
query: 'query helloWho($who: String){ test(who: $who) }',
|
||||
variables: JSON.stringify({ who: 'Dolly' }),
|
||||
}
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(query),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"test":"Hello Dolly"}}')
|
||||
})
|
||||
|
||||
it('Supports POST url encoded query with string variables', async () => {
|
||||
const searchParams = new URLSearchParams({
|
||||
query: 'query helloWho($who: String){ test(who: $who) }',
|
||||
variables: JSON.stringify({ who: 'Dolly' }),
|
||||
})
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: searchParams.toString(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"test":"Hello Dolly"}}')
|
||||
})
|
||||
|
||||
it('Supports POST JSON query with GET variable values', async () => {
|
||||
const variables = {
|
||||
variables: JSON.stringify({ who: 'Dolly' }),
|
||||
}
|
||||
const query = { query: 'query helloWho($who: String){ test(who: $who) }' }
|
||||
const res = await app.request(urlString(variables), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(query),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"test":"Hello Dolly"}}')
|
||||
})
|
||||
|
||||
it('Supports POST url encoded query with GET variable values', async () => {
|
||||
const searchParams = new URLSearchParams({
|
||||
query: 'query helloWho($who: String){ test(who: $who) }',
|
||||
})
|
||||
const variables = {
|
||||
variables: JSON.stringify({ who: 'Dolly' }),
|
||||
}
|
||||
const res = await app.request(urlString(variables), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: searchParams.toString(),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"test":"Hello Dolly"}}')
|
||||
})
|
||||
|
||||
it('Supports POST raw text query with GET variable values', async () => {
|
||||
const variables = {
|
||||
variables: JSON.stringify({ who: 'Dolly' }),
|
||||
}
|
||||
const res = await app.request(urlString(variables), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/graphql',
|
||||
},
|
||||
body: 'query helloWho($who: String){ test(who: $who) }',
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('{"data":{"test":"Hello Dolly"}}')
|
||||
})
|
||||
|
||||
it('Allows POST with operation name', async () => {
|
||||
const query = {
|
||||
query: `
|
||||
query helloYou { test(who: "You"), ...shared }
|
||||
query helloWorld { test(who: "World"), ...shared }
|
||||
query helloDolly { test(who: "Dolly"), ...shared }
|
||||
fragment shared on QueryRoot {
|
||||
shared: test(who: "Everyone")
|
||||
}
|
||||
`,
|
||||
operationName: 'helloWorld',
|
||||
}
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(query),
|
||||
})
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
data: {
|
||||
test: 'Hello World',
|
||||
shared: 'Hello Everyone',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('Allows POST with GET operation name', async () => {
|
||||
const res = await app.request(
|
||||
urlString({
|
||||
operationName: 'helloWorld',
|
||||
}),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/graphql',
|
||||
},
|
||||
body: `
|
||||
query helloYou { test(who: "You"), ...shared }
|
||||
query helloWorld { test(who: "World"), ...shared }
|
||||
query helloDolly { test(who: "Dolly"), ...shared }
|
||||
fragment shared on QueryRoot {
|
||||
shared: test(who: "Everyone")
|
||||
}
|
||||
`,
|
||||
}
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
data: {
|
||||
test: 'Hello World',
|
||||
shared: 'Hello Everyone',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Pretty printing', () => {
|
||||
it('Supports pretty printing', async () => {
|
||||
const app = new Hono()
|
||||
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlServer({
|
||||
schema: TestSchema,
|
||||
pretty: true,
|
||||
})
|
||||
)
|
||||
|
||||
const res = await app.request(urlString({ query: '{test}' }))
|
||||
expect(await res.text()).toEqual(
|
||||
[
|
||||
// Pretty printed JSON
|
||||
'{',
|
||||
' "data": {',
|
||||
' "test": "Hello World"',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Error handling functionality', () => {
|
||||
const app = new Hono()
|
||||
app.use(
|
||||
'/graphql',
|
||||
graphqlServer({
|
||||
schema: TestSchema,
|
||||
})
|
||||
)
|
||||
|
||||
it('Handles query errors from non-null top field errors', async () => {
|
||||
const schema = new GraphQLSchema({
|
||||
query: new GraphQLObjectType({
|
||||
name: 'Query',
|
||||
fields: {
|
||||
test: {
|
||||
type: new GraphQLNonNull(GraphQLString),
|
||||
resolve() {
|
||||
throw new Error('Throws!')
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
const app = new Hono()
|
||||
|
||||
app.use('/graphql', graphqlServer({ schema }))
|
||||
|
||||
const res = await app.request(
|
||||
urlString({
|
||||
query: '{ test }',
|
||||
})
|
||||
)
|
||||
|
||||
expect(res.status).toBe(500)
|
||||
})
|
||||
|
||||
it('Handles syntax errors caught by GraphQL', async () => {
|
||||
const res = await app.request(
|
||||
urlString({
|
||||
query: 'syntax_error',
|
||||
}),
|
||||
{
|
||||
method: 'GET',
|
||||
}
|
||||
)
|
||||
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('Handles errors caused by a lack of query', async () => {
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('Handles invalid JSON bodies', async () => {
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify([]),
|
||||
})
|
||||
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('Handles incomplete JSON bodies', async () => {
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: '{"query":',
|
||||
})
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('Handles plain POST text', async () => {
|
||||
const res = await app.request(
|
||||
urlString({
|
||||
variables: JSON.stringify({ who: 'Dolly' }),
|
||||
}),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: 'query helloWho($who: String){ test(who: $who) }',
|
||||
}
|
||||
)
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('Handles poorly formed variables', async () => {
|
||||
const res = await app.request(
|
||||
urlString({
|
||||
variables: 'who:You',
|
||||
query: 'query helloWho($who: String){ test(who: $who) }',
|
||||
}),
|
||||
{
|
||||
method: 'GET',
|
||||
}
|
||||
)
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('Handles invalid variables', async () => {
|
||||
const res = await app.request(urlString(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: 'query helloWho($who: String){ test(who: $who) }',
|
||||
variables: { who: ['John', 'Jane'] },
|
||||
}),
|
||||
})
|
||||
expect(res.status).toBe(500)
|
||||
})
|
||||
|
||||
it('Handles unsupported HTTP methods', async () => {
|
||||
const res = await app.request(urlString({ query: '{test}' }), {
|
||||
method: 'PUT',
|
||||
})
|
||||
|
||||
expect(res.status).toBe(405)
|
||||
expect(res.headers.get('allow')).toBe('GET, POST')
|
||||
expect(await res.json()).toEqual({
|
||||
errors: [{ message: 'GraphQL only supports GET and POST requests.' }],
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,61 +0,0 @@
|
|||
import { parseBody } from './parse-body.ts'
|
||||
|
||||
describe('parseBody', () => {
|
||||
it('Should return a blank JSON object', async () => {
|
||||
const req = new Request('http://localhost/graphql')
|
||||
const res = await parseBody(req)
|
||||
expect(res).toEqual({})
|
||||
})
|
||||
|
||||
it('With Content-Type: application/graphql', async () => {
|
||||
const req = new Request('http://localhost/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/graphql',
|
||||
},
|
||||
body: 'query { hello }',
|
||||
})
|
||||
const res = await parseBody(req)
|
||||
expect(res).toEqual({ query: 'query { hello }' })
|
||||
})
|
||||
|
||||
it('With Content-Type: application/json', async () => {
|
||||
const variables = JSON.stringify({ who: 'Dolly' })
|
||||
const req = new Request('http://localhost/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: 'query { hello }',
|
||||
variables: variables,
|
||||
}),
|
||||
})
|
||||
const res = await parseBody(req)
|
||||
expect(res).toEqual({
|
||||
query: 'query { hello }',
|
||||
variables: variables,
|
||||
})
|
||||
})
|
||||
|
||||
it('With Content-Type: application/x-www-form-urlencoded', async () => {
|
||||
const variables = JSON.stringify({ who: 'Dolly' })
|
||||
|
||||
const searchParams = new URLSearchParams()
|
||||
searchParams.append('query', 'query { hello }')
|
||||
searchParams.append('variables', variables)
|
||||
const req = new Request('http://localhost/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: searchParams.toString(),
|
||||
})
|
||||
const res = await parseBody(req)
|
||||
|
||||
expect(res).toEqual({
|
||||
query: 'query { hello }',
|
||||
variables: variables,
|
||||
})
|
||||
})
|
||||
})
|
|
@ -5,15 +5,17 @@
|
|||
"author": "Minghe Huang <h.minghe@gmail.com>",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": ["dist"],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test:deno": "deno test deno_test",
|
||||
"test:bun": "bun wiptest bun_test/index.test.ts",
|
||||
"test:all": "yarn test && yarn test:deno && yarn test:bun",
|
||||
"denoify": "rimraf deno_dist && denoify",
|
||||
"build": "rimraf dist && tsc",
|
||||
"denoify": "rimraf deno_dist && denoify && rimraf 'deno_dist/**/*.test.ts'",
|
||||
"build": "rimraf dist && tsc --project tsconfig.build.json",
|
||||
"release": "npm publish"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/deno/**/*.ts",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue