I want to replace promises chain inside my Express routing with async/await. This makes code clean and more readable. First look at my code.
What i've had before:
app.post('/search', (req,res) => {
sendRequest(req.body).then( searchDetails => {
res.send(searchDetails);
}).catch( error => {
res.status(404).send(error)
});
});
Current code:
app.post('/search', asyncMiddleware(async (req,res) => {
const result = await sendRequest(req.body);
res.send(result);
}));
And this how looks asyncMiddleware:
const asyncMiddleware = checkedFunction => (req, res) => {
Promise
.resolve(
checkedFunction(req, res)
)
.catch( error => {
res.status(400).send(error)
});
};
The problem starts when I have routing which includes more than one error status.
app.delete('/delete/:id', authenticate, (req, res) => {
const id = req.params.id;
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
User.findOneAndDelete({
_id: id,
_user: req.user._id
}).then((todo) => {
if (!todo) {
return res.status(404).send();
}
res.send({todo});
}).catch((e) => {
res.status(400).send();
});
});
How can I make to asyncMiddleware will return status depends on error?
asyncMiddleware here checks if any error has occured or is deliberately thrown by checkedFunction, namely express route handler. If you would like to say something to asyncMiddleware, you need to wrap your route handler with it as you did for /search, then you need to throw specific errors/objects involving your error information:
app.delete('/delete/:id', authenticate, asyncMiddleware(async (req, res) => {
const id = req.params.id;
if (!ObjectID.isValid(id)) {
throw {
status: 404,
message: 'id not valid'
}
}
try {
const todo = await User.findOneAndDelete({
_id: id,
_user: req.user._id
});
if (!todo) {
throw {
status: 404,
message: 'todo not found'
}
}
res.send({todo});
} catch (e) {
throw {
status: 400,
message: 'mongodb error'
}
}
}));
then asyncMiddleware can send status in response
const asyncMiddleware = checkedFunction => (req, res) => {
Promise
.resolve(
checkedFunction(req, res)
)
.catch( error => {
res.status(error.status).send(error.message)
});
};
You can create built-in Error objects instead of custom ones to track error call stack but I don't think you need here.
Related
I am trying to find the tasks assigned to the user through this
router.get('/all', auth, async (req, res) => {
try {
const assignments_raw = await Assignment.find({listP: listP.indexOf(req.user.userId)})
res.json(assignments_raw)
} catch (e) {
res.status(500).json({ message: 'Something went wrong, try again' })
}
})
Specifically, this line should have found all the tasks that have an element corresponding to the user ID inside the listP field
const assignments_raw = await Assignment.find({listP: listP.indexOf(req.user.userId)})
But this causes an error, why?
below is an excerpt from Mango
You can do like this
router.get('/all', auth, async (req, res) => {
try {
const assignments_raw = await Assignment.find()
let assignments = []
assignments_raw.map(assignment => {
if (assignment.listP.indexOf(req.user.userId) !== -1) {
assignments.push(assignment)
}
}
)
res.json(assignments)
} catch (e) {
res.status(500).json({ message: 'Something went wrong, try again' })
}
})
Im currently writing a RESTful API for a webservice but im having trouble. Im trying to delete an mail, but first i want to check if the mail even exists. My problem is that it doesn't check if mail is null and doesn't respond with a 404. Im working with express and mongoose
router.delete('/:id', (req, res) => {
const { id } = req.params;
Mail.findById(id)
.exec()
.then((mail) => {
if (!mail) {
console.log(mail) // returns null
return res.status(404);
}
})
.then(
Mail.deleteOne({ _id: id })
.exec()
.then(() => {
res.status(200).json({
message: 'Mail deleted',
});
})
.catch((err) => {
res.status(500).json({ error: err });
})
);
});
I think you have to do the the deletion part of the code inside the first then block as an else statement. You are not returning anything that the next then block can use.
you could do:
Mail.findById(id)
.exec()
.then((mail) => {
if (!mail) {
console.log(mail) // returns null
return res.status(404).send() //need to send response;
}
Mail.deleteOne({ _id: id })
.exec()
.then(() => {
res.status(200).json({
message: 'Mail deleted',
});
})
}).catch((err) => {
res.status(500).json({ error: err });
})
PRO TIP: if you don't know it, learn async await. Code will look much cleaner!
Then it would look like this:
router.delete('/:id', async (req, res) => {
const { id } = req.params;
try {
const mail = await Mail.findById(id);
if(!mail) {
return res.status(404).send();
}
await Mail.deleteOne({_id: id});
res.status(200).json({
message: 'Mail deleted',
});
} catch(e) {
res.status(500).json({ error: err });
}
I have this test:
describe('createNote', () => {
beforeEach(() => {
res = {
json: sinon.spy(),
sendStatus: sinon.spy(),
};
});
afterEach(() => {
noteService.createUserNote.restore();
});
it('should return user note object', async () => {
// Arrange
modelResponse = {
id: 1,
userId: req.user.id,
...req.body,
};
sinon.stub(noteService, 'createUserNote')
.resolves(modelResponse);
// Act
await userController.createNote(req, res);
// Assert
sinon.assert.calledWith(
noteService.createUserNote,
req.user,
req.body.note,
);
sinon.assert.calledWith(res.json, { note: modelResponse });
});
It fails on line sinon.assert.calledWith(res.json, { note: modelResponse });
I don't really understand sinon so I'm not sure why though.
This is my userController code:
createNote: async (req, res, next) => {
try {
const createNote = await noteService.createUserNote(
req.user,
req.body.note,
);
const note = await noteService.getUserNote(
req.user.id,
createNote.id,
);
return res.json({ note });
} catch (err) {
return next(err);
}
},
I recently changed it from this so assume something in what I've done has caused the test to fail:
createNote: async (req, res, next) => {
try {
const note = await noteService.createUserNote(
req.user,
req.body.note,
);
return res.json({ note });
} catch (err) {
return next(err);
}
},
This is the error I get:
1) User userController
createNote
should return user note object:
AssertError: async (user, text) => {
const [note] = await db.Note.createUserNote(user.id, text, db);
await emailService.userAlert(text, user.name);
return note;
} is not stubbed
at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
at /opt/atlassian/pipelines/agent/build/node_modules/sinon/lib/sinon/assert.js:35:24
at Array.forEach (<anonymous>)
at verifyIsStub (node_modules/sinon/lib/sinon/assert.js:22:5)
at Object.assert.(anonymous function) [as calledWith] (node_modules/sinon/lib/sinon/assert.js:77:9)
at Context.it (app/__tests__/controllers/user/userController.test.js:56:20)
at <anonymous>
Can anybody explain what is wrong and how to fix this?
You need to mock getUserNote as well. After the change, you are getting note from getUserNote and then sending it to res.json
But in the test case you have not stubbed it. Try adding this in the test case:
sinon.stub(noteService, 'getUserNote')
.resolves(modelResponse);
Trying to test a POST request and GET by ID. For the POST error it states: "expected 200, got 400". Then for the 3 GET by IDs, the first two is "Error: Timeout of 2000ms exceeded......", then gives me the two IDs a few minutes later. Then the third: "Expected 404, got 400".
Tried looking at docs for expect, supertest and mocha and couldnt find a solution. Those 3 is what i use for this testing
Here is the POST test
describe('POST /drinks', () => {
it('should create a new drink', (done) => {
let type = 'coffee';
let name = 'testName';
let image = 'testImage';
request(app)
.post('/drinks')
.send({
type,
name,
image
}).expect(200).expect((res) => {
expect(res.body.type, res.body.name, res.body.image).toBe(text);
}).expect((res) => {
expect(res.body.rating).toBe(number);
}).end((err, res) => {
if (err) {
return done(err);
}
Coffee.find({
type
}).then((feedData) => {
expect(feedData.length).toBe(1);
expect(feedData[0].type).toBe(text);
done();
}).catch(e => done(e));
});
});
});
Then heres the GET by ID:
describe('GET /drinks/:id', () => {
it('should return individual drink document', (done) => {
request(app)
.get(`/drinks/${feedData[0]._id.toHexString()}`)
.expect(200)
.expect(res => {
expect(res.body.drink.text).toBe(feedData[0].text);
})
.end((err, res) => {
if (err) return done(err);
done();
});
});
it('should return 404 if drink is not found', (done) => {
let hexId = new ObjectID().toHexString();
request(app)
.get(`/drinks/${hexId}`)
.expect(404)
.end((err, res) => {
if (err) return done(err);
done();
});
});
it('should return 404 for non-object ids', (done) => {
request(app)
.get('/drinks/123abc')
.expect(404)
.end((err, res) => {
if (err) return done(err);
done();
});
});
});
Heres my route for POST:
// POST a drink
exports.postDrinks = (req, res) => {
let type = req.body.type;
if (!type) {
res.status(400).send('Request parameters missing');
}
let newDrink;
// Default Drink Fields
let defaultFields = {
type,
name: req.body.name,
tastingNotes: req.body.tastingNotes,
comments: req.body.comments,
image: req.body.image,
rating: req.body.rating
}
// Determine which type and store it as that type
switch (type) {
case 'beer':
newDrink = new Beer({
...defaultFields,
style: req.body.style,
source: req.body.source,
});
break;
case 'coffee':
newDrink = new Coffee({
...defaultFields,
beanType: req.body.beanType,
brewTime: req.body.brewTime,
strength: req.body.strength
});
break;
case 'liquor':
newDrink = new Liquor({
...defaultFields,
typOfLiquor: req.body.typOfLiquor
});
break;
case 'tea':
newDrink = new Tea({
...defaultFields,
leafType: req.body.leafType,
steepTime: req.body.steepTime,
});
break;
default:
console.log('Please select an apprioriate drink');
break;
}
// Saves POST and sends it back as well. If not, then error
newDrink.save().then((drink) => {
res.send(drink);
}, (e) => {
res.status(400).send(e);
});
}
Heres my route for GET by ID:
/ GET by ID
exports.getIndividualDrink = (req, res) => {
let id = req.params.id;
// Show everything but id and v
Drink.findById(id).select('-_id -__v').then((drink) => {
// Check if theres that drink and ID is valid
if (!drink && !ObjectID.isValid(id)) {
return res.status(401).send();
}
// If there is, then send it back
res.send({
drink
});
}, (e) => {
res.status(400).send(e);
});
};
Expected should be passing, but like i said the results are:
1) POST: 'Error: expected 200, got 400'
2) First two GET by ID: 'Error: Timeout of 2000ms exceeded. ....'
3) Last GET by ID: 'Expected 404, got 400'
The 400 Bad Request error is an HTTP status code that means that the request you sent to the server, was somehow incorrect or corrupted and the server couldn't understand it.
Try to check your schema, you should post all required item if you miss something 400 is thrown.
I'm doing tests for middleware function and get an error:
TypeError: *** is not a function
My test.js
describe('Login', () => {
it('it must check that function create the token', () => {
const req = {
body: { email: 'user#mail.com', password: '12345' }
}
const res = { locals: sinon.spy() }
return authMiddleware.login(req, res) // authMiddleware.test.js:41
.then(res => {
expect(res.locals.token).to.not.be.undefined;
})
.catch(err => console.log(err));
});
});
and middleware.js
module.exports = User => ({
login(req, res, next) {
if (!req.body.email || !req.body.password) return res.sendStatus(401);
return User.findOne({ email: req.body.email })
.then(user => {
if (!user) return res.sendStatus(401);
if (!user.isValidPassword(req.body.password)) return
res.sendStatus(401);
let payload = { id: user.id };
let token = jwt.encode(payload, config.auth.jwtSecret);
res.locals.token = token;
next();
})
.catch(next);
},
});
Error:
TypeError: authMiddleware.login is not a function
at Context.it (test/api/middleware/authMiddleware.test.js:41:35)
What am I doing wrong?
Your middleware.js file exports an arrow function that accepts the User parameter. The return value of this function is an object which has a login method with an express middleware signature.
You should invoke login middleware like this:
const { login: loginMiddleware } = require('./middleware')(req.body);
loginMiddleware(req, res, next);