Querying with apollo-link-state gives the error "Field <name> doesn't exist on type 'Query'" - apollo

I'm totally new to both Apollo and GraphQL. I'm following along with this apollo-link-state-tutorial, and am hitting a stumbling block.
I have set up my link with a currentGame property default.
const stateLink = withClientState({
cache: stateCache,
defaults: {
currentGame: {
__typename: 'currentGame',
teamAScore: 0
}
}
})
I'm using it in my client.
const client = new ApolloClient({
stateCache,
link: stateLink,
...
})
I'm defining a GraphQL query like this:
const getCurrentGame = gql`
query {
currentGame #client {
teamAScore
}
}
`
I am connecting it to my component's props.
export default compose(
graphql(getCurrentGame, {
props: ({ data: { currentGame }}) => ({
currentGame
})
})
)
This generates an error in the console.
[GraphQL error]: Message: Field 'currentGame' doesn't exist on type 'Query', Location: [object Object], Path: undefined
I've gone over my code and haven't been able to spot what is surely a typo or simple mistake. How can I debug this error message, or what does it suggest the problem is?
Update: I have tried adding a resolver as suggested by Tal Z, but am still receiving the same error message.
const stateCache = new InMemoryCache()
const stateLink = withClientState({
cache: stateCache,
resolvers: {
Query: {
currentGame: () => {
return {}
}
}
},
defaults: defaultState
})
For what it's worth, most of the few example repositories I've found have queries for fields that do not have resolvers defined. For example, this queries for todo list items, but the only resolver defined is for a mutation.

Well, I figured it out... this breaks:
import ApolloClient from 'apollo-boost'
This works:
import ApolloClient from 'apollo-client'
I have no idea what the difference is.

Related

Apollo: Using executor function server side removes operation names

I currently have the following code in a codebase using "#apollo/client": "^3.4.17",
const getFrontEndApiSchema = async (authToken: string, hostname: string) => {
const executor = async ({
document,
variables,
}: Parameters<Parameters<typeof introspectSchema>[0]>[0]) => {
const fetchResult = await crossFetch(`${resolveApiUri(hostname)}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authentication-Token': authToken,
},
body: JSON.stringify({ query: print(document), variables }),
})
return fetchResult.json()
}
return makeExecutableSchema({
typeDefs: wrapSchema({
schema: buildClientSchema(await unzipSchema()),
executor,
}),
})
}
export const getSchema = async () => {
const frontEndSchema = await getFrontEndApiSchema()
return stitchSchemas({
subschemas: frontEndSchema ? [frontEndSchema, schema] : [schema],
mergeDirectives: true,
})
}
const apolloClient = createApolloClient(
{
schema,
rootValue: { request: req },
},
getAuthenticationToken(req),
false,
)
Which works and fires off requests. However we noticed during a Telemetry exercise (whereby we are trying to track traces through individual operations in DataDog / NewRelic) that a single operation is effectively being split up into it's constituent queries and sent without it's parent operation name.
It's not so clear to me from reading the docs why I would need to this executor function for graphql queries rather than the standard Apollo link chain (similar to what i'm using for the client side apollo client).
So I removed the unneeded executor function to the following.
makeExecutableSchema({
typeDefs: wrapSchema({
schema: buildClientSchema(await unzipSchema()),
}),
})
This worked in so far as the operations where being made and return a result, however ostensibly it was returning results which matched those which would be returned if unauthenticated, (i.e. no authentication token set in the header).
I've checked my error link and have logged context headers and it appears to have the token.
I've also tried swapping the Schemalink for a normal link with no success.
export default function createApolloClient(
schema: SchemaLink.Options,
token: string,
isTest?: boolean,
) {
const link = from([
authLink(token),
serverErrorLink(),
...(__DEV__ ? [logLink(true)] : []),
new SchemaLink(schema),
])
return new ApolloClient({
link,
cache: createCache(),
ssrMode: true,
queryDeduplication: true,
...(!isTest && {
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
},
query: { fetchPolicy: 'cache-first' },
},
}),
})
}
A typical graphql operation I'm sending
query myOperationName{
user {
id
firstName
}
query2{
id
}
query3{
id
}
}
When I do print(document) in the body of my original executor function I am getting
query2{
id
}
etc
So my question is how server side do I construct the correct Apollo client/ link chain combo such that operations are not stripped of their operation names? And any additional clarity on whether it's necessary to use the SchemaLink at all if my express server is on a different box to the api it talks to would be helpful

Apollo client & Absinthe - difficulty parsing errors

I'm working with the #apollo/client and #absinthe/socket-apollo-link NPM packages in my React app, but I'm having some trouble parsing query and mutation errors received by onError in my implementation of the useQuery and useMutation hooks.
For example, here is the way I've set up a query in my component:
useQuery(OperationLib.agendaQuery, {
fetchPolicy: "network-only",
onCompleted: ({ myData }) => {
setData(myData)
setLoading(false)
},
onError: (error) => {
console.log(error)
}
})
When that onError handler is called, the error object that is returned is logged as:
Error: request: [object Object]
at new ApolloError (app.js:36358)
at app.js:146876
at app.js:145790
at new Promise (<anonymous>)
at Object.error (app.js:145790)
at notifySubscription (app.js:145130)
at onNotify (app.js:145169)
at SubscriptionObserver.error (app.js:145230)
at app.js:58209
at Array.forEach (<anonymous>)
I can break this response into its parts "graphQLErrors", "networkError", "message", "extraInfo", but I'm finding it difficult to get any useful info there. In particular, I'd like to be able to get something out of the message - but in this case, error.message is the string,
request: [object Object]
typeof error.message logs string so yeah I can't really do anything with this.
Maybe I could find something useful under one of the other attributes? Nope, graphQLErrors is an empty array, networkError yields the same output as I got when I logged the initial error above, and extraInfo is undefined.
I dug into the source code and found the method createRequestError - when I added a debug log here to see what the message was, I good some good data - I could see the message that I would think would be available somewhere in the error response:
var createRequestError = function createRequestError(message) {
return new Error("request: ".concat(message));
}.bind(undefined);
What could be causing this issue? Is there something I need to configure in my Apollo/Absinthe initialization? I've set those up like so:
apollo-client.js
import { ApolloClient, InMemoryCache } from "#apollo/client"
import absintheSocketLink from "./absinthe-socket-apollo-link"
export default new ApolloClient({
link: absintheSocketLink,
cache: new InMemoryCache()
})
absinthe-socket-apollo-link.js
import * as AbsintheSocket from "#absinthe/socket"
import { createAbsintheSocketLink } from "#absinthe/socket-apollo-link"
import { Socket as PhoenixSocket } from "phoenix"
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
const getToken = () => JSON.parse(window.localStorage.getItem("token"))
let token = getToken();
const params = {
get jwt() {
if (!token) {
token = getToken();
}
return token;
},
};
export default createAbsintheSocketLink(
AbsintheSocket.create(
new PhoenixSocket(`${protocol}://${WS_API_URL}/graphql`, {
reconnect: true,
params: params
})
)
);
Thanks much for any insight!

testing multiple http request using mocha

I've been trying to solve this issue for days;
create the test for this case using mocha:
app.post('/approval', function(req, response){
request.post('https://git.ecommchannel.com/api/v4/users/' + req.body.content.id + '/' + req.body.content.state + '?private_token=blabla', function (error, resp, body) {
if (resp.statusCode == 201) {
//do something
} else {
response.send("failed"), response.end();
}
});
} else {
response.send("failed"), response.end();
}
});
});
I've tried several ways, using supertest to test the '/approval' and using nock to test the post request to git api. But it always turn "statusCode" is undefined. I think that's because the request to git api in index.js is not inside a certain function(?)
So I can't implement something like this :
https://codeburst.io/testing-mocking-http-requests-with-nock-480e3f164851 or
https://scotch.io/tutorials/nodejs-tests-mocking-http-requests
const nockingGit = () => {
nock('https://git.ecommchannel.com/api/v4/users')
.post('/1/yes', 'private_token=blabla')
.reply(201, { "statusCode": 201 });
};
it('approval', (done) => {
let req = {
content: {
id: 1,
state: 'yes'
},
_id: 1
}
request(_import.app)
.post('/approval')
.send(req)
.expect(200)
.expect('Content-Type', /html/)
.end(function (err, res) {
if (!err) {
nockingGit();
} else {
done(err);
}
});
done();
})
Then I tried to use supertest as promise
it('approve-block-using-promise', () => {
return promise(_import.app)
.post('/approval')
.send(req = {
content: {
id: 1,
state: 'yes'
},
_id: 1
})
.expect(200)
.then(function(res){
return promise(_import.app)
.post("https://git.ecommchannel.com/api/v4/users/")
.send('1/yes', 'private_token=blabla')
.expect(201);
})
})
But it gives error: ECONNEREFUSED: Connection refused. I didn't find any solution to solve the error. Some sources said that it needs done() .. but it gives another error message, 'ensure "done()" is called" >.<
So then I've found another way, using async (https://code-examples.net/en/q/141ce32)
it('should respond to only certain methods', function(done) {
async.series([
function(cb) { request(_import.app).post('/approval')
.send(req = {
content: {
id: 1,
state: 'yes'
},
_id: 1
})
.expect(200, cb); },
function(cb) { request(_import.app).post('/https://git.ecommchannel.com/api/v4/users/').send('1/yes', 'private_token=blabla').expect(201, cb); },
], done);
});
and it gives this error : expected 201 "Created", got 404 "Not Found". Well, if I open https://git.ecommchannel.com/api/v4/users/1/yes?private_token=blabla in the browser it does return 404. But what I expect is I've injected the response to 201 from the unit test; so whatever the actual response is, the statusCode suppose to be 201, right?
But then since it gives that error, is it means the unit test really send the request to the api?
Pls help me to solve this; how to test the first code I shared.
I really new into unit test.
There are a few things wrong with your posted code, I'll try to list them out but I'm also including a full, passing example below.
First off, your call to git.ecommchannel in the controller, it's a POST with no body. While this isn't causing the errors you're seeing and is technically not incorrect, it is odd. So you should double check what the data you should be sending is.
Next, I'm assuming this was a copy/paste issue when you created the question, but the callback for the request in your controller is not valid JS. The brackets don't match up and the send "failed" is there twice.
Your Nock setup had two issues. First the argument to nock should only have origin, none of the path. So /api/v4/users had to be moved into the first argument of the post method. The other issue was with the second argument passed to post that is an optional match of the POST body. As stated above, you aren't currently sending a body so Nock will always fail to match and replace that request. In the example below, the private_token has been moved to match against the query string of the request, as that what was shown as happening.
The calling of nockingGit was happening too late. Nock needs to register the mock before you use Supertest to call your Express app. You have it being called in the end method, by that time it's too late.
The test labeled approve-block-using-promise has an issue with the second call to the app. It's calling post via Supertest on the Express app, however, the first argument to that post method is the path of the request you're making to your app. It has nothing to do with the call to git.ecommchannel. So in that case your Express app should have returned a 404 Not Found.
const express = require('express')
const nock = require('nock')
const request = require('request')
const supertest = require('supertest')
const app = express()
app.use(express.json())
app.post('/approval', function(req, response) {
const url = 'https://git.ecommchannel.com/api/v4/users/' + req.body.content.id + '/' + req.body.content.state
request.post({
url,
qs: {private_token: 'blabla'}
// body: {} // no body?
},
function(error, resp, body) {
if (error) {
response.status(500).json({message: error.message})
} else if (resp.statusCode === 201) {
response.status(200).send("OK")
} else {
response.status(500).send("failed").end();
}
});
});
const nockingGit = () => {
nock('https://git.ecommchannel.com')
.post('/api/v4/users/1/yes')
.query({private_token: 'blabla'})
.reply(201, {"data": "hello world"});
};
it('approval', (done) => {
const reqPayload = {
content: {
id: 1,
state: 'yes'
},
_id: 1
}
nockingGit();
supertest(app)
.post('/approval')
.send(reqPayload)
.expect(200)
.expect('Content-Type', /html/)
.end(function(err) {
done(err);
})
})

How to replace the authorize method in ember-simple-auth

I'm trying to refactor my Ember acceptance tests to not use the deprecated authorize method, as it is throwing a warning:
The `authorize` method should be overridden in your application adapter
I checked the docs, and numberous other sources, but they don't actually explain how to migrate my code. Here's what I've got at the moment:
// projectname/app/pods/login/controller.js (excerpt)
export default Controller.extend({
session: service(),
sessionToken: null,
onSuccess: function(res) {
res = res.response;
this.set('sessionToken', res.session);
if (res.state === "authenticated") {
document.cookie = "token="+res.session+";path=/;";
var authOptions = {
success: true,
data : {
session : res.session,
}
};
this.get('session').authenticate("authenticator:company", authOptions);
}
}
});
And this must be the part that I'm meant to get rid of:
// project/app/adapters/application.js (excerpt)
export default DS.RESTAdapter.extend(DataAdapterMixin, {
authorize(xhr) { // This is deprecated! I should remove it
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
xhr.setRequestHeader('Authorization', "Token " + sessionToken);
}
},
});
And here is my test:
import { test, module } from 'qunit';
import { visit, currentURL, find, click, fillIn } from '#ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { authenticateSession} from 'ember-simple-auth/test-support';
module('moduleName', function(hooks) {
setupApplicationTest(hooks);
test('moduleName', async function(assert) {
// await authenticateSession(this.application); // Never works
// await authenticateSession(); // Never works
await authenticateSession({
authenticator: "authenticator:company"
}); // Works slightly more?
await visit('/my/other/page');
await assert.equal(currentURL(), '/my/other/page');
});
});
REMOVING the authorize method and attempting either of the commented out methods yields:
Error: Assertion Failed: The `authorize` method should be overridden in your application adapter. It should accept a single argument, the request object.
If I use the authenticator block as an arg, then regardless of the presence of the authorize method, I simply get:
actual: >
/login
expected: >
/my/other/page
Which, I assume, is because it did not login.
Leaving the authorize method there, and trying the commented methods yields:
Error: Browser timeout exceeded: 10s
Per the docs you linked above: To replace authorizers in an application, simply get the session data from the session service and inject it where needed.
Since you need the session data in your Authorization header, a possible solution for your use case may look like this:
export default DS.RESTAdapter.extend(DataAdapterMixin, {
headers: computed('session.data.authenticated.session', function() {
const headers = {};
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
headers['Authorization'] = "Token " + sessionToken;
}
return headers;
})
});
This should allow you to dynamically set the Authorization header, without doing so via the authorize method.
Ember Simple Auth, has an excellent community and quickly created a guide on how to upgrade to v3.
The latest version fixes this problem completely - If anyone is having this problem, upgrading to 2.1.1 should allow you to use the new format in your application.js:
headers: computed('session.data.authenticated.session', function() {
let headers = {};
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
headers['Authorization'] = "Token " + sessionToken;
}
return headers;
}),
This problem was only present in 2.1.0.

TDD: Sinon 2.x and trying to test a sync method that uses async

So I've run into another snag, which I'm fighting with... I have a method that is a sync call, and within this method it calls a promise, async, method.
in my app I have the following:
export class App {
constructor(menuService) {
_menuService = menuService;
this.message = "init";
}
configureRouter(config, router) {
console.log('calling configureRouter');
_menuService.getById(1).then(menuItem => {
console.log('within then');
console.log(`configureRouter ${JSON.stringify(menuItem, null, 2)}`);
const collection = menuItem.links.map(convertToRouteCollection);
console.log(`collection ${JSON.stringify(collection, null, 2)}`);
//I think there is an issue with asyn to synch for the test
config.map(collection);
}).catch(err => {
console.error(err);
});
console.log('calling configureRouter assign router');
this.router = router;
}
}
The test I've tried the following within mocha
...
it('should update router config', function () {
const expectedData = {
name: "main menu",
links: [{
url: '/one/two',
name: 'link name',
title: 'link title'
}]
};
const configMapStub = sinon.stub();
const config = {
map: configMapStub
};
const routerMock = sinon.stub();
let app = null;
const actualRouter = null;
let menuService = null;
setTimeout(() => {
menuService = {
getById: sinon.stub().returns(Promise.resolve(expectedData).delay(1))
};
app = new App(menuService);
app.configureRouter(config, routerMock);
}, 10);
clock.tick(30);
expect(app.router).to.equal(routerMock);
expect(menuService.getById.calledWith(1)).to.equal(true);
//console.log(configMapStub.args);
expect(configMapStub.called).to.equal(true);
const linkItem = expectedData.links[0];
const actual = [{
route: ['', 'welcome'],
name: linkItem.name,
moduleId: linkItem.name,
nav: true,
title: linkItem.title
}];
console.log(`actual ${JSON.stringify(actual, null, 2)}`);
expect(config.map.calledWith(actual)).to.equal(true);
});
...
No matter what, I get configMockStub to always get false, while I am getting the menuService.getById.calledWith(1).to.equal(true) to equal true.
The test above was an attempt to try and get 'time' to pass. I've tried it without and have equally failed.
I'm really striking out on ideas on how to test this. Maybe I have the code wrong to reference a promise inside this method.
The only thing I can say I don't have any choice over the configureRouter method. Any guidance is appreciated.
Thanks!
Kelly
Short answer:
I recently discovered I was trying to make configureRouter method be a synchronous call (making it use async await keywords). What I found out was Aurelia does allow that method to be promised. Because of this, the test in question is no longer an issue.
Longer answer:
The other part of this is that I had a slew of babel issues lining up between babelling for mocha, and then babelling for wallaby.js. For some reason these two were not playing well together.
in the test above, another thing was to also change the following:
it('should update router config', function () {
to
it('should update router config', async function () {
I feel like there was another step, but at this time I cannot recall. In either case, knowing that I could use a promise made my world much easier for Aurelia.