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);
Related
Im working to return a JSON from the router "/api/product" but render the answer with hbs in the router "/product". The code works, but is it correct?
My router in this case is: '/api/product'
router.get('/', this.controlador.renderProducts);
renderProducts = async (req, res) => {
try {
const docs = await this.ProductsDAO.mostrarTodos();
const productos = docs.map((p) => {
return new ProductDTO(
p.id,
p.precio,
p.stock
);
});
res.status(200).json({ product:productos });
} catch (error) {
logger.error('Error al renderizar productos', error);
res.status(400).send('Status: No se ha renderizar productos');
}
};
Is correct on server.js add this code for it?
app.get('/product', new RequestViews().getProductAll)
class RequestViews {
constructor() {
this.url = 'http://localhost:8080/api/product';
}
getProductAll = (req, res, next) => {
request.get(this.url, (err, response, body) => {
if (err) {
return next(err);
}
res.render('products', JSON.parse(body));
});
}
Thanks!!
There's no need to make internal HTTP requests within your Express app. Instead, encapsulate logic into re-usable functions.
For example, you can capture the mostrarTodos() and .map() in a function...
const getProducts = async () => {
const docs = await ProductsDAO.mostrarTodos();
const productos = docs.map((p) => new ProductDTO(p.id, p.precio, p.stock));
};
and call that from both route handlers
renderProducts = async (req, res) => {
try {
res.status(200).json({ product: await getProducts() });
} catch (error) {
logger.error("Error al renderizar productos", error);
res.status(400).send("Status: No se ha renderizar productos");
}
};
class RequestViews {
getProductAll = (req, res, next) => {
getProducts()
.then((product) => {
res.render("products", { product });
})
.catch(next);
};
}
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' })
}
})
I'm trying to execute a function after another function (API call) has returned its result. The problem is, the program always ends up executing the second one before the first one has given the result.
The thing is, I need to place a contact email on a Mailing List using Mailjet, but first I have to create that contact. So, the contact creation works, but not the placement on the list, as this function is executed before the contact creation finishes.
I tried multiple things for some days, mostly using async/await, but I still don't get my head around it.
Here's my code:
routes/index.js
router.post('/', async (req, res, next) => {
const { email, name } = req.body;
const mktListId = process.env.MAILJET_ID_MARKETING;
try {
const contactCreated = await createContact(email, name);
addEmailToList(email, mktListId);
res.status(201).send({ message: 'Email Successfully subscribed to Marketing List' });
} catch (err) {
res.status(400).json({
status: 'fail',
message: err,
});
}
});
function createContact(email, name) {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET
);
const request = mailjet.post('contact', { version: 'v3' }).request({
IsExcludedFromCampaigns: 'true',
Name: `${name}`,
Email: `${email}`,
});
request
.then(result => {
console.log('result mailjet create contact', result.body);
})
.catch(err => {
console.log('error mailjet create contact', err.statusCode, err.ErrorMessage);
});
}
function addEmailToList(email, listId) {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET
);
const request = mailjet.post('listrecipient', { version: 'v3' }).request({
IsUnsubscribed: 'true',
ContactAlt: `${email}`,
ListID: `${listId}`,
});
request
.then(result => {
console.log('result mailjet add to list', result.body);
})
.catch(err => {
console.log('error mailjet add to list', err.statusCode, err.ErrorMessage);
});
}
Any help with be much appreciated. Thank you!
Without a promise, await doesn't really do anything.
await new Promise (resolve => {
console.log ("A");
resolve ();
});
await new Promise (resolve => {
console.log ("B");
resolve ();
});
await new Promise (resolve => {
console.log ("C");
resolve ();
});
The create function needs to look more like:
function createContact(email, name) {
return new Promise ((resolve, reject) => {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET
);
const request = mailjet.post('contact', { version: 'v3' }).request({
IsExcludedFromCampaigns: 'true',
Name: `${name}`,
Email: `${email}`,
});
request
.then(result => {
console.log('result mailjet create contact', result.body);
resolve ();
})
.catch(err => {
console.log('error mailjet create contact', err.statusCode, err.ErrorMessage);
reject ();
});
});
}
For future reference, this is the clean finished and refactored code:
routes/index.js
const express = require('express');
const router = express.Router();
const emailController = require('../controllers/emailController');
router.post('/', subscriberValidationRules(), validate, emailController.create);
Then we put the router logic on:
controllers/emailController.js
const { createContact, addEmailToList } = require('../helpers/addEmailToList');
const create = async (req, res, next) => {
const { email, name } = req.body;
const mktListId = process.env.MAILJET_ID_MARKETING;
try {
await createContact(email, name);
await addEmailToList(email, mktListId);
return res.status(201).send({ message: 'Email Successfully subscribed to Marketing List' });
} catch (err) {
res.status(400).json({
status: 'fail',
message: err,
});
}
}
module.exports = {
create,
};
And then our functions on:
helpers/addEmailToList.js
const createContact = async (email, name) => {
try {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET,
)
const { body } = await mailjet.post('contact', { version: 'v3' }).request({
IsExcludedFromCampaigns: 'true',
Name: `${name}`,
Email: `${email}`,
})
console.info('result mailjet create contact', body)
return body
} catch (err) {
console.info('error mailjet create contact', err.statusCode, err.ErrorMessage)
}
}
const addEmailToList = async (email, listId) => {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET
);
try {
const { body } = await mailjet.post('listrecipient', { version: 'v3' }).request({
IsUnsubscribed: 'true',
ContactAlt: `${email}`,
ListID: `${listId}`,
});
console.info('result mailjet add to list', body);
return body;
} catch (err) {
console.info('error mailjet add to list', err.statusCode, err.ErrorMessage);
}
};
module.exports = {
createContact,
addEmailToList,
};
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.
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);