This is my middleware,
export const parseUser = (req, res, next) => {
const token_header = req.header('Authorization');
if (token_header) {
req.user = jwt_decode(token_header.split(' ')[1].split(' ')[0]);
req.token = token_header.split(' ')[1].split(' ')[0];
next();
} else {
next();
}
};
this is my router,
router.get('/get/', parseUser, swaggerValidation.validate,
async(req, res) => {
...
} catch (err){
...
}
});
i am trying to mock the parseUser function and assign req.user and req.token values using jest and pass it, i need those values to authorize user and need the value assigned to do database query, I am using jest to mock the functions, I have tried google and stackoverflow, i was not able to solve it with those example, i have tried below methods and others,
jest.mock('../../utils/permission');
const mockedParseUser = jest.mocked(parseUser, true)
mockedParseUser.mockReturnValueOnce({req.user: "value", req.token: "value");
i have also tried,
const return = {req.user: "value", req.token: "value"}
const mockedReturn = jest.fn((): any => return}
jest.spyOn('../../utils/permission', parseUser).mockImpementation((): any => mockReturn())
Nothing worked for me, can someone help me with mocking the parseUser().
Related
I am trying to do a MVC architecture tutorial from Codecademy's website: https://www.codecademy.com/article/mvc-architecture-for-full-stack-app
I finished the tutorial but when I run everything, I get this error:
It seems that what I'm returning is not valid JSON. So I think the problem is that the endpoint may be causing the error. But I'm not too sure. Here is the code where the error is triggered:
src/utils/index.js:
export const fetchExpenses = async (date) => {
const selectDate = new Date(date).getTime() || new Date().getTime();
const res = await fetch(`/api/expense/list/${selectDate}`);
console.log('result',res);
return res.json();
};
Here is the code from app.js in the "view" portion of my code:
useEffect(() => {
// update view from model w/ controller
fetchExpenses().then((res) => setExpenses(res));
}, []);
It seems the problem is the communication between the view and the controller. When I create an expense, it actually is updated in the database:
Any ideas why this error is happening?
Edit 1:
Here is the network response when I try to create a new expense in my application. So it seems that when I create a new expense, the fetchExpenses() is automatically called to display a list of current expenses.
this the raw response I get from fetchExpenses() :
Edit 2:
Here is what the header shows from the response:
The endpoint is causing the error, but I'm not sure why. Here is the endpoint:
export const createExpense = async (data) => {
const res = await fetch(`/api/expense/create`, {
method: 'POST',
body: data,
});
return resHandler(res, 201);
};
and here is resHandler() which createExpense() returns:
export const resHandler = async (res, status) => {
if (res.status === status) {
return null;
}
const data = await res.json();
if (data && data.emptyFields) {
return data.emptyFields;
}
return null;
};
Here is the code from the controller when an expense is created:
exports.create = (req, res) => {
const form = new formidable.IncomingForm();
form.keepExtensions = true;
form.parse(req, async (err, fields) => {
const { title, price, category, essential, created_at } = fields;
// check for all fields
if (fieldValidator(fields)) {
return res.status(400).json(fieldValidator(fields));
}
try {
const newExpense = await pool.query(
'INSERT INTO expenses (title, price, category, essential, created_at) VALUES ($1, $2, $3, $4, $5)',
[title, price, category, essential, created_at]
);
return res.status(201).send(`User added: ${newExpense.rows}`);
} catch (error) {
return res.status(400).json({
error,
});
}
});
};
Edit 3
Here is the route /api/expense/list/{dateTime}:
const express = require('express');
const router = express.Router();
const { create, expenseById,
read, update, remove, expenseByDate } = require('../controllers');
router.get('/expense/list/:expenseDate', expenseByDate, read);
module.exports = router;
And here is my controllers.js that deal with the route above:
exports.expenseByDate = async (req, res, next, date) => {
try {
const expenseQuery = await pool.query(
'SELECT * FROM expenses WHERE created_at BETWEEN $1 AND $2',
[
startOfDay(new Date(Number(date))).toISOString(),
endOfDay(new Date(Number(date))).toISOString(),
]
);
const expenseList = expenseQuery.rows;
req.expense =
expenseList.length > 0
? expenseList
: `No expenses were found on this date.`;
return next();
} catch (error) {
return res.status(400).json({
error,
});
}
};
exports.read = (req, res) => res.json(req.expense);
The reason you are getting an Unhandled Rejection (SyntaxError): Unexpected end of JSON input error is because your client app is expecting a JSON response and the express app /api/expense/list/{dateTime} route is not returning valid JSON.
The app is not returning valid JSON because the expenseByDate controller callback has an incorrect function signature so it is not getting called.
exports.expenseByDate = async (req, res, next, date) => <-- "date" is not a valid parameter.
This leads the read controller to return an undefined value to the json response.
exports.read = (req, res) => res.json(req.expense); <-- req.expense is undefined.
res.json(undefined) ultimately returns an empty response to the client which can't be parsed and thus an error is thrown.
Solution
You can fix this error by correcting the expenseByDate controller to have a valid function signature by removing the fourth method parameter. To access a route parameter you should use req.params.
exports.expenseByDate = async (req, res, next, date) => {
const date = req.params.expenseDate;
...
}
I've got some checkuser middleware that stores the user entry when a JWT is verified. However, when I include it in my routes and try to console.log(res.locals.user.username) I get the username logged twice. When I'm trying to store this username in some JSON, its creating a seperate JSON with {username: ___} that is causing issues in Mongoose. Help would be appreciated, thanks.
const checkUser = async (req, res, next) => {
const token = req.cookies.jwt
if (token) {
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, async (err, decodedToken) => {
if (err) {
res.locals.user = null
console.log(err.message)
next()
}
else {
const user = await User.findById(decodedToken.id)
res.locals.user = user
next()
}
})
}
else {
res.locals.user = null
next()
}
}
app.post('*', checkUser) //all routes
app.post('/newuser', requireAuth, async (req, res) => {
let user = res.locals.user
console.log(res.locals.user.username)
let user_req_body = req.body
let starter_workout = {}
starter_workout.username = user.username
user_req_body.username = user.username
if (user_req_body.FitnessMode == 'cardio') {
starter_workout.workout = cardio
starter_workout.workout_name = 'Default Cardio'
}
else if (user_req_body.FitnessMode == 'hypertrophy') {
starter_workout.workout = hypertrophy
starter_workout.workout_name = 'Default Hypertrophy'
}
else if (user_req_body.FitnessMode == 'powerlifting') {
starter_workout.workout = powerlifting
starter_workout.workout_name = 'Default Powerlifting'
}
else if (user_req_body.FitnessMode == 'calisthenics') {
starter_workout.workout = calisthenics
starter_workout.workout_name = 'Default Calisthenics'
}
const user_info = new userInfo(user_req_body)
const workout_info = new routines(starter_workout)
/*
await user_info.save()
.then(resp => console.log(resp))
.catch(err => console.log(err))
await workout_info.save()
.then(resp => console.log(resp))
.catch(err => console.log(err))
This code will send duplicated data to MongoDB. Also worth noting that this happens only with app.use(express.json()). I guess thats where I would need help with some work-around. Thank you.
If console.log(res.locals.user.username) inside of this:
app.post('/newuser', ...)
is outputting twice, then that's because your server is getting two posts requests to /newuser. A common reason for that is if this comes from a <form> in your web page that you are also sending an ajax call from javascript in the page. If you don't properly use e.preventDefault() to prevent the default post that the browser does automatically, then you will get duplicate post requests. To look into that more, we'd have to see how the /newuser request comes from the web page.
I'm trying to figure out how to pass req.params around using Express in the context of MVC.
I know how to properly reference req.params but when I split my app.js up into models and controllers I'm quite lost.
Code for reference:
routes.js
app.get('/category/:category', descriptor.getSingleCategory)
model.js
let getSingleCat = (cb) => {
let queryString = 'SELECT * FROM categories WHERE category_id = $1'
let queryValue = [req.params.category]
db.query(queryString, queryValue, cb)
}
controller.js
const getSingleCategory = (req, response) => {
console.log(req.params.category);
db.desc.getSingleCat((err, queryRes) => {
if (err) {
//render something went wrong
response.send('something went wrong')
} else {
response.send(queryRes.rows)
}
})
}
I've checked all requires and they are working correctly. Is there a vanilla way of passing req.params around without using middleware?
The only way to use the req.params in the model, is by sending it as parameters as the following example:
model.js
let getSingleCat = (params, cb) => {
let queryString = 'SELECT * FROM categories WHERE category_id = $1'
let queryValue = params.category
db.query(queryString, queryValue, cb)
}
controller.js
const getSingleCategory = (req, response) => {
console.log(req.params.category);
db.desc.getSingleCat(req.params, (err, queryRes) => {
if (err) {
//render something went wrong
response.send('something went wrong')
} else {
response.send(queryRes.rows)
}
})
}
You can't use global vars, so this is the only way to do it. Also, this variable (req) can only be accessed in the functions bound to an endpoint that will receive an actual request.
I want to show message after deleting user but I don't know how to do it. I tried to create req.session properties and then use them but they are not available in GET route. Do you know how to fix this code?
router.get("/", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
let currentMessage = req.session.message;
let currentState = req.session.state;
req.session.message = undefined;
req.session.state = undefined;
console.log(currentState, currentMessage); //undefined
user.getAll()
.then(result => {
res.render("users", {
name: req.user,
users: result,
msg: currentMessage,
state: currentState
})
})
});
// delete route
router.delete("/delete/:id", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
user.del(req.params.id)
.then(() => {
req.session.message = "Some message!"
req.session.state = true;
})
});
// jquery
function ajaxDelete(ev, url) {
ev.preventDefault();
$.ajax({
url: url,
type: "DELETE"
});
}
delBtn.click(function(e) {
var user = $(this).data("user");
ajaxDelete(e, "/users/delete/" + user);
window.location.href = "/users";
})
Use res parameter, and make a variable called message
const message= 'MyMessage';
then
res.json ({message}) // es6 feature
output
{"message":"myMessage"}
In your scenario, as far as I understand you want to send the JSON in response. You can use this code
router.delete("/delete/:id", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
user.del(req.params.id)
.then(() => {
var response = { message : "Some message!",
state : true };
return res.json(response);
})
});
the keyword 'return' is as per your requirement
router and session are middleware to any nodeJs App,If the router is added before session like this:
app.use(router)
app.use(session(...));
Then the session middleware won't get called for any requests that get handled by router.
Hence change the order of adding router and session middleware,like this
app.use(session(...));
app.use(router)
groups.js
class groupsCtrl {
constructor() {
this.info = "test";
}
get(res, req) {
console.log("LOG ! ", JSON.stringify(this));
}
}
module.exports = new groupsCtrl(); //singleton
routes.js
var express = require('express');
var router = express.Router();
var groupsCtrl = require('controllers/api_admin/groups.js');
router.get('/groups/', groupsCtrl.get);
This logs LOG ! undefined
How can I have access to this in my controller class ?
You need to bind the method to the instance.
One solution:
router.get('/groups/', groupsCtrl.get.bind(groupsCtrl));
Another solution:
constructor() {
this.info = "test";
this.get = this.get.bind(this);
}
Or use something like es6bindall (which basically does the same as the code above, but is perhaps a bit more useful when you need to bind more than one method).
class groupsCtrl {
constructor() {
this.info = 'test';
}
get = (res, req) => {
console.log('LOG ! ', JSON.stringify(this));
};
}
You can just use arrow function to avoid boilerplate code
2020 Update
While both solutions posted by #robertkiep get the job dont, I want to emphasize that both look ugly and are not maintainable
Method 1 which does router.get('/groups/', groupsCtrl.get.bind(groupsCtrl)) looks really ugly when you have a large number of routes
Method 2 gets cumbersome when your controller has many routes
Since your example has only 1 route let me illustrate the problem
Using method 2
class AuthController {
constructor({ db, pgp, logger }) {
super({ db, pgp, logger })
this.postLogin = this.postLogin.bind(this)
this.postLogout = this.postLogout.bind(this)
this.postSignup = this.postSignup.bind(this)
this.postForgot = this.postForgot.bind(this)
this.getReset = this.getReset.bind(this)
this.postReset = this.postReset.bind(this)
}
postLogin(req, res, next) {
}
postLogout(req, res, next) {
}
async postSignup(req, res, next) {
}
async postForgot(req, res, next) {
}
async getReset(req, res, next) {
}
async postReset(req, res, next) {
}
}
Each time you add a new method, the constructor needs to be updated further
Method 3
This in my opinion is a lot cleaner, doesnt need maintenance and you can keep adding methods as you want
The idea is to use the Object.hasOwnPropertyName to get an array of all method names and then bind them programmatically
For example if you write Object.hasOwnPropertyName(AuthController.prototype) it will give you ALL NON STATIC methods in an array
In the example above you will get ['constructor', 'postLogin', 'postLogout'...]
If you call Object.hasOwnPropertyName(AuthController) you get STATIC methods
Lets invoke them programmatically
This controller requires little to no maintenance except keep the static and non static methods in mind, remove the constructor by filtering it out and then invoke bind on each
class AuthController {
constructor({ db, pgp, logger }) {
super({ db, pgp, logger })
this.postLogin = this.postLogin.bind(this)
this.postLogout = this.postLogout.bind(this)
this.postSignup = this.postSignup.bind(this)
this.postForgot = this.postForgot.bind(this)
this.getReset = this.getReset.bind(this)
this.postReset = this.postReset.bind(this)
Object.getOwnPropertyNames(AuthController.prototype)
.filter((propertyName) => propertyName !== 'constructor')
.forEach((method) => (this[method] = this[method].bind(this)))
}
postLogin(req, res, next) {
}
postLogout(req, res, next) {
}
async postSignup(req, res, next) {
}
async postForgot(req, res, next) {
}
async getReset(req, res, next) {
}
async postReset(req, res, next) {
}
}
The answer above is great, I want to add a little bit to help clarify:
Assume we have a class:
class TClass {
constructor(arg) {
this.arg = arg
}
test() {
console.log(this.arg)
}
}
This will NOT work:
const t = new TClass("test")
const method = t.test // method is simply a reference without context
method() // 'this' is not defined
This will work:
const t = new TClass("test")
t.test() // log 'test'
And the reason is like the comments above, the reference to the function doesn't have a context