Send POST request to apollo server with "operationName" and "variables" - graphql

I follow this doc https://www.apollographql.com/docs/apollo-server/requests.html#postRequests and try to send a POST request to apollo server.
test code:
it('should get author correctly', () => {
const body = {
query: `
query {
getAuthor($id: Int!) {
name
}
}
`,
// operationName: 'query author',
variables: {
id: 1
}
};
return rp.post(body).then(res => {
expect(res.data.getAuthor.name).to.equal('lin');
});
});
rp.js:
const requestPromise = require('request-promise');
const { PORT } = require('./config');
const GRAPHQL_ENDPOINT = `http://localhost:${PORT}/graphql`;
function rp(options) {
function post(body) {
return requestPromise(GRAPHQL_ENDPOINT, {
method: 'POST',
body,
json: true,
headers: {
'Content-Type': 'application/json'
}
});
}
return {
post
};
}
module.exports = rp;
When I run npm test command, got an error:
graphql test suites
Go to http://localhost:3000/graphiql to run queries!
✓ t0
1) should get author correctly
1 passing (79ms)
1 failing
1) graphql test suites
should get author correctly:
StatusCodeError: 400 - {"errors":[{"message":"Syntax Error: Expected Name, found $","locations":[{"line":3,"column":21}]}]}
at new StatusCodeError (node_modules/request-promise-core/lib/errors.js:32:15)
at Request.plumbing.callback (node_modules/request-promise-core/lib/plumbing.js:104:33)
at Request.RP$callback [as _callback] (node_modules/request-promise-core/lib/plumbing.js:46:31)
at Request.self.callback (node_modules/request/request.js:186:22)
at Request.<anonymous> (node_modules/request/request.js:1163:10)
at IncomingMessage.<anonymous> (node_modules/request/request.js:1085:12)
at endReadableNT (_stream_readable.js:1106:12)
at process._tickCallback (internal/process/next_tick.js:178:19)

The format of your query is not valid. There's actually two things wrong. One, variables are defined at the very top of the operation (next to the query or mutation keyword). And, two, if you define a variable, you have to use it. So your query should look more like this:
query($id: Int!) {
getAuthor(id: $id) {
name
}
}

Related

Getting test coverage of GraphQL resolve function

I'm using mocha chai and supertest to test a new graphql endpoint set up on our Node/Express server.
I have all the tests running and passing accordingly but when I run the following script:
"test-coverage": "nyc mocha tests/ --recursive",
it is not counting the tests for users resolve function in the code coverage.
My graphql query endpoint looks as shown below:
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: () => ({
users: {
type: new GraphQLList(UserType),
args: {
searchByName: { type: GraphQLString },
queryNearbyUsers: { type: GraphQLBoolean },
skip: { type: GraphQLInt },
limit: { type: GraphQLInt }
},
async resolve(parent, args, req) {
const { searchByName, queryNearbyUsers, skip = 0, limit = 20 } = args
// No search criteria was specified so just return an error
if(!searchByName && !queryNearbyUsers)
throw new Error('NO_SEARCH_CRITERIA_SPECIFIED')
...
}
},
...
})
An example of one of my tests:
it('should throw an error (NO_SEARCH_CRITERIA_SPECIFIED) when no params supplied', function(done) {
request.post('spikeql')
.set({ Authorization: `Bearer ${token}`})
.send({ query: '{ users { _id firstName lastName displayName rpr distanceAway avatar { styles { thumb_square }}}}'})
.expect(200) // TODO: Setup GraphQL to match approproate HTTP res codes
.end((err, res) => {
if(err) return done(err)
let errors = res.body.errors
expect(errors).to.be.an('array')
// Check to make sure error was sent properly
expect(errors[0]).to.have.property('message', 'NO_SEARCH_CRITERIA_SPECIFIED')
expect(errors[0].message).to.be.a('string')
done()
})
})
I perform 3 other tests with different inputs for the GET_USERS query. All of them pass. It just doesn't get tracked in coverage report.
New to graphql and unit/integration testing so any help is appreciated. Can supply additional info if needed.

How to pass a request header to fastify plugin options at register

I can access the request header in a get or post call
fastify.get('/route1',(req,res,next)=>{
console.log(req.headers.Authorization)
...
}
I am looking for a way to pass it to a plugin register call, specifically fastify-graphql
const { graphqlFastify } = require("fastify-graphql");
fastify.register(graphqlFastify,
{
prefix: "/graphql",
graphql: {
schema: schema,
rootValue: resolvers,
context:{auth:req.headers.Authorization} <-----
}
},
err => {
if (err) {
console.log(err);
throw err;
}
}
);
Is there a way to write a wrapper or any ideas?
I think you can't do that.
If read the code you will find that:
fastify-graphql is calling runHttpQuery
runHttpQuery is calling context without passing the request
So I think that you should check the auth-client with a standard JWT and then use another token server-side.
The final solution could be to check Apollo 2.0 and open the issue on fastify-graphql.
Here a little snippet that explain the idea:
const fastify = require('fastify')({ logger: true })
const { makeExecutableSchema } = require('graphql-tools')
const { graphiqlFastify, graphqlFastify } = require('fastify-graphql');
const typeDefs = `
type Query {
demo: String,
hello: String
}
`
const resolvers = {
Query: {
demo: (parent, args, context) => {
console.log({ args, context });
return 'demo'
},
hello: () => 'world'
}
}
const schema = makeExecutableSchema({ typeDefs, resolvers })
fastify.register(graphqlFastify, {
prefix: '/gr',
graphql: {
schema,
context: function () {
return { serverAuth: 'TOKEN' }
},
},
});
fastify.listen(3000)
// curl -X POST 'http://localhost:3000/gr' -H 'Content-Type: application/json' -d '{"query": "{ demo }"}'
For anyone who need to access request headers in graphql context, try
graphql-fastify
Usage
Create /graphql endpoint like following
const graphqlFastify = require("graphql-fastify");
fastify.register(graphqlFastify, {
prefix: "/graphql",
graphQLOptions
});
graphQLOptions
graphQLOptions can be provided as an object or a function that returns graphql options
graphQLOptions: {
schema: schema,
rootValue: resolver
contextValue?: context
}
If it is a function, you have access to http request and response. This allows you to do authentication and pass authentication scopes to graphql context. See the following pseudo-code
const graphQLOptions = function (request,reply) {
const auth = decodeBearerToken(request.headers.Authorization);
// auth may contain userId, scope permissions
return {
schema: schema,
rootValue: resolver,
contextValue: {auth}
}
});
This way, context.auth is accessible to resolver functions allowing you to check user's scope/permissions before proceeding.

Writing Structural Expectations with Jest

I am looking to write what I am calling structural expectations with Jest and I am not sure how this could be accomplished.
To start I have a graphql server and a database with a number of todo items. I currently have the following test that just returns true if the content within the database is the same as the response that I have written. I want to check instead that the response looks like an object with data that could be anything.
Here is the code that I have:
describe('To Do:', () => {
it('add todo items', async () => {
const response = await axios.post('http://localhost:5000/graphql', {
query: `
query {
getTodoItems {
message
id
dateCreated
dateDue
}
}
`
});
const { data } = response;
expect(data).toMatchObject({
data: {
getTodoItems: [
{
message: "message",
id: "5bd9aec8406e0a2170e04494",
dateCreated: "1540992712052",
dateDue: "1111111111"
},
{
message: "message",
id: "5bd9aeec60a9b2579882a308",
dateCreated: "1540992748028",
dateDue: "1111111111"
},
{
message: "new message",
id: "5bd9af15922b27236c91837c",
dateCreated: "1540992789836",
dateDue: "1111111111"
}
]
}
})
});
});
Now I want to write something like this, where there can be any number of returned items and they follow similar structuring:
describe('To Do:', () => {
it('add todo items', async () => {
const response = await axios.post('http://localhost:5000/graphql', {
query: `
query {
getTodoItems {
message
id
dateCreated
dateDue
}
}
`
});
const { data } = response;
expect(data).toMatchObject({
data: {
getTodoItems: [
{
message: expect.any(String),
id: expect.any(String),
dateCreated: expect.any(String),
dateDue: expect.any(String)
} // There needs to be unlimited additional items here
]
}
})
});
});
I have been looking throught the docs and I even tried nesting the expectations but I can't seem to get the desired response. Let me know what yo think or if I can clarify in any way.
I figured out the best way for me to do it. I would love to hear better answers. I wrote a function within the scope of the test as a jest.fn and then I called it. In that function, I made custom checks to parse the data that was received in the response. From there I added an expect function with the 'toHaveReturnedWith' method to see what the response of my custom function was and finishing out the test.
const addTodoResponse = jest.fn(() => {
// Custom parsing and check here
// Returns true or false
});
addTodoResponse();
expect(addTodoResponse).toHaveReturnedWith(true);
Are there better ways to do this out there?

How can I catch RelayObervable Unhandled error when graphql errors are returned?

My backend is returning the following response:
const errorMessage = {
errors: [
{
message: 'User is logged out',
},
],
data: null,
};
return res.status(200).json(errorMessage);
My react-native app using relay is returning the following error:
RelayObservable: Unhandled Error Error:
Relay request for `HomeQuery` failed by the following reasons:
This error shows up when I try to query the backend and it returns the above 'errorMessage' graphql errors array. I have no way of catching this error in my relay network layer BEFORE it throws the redscreen. My Relay Environment looks like this:
const network = new RelayNetworkLayer([
urlMiddleware({
url: () => Promise.resolve(`${config.endpoint}backend`),
headers: async () => authenticateHeaders(fetchHeaders),
}),
retryMiddleware({ fetchTimeout: 1000 }),
next => req => {
if (!req.uploadbles) {
req.Accept = 'application/json';
req['Content-Type'] = 'application/json';
}
return next(req);
},
next => async req => {
const res = await next(req);
if (isLoggedOut(res)) {
// Using react-navigation, route to the login screen
dispatch(routeToLogin())
// I also tried returning `dispatch(routeToLogin())` and `{ data: null, errors: res.payload.errors }` without luck
}
return res;
}
]);
Is there a way I can navigate using dispatch(routeToLogin()) without seeing the redscreen error when graphql errors are returned?
Edit 1
For react-relay-network-modern: You can add the noThrow option like so: new RelayNetworkLayer(middlewares, { noThrow: true });
The noThrow option doesn't exist for react-relay-network-layer (relay classic), is there anything I can do to work around it?
Please try noThrow option:
const network = new RelayNetworkLayer(middlewares, { noThrow: true });

Apollo response from mutation is undefined

I use Apollo-client 2.3.5 to to add some data and then update the local cache. The mutation works but the return from the mutation is undefined in the Apollo client, but the response in the network request is correct.
So I have two querys, one for fetching all bookings and one for adding a booking.
const addBookingGQL = gql`
mutation createBooking($ref: String, $title: String, $description: String, $status: String!){
createBooking(ref: $ref, title: $title, description: $description, status: $status){
id
ref
title
description
status
}
}
`;
const GET_BOOKINGS = gql`
query {
bookings {
id
ref
title
status
description
}
}
`;
I then have a Apollo mutation wrapper where I use the update prop. addBooking should be populated with the result of the mutation, but unfortunately it is undefined.
<Mutation
mutation={addBookingGQL}
update={(cache, { data: { addBooking } }) => {
const { bookings } = cache.readQuery({ query: GET_BOOKINGS });
console.log("cache read query bookings: ", cache);
cache.writeQuery({
query: GET_BOOKINGS,
data: { bookings: bookings.concat([addBooking]) }
});
}}
>
{(addBooking, { loading, error }) => (
<div>
<Button
onClick={() => {
addBooking({
variables: {
ref: this.state.ref,
title: this.state.title,
description: this.state.description,
status: "BOOK_BOX",
}
});
this.handleClose();
}}
color="primary">
Create
</Button>
{loading && <p>Loading...</p>}
{error && <p>Error :( Please try again</p>}
</div>
)}
</Mutation>
This results in following error in the console:
errorHandling.js:7 Error: Error writing result to store for query:
{
bookings {
id
ref
title
status
description
__typename
}
}
Cannot read property '__typename' of undefined
at Object.defaultDataIdFromObject [as dataIdFromObject] (inMemoryCache.js:33)
at writeToStore.js:347
at Array.map (<anonymous>)
at processArrayValue (writeToStore.js:337)
at writeFieldToStore (writeToStore.js:244)
at writeToStore.js:120
at Array.forEach (<anonymous>)
at writeSelectionSetToStore (writeToStore.js:113)
at writeResultToStore (writeToStore.js:91)
at InMemoryCache.webpackJsonp../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.write (inMemoryCache.js:96)
I tried running the mutation in the Graphiql dev tool receiving the expected response:
{
"data": {
"createBooking": {
"id": "bd954579-144b-41b4-9c76-5e3c176fe66a",
"ref": "test",
"title": "test",
"description": "test",
"status": "test"
}
}
}
Last I looked at the actual response from the graphql server:
{
"data":{
"createBooking":{
"id":"6f5ed8df-1c4c-4039-ae59-6a8c0f86a0f6",
"ref":"test",
"title":"test",
"description":"test",
"status":"BOOK_BOX",
"__typename":"BookingType"
}
}
}
If i use the Apollo dev tool for chrome i can see that the new data is actually appended to the cache, which confuses me.
Have you checked out this apollo issue comment?
The suggestion is to create an apollo-link that parses the operation variables and omits keys containing __typename:
function createOmitTypenameLink() {
return new ApolloLink((operation, forward) => {
if (operation.variables) {
operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename)
}
return forward(operation)
})
}
function omitTypename(key, value) {
return key === '__typename' ? undefined : value
}

Resources