Successful GraphQL Mutation keeps Returning Undefined - react-native

I am sending a mutation over from a React Native Frontend to a NodeJs / GraphQL Backend. The mutation request goes through, and a mutation occurs, but back on the frontend, the value of that mutation is undefined when it should instead be returning an ID. My mutation looks like this...
export default {
Mutation: {
driverCreateCollisionAccident: async (_, {
accidentId,
specific_pictures,
contact_info,
collision_report,
extra_info
}, context) => {
const driver = await checkDriverAuth(context)
const foundAccident = await db.accident.findUnique({
where: {
id: accidentId
}
})
if (!foundAccident) {
throw new Error("Accident does not exist")
}
await handleDriverAccidentOwnership(driver.id, accidentId)
console.log("right before create collision mutation")
try {
return await db.collisionAccident.create({
data: {
specific_pictures: specific_pictures,
contact_info: contact_info,
collision_report: collision_report,
extra_info: extra_info,
accident: {
connect: {
id: accidentId
}
},
accidentId: accidentId
}
}).then( (resolved) => {
console.log(resolved)
return resolved
})
} catch (error) {
console.log(error)
throw new Error(error)
}
}
}
}
The most important part of that code is
.then( (resolved) => {
console.log(resolved)
return resolved
})
As the console.log returns exactly what it is supposed to, an object that looks like this...
id: '81b7cc7c-53d7-43d7-bb14-1438ef53a227',
specific_pictures: { 'Pic One': 'Test url' },
contact_info: {
address: 'Have Picture',
lastname: 'Have Picture',
firstname: 'Have Picture',
phone_number: '123456789',
insurance_provider: 'Have Picture',
driver_license_number: 'Have Picture',
insurance_policy_number: 'Have Picture'
},
extra_info: 'null',
collision_report: {
towed: true,
legal_fault: 'I caused the accident',
fire_or_explode: true
},
accidentId: '0b5fd832-9540-475e-9b34-ece6dfdc58df'
}
But for some reason, when I try to log the results of this mutation on the frontend, all I get is undefined, but no errors occur, and I still get the backend's console.logs to hit properly so the mutation itself is working. My front end code looks like this...
const handleSubmit = () => {
handleMutation().then( (resolved) => {
console.log(resolved)
})
}
const handleMutation = async () => {
await driverCreateCollisionAccident({
variables: {
accidentId: collisionData.accidentId,
specific_pictures: collisionData.specific_pictures,
contact_info: collisionData.contact_info,
collision_report: collisionData.collision_report,
extra_info: collisionData.extra_info,
}
})
}
I don't even need the full object returned, I JUST need an ID. Does anyone see what could be going wrong here?

Maybe you can try something like this
const result = await db.collisionAccident.create({
data: {
specific_pictures: specific_pictures,
contact_info: contact_info,
collision_report: collision_report,
extra_info: extra_info,
accident: {
connect: {
id: accidentId
}
},
accidentId: accidentId
}
})
return result
instead of
return await db.collisionAccident.create({
data: {
specific_pictures: specific_pictures,
contact_info: contact_info,
collision_report: collision_report,
extra_info: extra_info,
accident: {
connect: {
id: accidentId
}
},
accidentId: accidentId
}
}).then( (resolved) => {
console.log(resolved)
return resolved
})

Related

Sentry for VueJS 2 - captureException Error - change name?

is there any way of change the 'Error' to something like 'Setup Error' followed by the description of something that went wrong?
async allBays() {
try {
let response = await mapService.getBaysList();
let bayList = response.data.data;
if (bayList) {
this.bays = bayList.map((list) => {
return {
value: { id: list.id, name: list.state },
text: list.state,
};
});
}
} catch (e) {
Sentry.captureException(new Error("Could not load bays"), {
tags: {
section: "Farm Setup",
},
});
}
},
This looks like the following:
Thanks for any help, documentation is ridiculously long and I can't find the exact answer
You can extend the JavaScript Error class.
class SetupError extends Error {
constructor(message) {
super(message)
this.name = 'Setup Error'
}
}
async allBays() {
try {
let response = await mapService.getBaysList();
let bayList = response.data.data;
if (bayList) {
this.bays = bayList.map((list) => {
return {
value: { id: list.id, name: list.state },
text: list.state,
};
});
}
} catch (e) {
Sentry.captureException(new SetupError("Could not load bays"), {
tags: {
section: "Farm Setup",
},
});
}
},

Jest / Unit test failing on the function which returns a asyncronous method

I'm using TypeORM.
It works fine at the local with real DB connection.
But when I run the test with Jest, the test gets failed.
I mocked the TypeORM method I needed, and expected all these methods to be called.
Test is failing on expect(typeorm.getRepository(Covid).save).toHaveBeenCalled();
it is not being called.
I cannot understand why this test code is not calling.
Does anyone know the solution?
Here is the code.
describe('getCoronaData', () => {
it('should get a Covid Status data and return it', async () => {
typeorm.getRepository = jest.fn().mockReturnValue({
findOne: jest.fn().mockResolvedValue(null),
create: jest.fn(),
save: jest.fn(),
});
await getCoronaData();
expect(typeorm.getRepository).toHaveBeenCalledWith(Covid);
expect(typeorm.getRepository(Covid).findOne).toHaveBeenCalled();
expect(typeorm.getRepository(Covid).save).toHaveBeenCalled(); // this expect result is making an error
// Expected number of calls: >= 1
// Received number of calls: 0
});
});
export const getCoronaData = async () => {
try {
// Just a fake data
const covidStatus = [
{
city: 'city',
totalCases: '100',
increasedCases: '100',
date: 'time',
},
];
const covidRepository = getRepository(Covid);
covidStatus.forEach(async (status) => {
const exist = await covidRepository.findOne({
where: { city: status.city },
});
if (!exist) {
// expecting save() method at this part to be called, but it fails
return await covidRepository.save(
covidRepository.create({
city: status.city,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: status.time,
})
);
} else {
return await covidRepository.save([
{
id: exist.id,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: status.time,
},
]);
}
});
} catch (err) {
console.error(err);
return;
}
};
Self-answer
I found the problem why the test failing.
forEach() method should not be used with async/await.
so I just changed the code using Promise.all() and map()
await Promise.all(
covidStatus.map(async (status) => {
const exist = await covidRepository.findOne({
where: { city: status.city },
});
if (!exist) {
const newData = covidRepository.create({
city: status.city,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: $standardTime,
});
return covidRepository.save(newData);
} else {
return covidRepository.save([
{
id: exist.id,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: $standardTime,
},
]);
}
})
);
Or
for (const status of covidStatus) {
const exist = await covidRepository.findOne({
where: { city: status.city },
});
if (!exist) {
const newData = covidRepository.create({
city: status.city,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: $standardTime,
});
return covidRepository.save(newData);
} else {
return covidRepository.save([
{
id: exist.id,
totalCases: status.totalCases,
increasedCases: status.increasedCases,
date: $standardTime,
},
]);
}
}
I found this solution from Here.
using forEach and async/await, behaves different for node and Jest

RN & Redux: Unable to put an object into state in the reducer

I'm getting data from the backend and want to store it in the state.
The server sends some user data (id, mail, name,.. ) as an object. This object I want to store as an attribute of the state object.
Here is the code,
starting with auth.js (action-file):
//loadUser(), which gets called via useEffect in App.js:
export const loadUser = () => async dispatch => {
try {
const token = await AsyncStorage.getItem('token')
await setToken(token)
const res = await axios.get(`${SERVER_IP}/api/auth`)
console.log('res.data', res.data) // see result of clog below
dispatch({
type: USER_LOADED,
payload: res.data
})
} catch (err) {
dispatch({
type: AUTH_ERROR
})
}
}
this is the result of console.log('res.data', res.data), which gets passed on to the reducer. This is also what I want:
Object {
"__v": 0,
"_id": "5d2b422322cdf413d4246566",
"avatar": "//www.gravatar.com/avatar/e14a77efcd408a95332f403e0db40b95?s=200&r=pg&d=mm",
"date": "2019-07-14T14:54:27.265Z",
"email": "awnwen#asdw.de",
"name": "awena",
}
This is what the reducer looks like:
// reducer:
import {
USER_LOADED,
AUTH_ERROR,
} from '../actions/types'
import {getToken, setToken, removeToken} from '../actions/auth'
const initialState = {
token: getToken(),
isAuthenticated: null,
loading: true,
user: null
}
export default async function (state = initialState, action) {
const { type, payload } = action
switch (type) {
case USER_LOADED:
return {
...state,
user: payload,
isAuthenticated: true,
loading: false
}
case AUTH_ERROR:
await removeToken()
return {
...state,
isAuthenticated: false,
token: null,
user: null,
loading: false
}
default:
return state
}
}
In case they are needed: token functions from auth.js actions-file (first file posted)
export const getToken = async () => {
try {
return await AsyncStorage.getItem('token')
} catch (error) {
console.log("Tokenerror: getToken() -" + error);
return null
}
}
export const setToken = async (token) => {
try {
await AsyncStorage.setItem('token', token);
setAuthToken(token)
} catch (error) {
console.log("Tokenerror: setToken() - " + error);
return null
}
}
export const removeToken = async () => {
try {
return await AsyncStorage.removeItem('token')
} catch (error) {
console.log("Tokenerror: removeToken() - " + error);
return null
}
}
I expect the auth state to be like:
{
auth: {
token: 'randomstringofnumbersandletters',
isAuthenticated: true,
loading: false,
user: {
name: 'john',
email: 'jdoe#gmail.com',
.....
}
}
}
This is what RND shows in auth state:
{
auth: {
_40: 0,
_65: 0,
_55: null,
_72: null
}
}
This is my first issue I am posting on stackoverflow, it says I should add more text because the text to code ratio is too bad.. Please feel free to ask questions if you need more information to help me resloving this issue.
Many thanks in advance, guys ;)
EDIT1:
As requested I console logged the payload in the reducer after the USER_LOADED case.
switch (type) {
case USER_LOADED:
console.log('payload: ', payload)
return {
...state,
user: payload,
isAuthenticated: true,
loading: false
}
It is returning the same object:
payload: Object {
"__v": 0,
"_id": "5d2b422322cdf413d4246566",
"avatar": "//www.gravatar.com/avatar/e14a77efcd408a95332f403e0db40b95?s=200&r=pg&d=mm",
"date": "2019-07-14T14:54:27.265Z",
"email": "awnwen#asdw.de",
"name": "awena",
}
The problem could be solved by removing "async" from
export default async function (state = initialState, action) {...}
in the reducer.
Now I get the user in state.
But the token in the auth state is not a string but an object/Promise.
This is what I get from logging the token property in the auth state object:
state.token Promise {
"_40": 0,
"_55": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNWQyYjc2MzRmN2U1MjcwOGUwNjNmZThmIn0sImlhdCI6MTU2MzEyOTM5NiwiZXhwIjoxODYzMTI5Mzk2fQ.YQayK3HBXIFl6aFSfkojTF8WrZkxXCGMf36Z0rXvRjY",
"_65": 1,
"_72": null,
}
I am not very experienced with promises. Is there a way to only store the value of the token as a string in my state?
I could build and ugly workaround and extract the value of the key with _55.
But on the one hand I don't understand what those keys in the promise are AND I could imagine the keys to change from one client to another.

Two custom methods/endpoints using loopBack, one works, the other gives a 401

I created two custom endpoints with Loopback.
Account.deleteAllHearingTests = function (req, callback) {
console.log('here comes the req to delete all hearing tests', req);
Account.findById(req.accessToken.userId)
.then(account => {
if (!account) {
throw new Error('cannot find user');
}
return app.models.HearingTest.updateAll({ accountId: account.id }, { isDeleted: new Date() });
})
.then(() => {
callback(null);
})
.catch(error => {
callback(error);
})
}
Account.remoteMethod(
'deleteAllHearingTests', {
http: {
path: '/clearHearingTests',
verb: 'post'
},
accepts: [
{ arg: 'req', type: 'object', http: { source: 'req' } }
],
returns: {}
}
);
the second one looks like this.
Account.deleteSingleHearingTest = function (req, callback) {
// console.log('accounts.js: deleteSingleHearingTest: are we being reached????', req)
Account.findById(req.accessToken.userId)
.then(account => {
if (!account) {
throw new Error('Cannot find user');
}
console.log('account.js: deleteSingleHearingTest: req.body.hearingTestId N: ', req.body.hearingTestId);
return app.models.HearingTest.updateAll({ accountId: account.id, id: req.body.hearingTestId }, { isDeleted: new Date() });
})
.then(() => {
callback(null);
})
.catch(error => {
callback(error);
});
}
Account.remoteMethod(
'deleteSingleHearingTest', {
http: {
path: '/deleteSingleHearingTest',
verb: 'post'
},
accepts: [
{ arg: 'req', type: 'object', description: 'removes a single hearing test', http: { source: 'req' } }
],
description: 'this is the end point for a single delete',
returns: {}
}
);
};
The first custom method returns a 401 status response when I make the initial fetch. The second returns a 200.
Inside my actions file the first method is called with something that looks like this:
export function deleteAllHearingTests() {
return (dispatch, getState) => {
let state = getState();
if (!state.user || !state.user.accessToken || !state.user.accessToken.id || !state.user.accessToken.userId) {
console.debug('deleteAllHearingTests', state.user);
// TODO: ERROR
return;
}
fetch(SERVERCONFIG.BASEURL + '/api/Accounts/clearHearingTests?access_token=' + state.user.accessToken.id, {
method: 'POST',
headers: SERVERCONFIG.HEADERS
})
.then(response => {
console.log('here is your response', response);
if (response.status !== 200) {
throw new Error('Something is wrong');
}
return response.json()
})
the second method is called with
export const deleteSingleHearingTest = (hearingTestNumber) => {
return (dispatch, getState) => {
let state = getState();
if (!state.user || !state.user.accessToken || !state.user.accessToken.id || !state.user.accessToken.userId) {
console.debug('writeTestResult', state.user);
// TODO: ERROR
return;
}
console.log('single delete ', SERVERCONFIG.BASEURL + '/api/Accounts/deleteSingleHearingTest?access_token=' + state.user.accessToken.id)
fetch(SERVERCONFIG.BASEURL + '/api/Accounts/deleteSingleHearingTest?access_token=' + state.user.accessToken.id, {
method: 'POST',
headers: SERVERCONFIG.HEADERS,
body: JSON.stringify({ "hearingTestId": hearingTestNumber })
})
.then(response => {
console.log('getting response from initial fetch inside deleteSingleReqport', response);
They are nearly identical, however, one works..the other fails. What are some possible causes for the 401?
Did you try to call those methods with external tool like a postman, so you would exactly know if you don't miss access_token or something else? Also, when you compare code from one function and another, you can see that you are colling the updateAll with different arguments. It's hard to say without original code, but maybe the issue is there? Compare below:
return app.models.HearingTest.updateAll(
{ accountId: account.id },
{ isDeleted: new Date() });
return app.models.HearingTest.updateAll(
{ accountId: account.id, id: req.body.hearingTestId },
{ isDeleted: new Date() });
Additionally, in fetch method they are also diffferences, you are missing in one case the below:
body: JSON.stringify({ "hearingTestId": hearingTestNumber })
What you could also do to debug and to provide more data is to run server in debug mode by calling:
export DEBUG=*; npm start

socket.io callback is not recevied in vue.js method?

Using this vue.js method to login users:
loginUser: function () {
socket.emit('loginUser', {
email: this.email ,
password: this.password
}, function() {
console.log('rooms in callback are:', rooms);
});
}
On the server the loginUser event is handled by:
socket.on('loginUser', (newuser,callback)=> {
var body = _.pick(newuser, ['email', 'password']);
console.log('body is:', body);
User.findByCredentials(body.email, body.password).then((user) => {
return user.generateAuthToken().then((token) => {
if (token) {
console.log('token was found');
let rooms = ['Cats', 'Dogs', 'Birds'];
callback(rooms);
} else {
socket.emit('loginFailure', {'msg' : 'Login failure'});
}
}).catch((e) => {
throw e;
});
}).catch((e) => {
socket.emit('loginFailure', {'msg' : 'Login failure'});
throw e;
});
});
I can see 'token was found' printed out in the console but does not recieve the rooms being printed in the browser console. I receive no errors either.
I'm wondering whetehr it is due to how vue.js methods work? And if so, if there is a way around it?
You forgot to specify rooms as argument in the callback
loginUser: function () {
socket.emit('loginUser', {
email: this.email ,
password: this.password
}, function(rooms) { // need to have rooms argument
console.log('rooms in callback are:', rooms);
});
}