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!
Related
Expectation: when wrong login credentials are provided, "non_field_errors: Unable to log in with provided credentials" is returned, such as below (screenshot from a tutorial which I'm following verbatim)
Reality: instead I'm getting the error below.
This gets printed to the console:
POST http://127.0.0.1:8000/api/v1/token/login 400 (Bad Request)
Interestingly I get this same error when I try to create users with passwords that are too short. I'm not having any issues with axios or the server when I provide the right credentials for log in, or use passwords of sufficient length when creating new users. When trying to catch errors such as these that I'm failing to get the expected result.
My code for catching the error is the same as in the tutorial:
methods: {
submitForm() {
axios.defaults.headers.common['Authorization'] = ''
localStorage.removeItem('token')
const formData = {
username: this.username,
password: this.password
}
axios
.post('/api/v1/token/login', formData)
.then(response => {
const token = response.data.auth_token
this.$store.commit('setToken', token)
axios.defaults.headers.common['Authorization'] = 'Token ' + token
localStorage.setItem('token', token)
this.$router.push('/dashboard/my-account')
})
.catch(error => {
if (error.response) {
for (const property in error.response) {
this.errors.push(`${property}: ${error.response.data[property]}`)
}
} else if (error.message) {
this.errors.push('Something went wrong. Please try again!')
}
})
}
}
Is there something in the server settings that I should change?
I'm using Django, rest framework, and djoser.
Don't know if you're using a custom exception handler in Django rest framework but it looks like the issue could be from the way you're handling the error in your frontend application.
You can handle the errors like this.
methods: {
submitForm() {
axios.defaults.headers.common['Authorization'] = ''
localStorage.removeItem('token')
const formData = {
username: this.username,
password: this.password
}
axios
.post('/api/v1/token/login', formData)
.then(response => {
const token = response.data.auth_token
this.$store.commit('setToken', token)
axios.defaults.headers.common['Authorization'] = 'Token ' + token
localStorage.setItem('token', token)
this.$router.push('/dashboard/my-account')
})
.catch(error => {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
})
}
Can be found here
I have been using Jest to do my unit tests with node.
I am used to mocking the first level of the modules/functions, but on the challenge to mock Twilio, I am not having so much luck.
I am using the twilio method: client.messages.create, so here I have the twilio client from the constructor require('twilio')(account sid, token), and the first layer is from the object/method(?) messages, and last the third level create, and it's this last guy that I am trying to mock.
I was trying something like this:
jest.mock('twilio', () => {
const mKnex = {
messages: jest.fn(),
};
return jest.fn(mKnex);
});
However, I am not able to mock the client resolved value, where I get client.message.create is not a function.
If I try the above mock plus this client.messages.create.mockReturnValueOnce({sid: "FOO", status: "foo"); I get that cannot read the property create from undefined(messages).
Any tip, post, docs that could give me some luck on this?
Thanks
The solution for this is:
Create a file for Twilio client:
// sms.client.ts
import { Twilio } from 'twilio';
const smsClient = new Twilio(
'TWILIO-ACCOUNT-SID',
'TWILIO-TOKEN'
);
export { smsClient };
Then, your service file should look like this:
// sms.service.ts
import { smsClient } from './sms.client';
class SMSService {
async sendMessage(phoneNumber: string, message: string): Promise<string> {
const result = await smsClient.messages.create({
from: '(555) 555-5555',
to: phoneNumber,
body: message,
});
if (result.status === 'failed') {
throw new Error(`Failed to send sms message. Error Code: ${result.errorCode} / Error Message: ${result.errorMessage}`);
}
return result.sid;
}
}
export const smsService = new SMSService();
Last but not least, your spec/test file needs to mock the client file. E.g.
// sms.service.spec.ts
import { MessageInstance, MessageListInstance } from 'twilio/lib/rest/api/v2010/account/message';
import { smsClient } from './sms.client';
import { smsService } from './sms.service';
// mock the client file
jest.mock('./sms.client');
// fixture
const smsMessageResultMock: Partial<MessageInstance> = {
status: 'sent',
sid: 'AC-lorem-ipsum',
errorCode: undefined,
errorMessage: undefined,
};
describe('SMS Service', () => {
beforeEach(() => {
// stubs
const message: Partial<MessageListInstance> = {
create: jest.fn().mockResolvedValue({ ...smsMessageResultMock })
};
smsClient['messages'] = message as MessageListInstance;
});
it('Should throw error if response message fails', async () => {
// stubs
const smsMessageMock = {
...smsMessageResultMock,
status: 'failed',
errorCode: 123,
errorMessage: 'lorem-ipsum'
};
smsClient.messages.create = jest.fn().mockResolvedValue({ ...smsMessageMock });
await expect(
smsService.sendMessage('(555) 555-5555', 'lorem-ipsum')
).rejects.toThrowError(`Failed to send sms message. Error Code: ${smsMessageMock.errorCode} / Error Message: ${smsMessageMock.errorMessage}`);
});
describe('Send Message', () => {
it('Should succeed when posting the message', async () => {
const resultPromise = smsService.sendMessage('(555) 555-5555', 'lorem-ipsum');
await expect(resultPromise).resolves.not.toThrowError(Error);
expect(await resultPromise).toEqual(smsMessageResultMock.sid);
});
});
});
I've found a solution. It's still calling the endpoint, but for each twilio account, you get a test SID and Token, I used this one so it does not send a sms when testing with this:
if (process.env.NODE_ENV !== 'test') {
client = require('twilio')(accountSid, authToken)
listener = app.listen(3010, function(){
console.log('Ready on port %d', listener.address().port)
})
}else{
client = require('twilio')(testSid, testToken)
}
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);
})
})
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.
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.