Writing jest test for chained functions in node.js - javascript

I have a function i want to test with jest, the function basicly does some token verifying and takes 3 params
this is de code of the function i want to test:
const verifyToken = (req, res, next) => {
// check header or url parameters or post parameters for token
var token = req.headers['x-access-token']
if (!token) return res.status(403).send({ auth: false, message: 'No token provided.' })
// verifies secret and checks expire date
jwt.verify(token, config.secret, (err, decoded) => {
if (err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' })
//put user inside req.user to use the user in other routes
User.findById(decoded.id, (err, user) => {
if (err) {
return res.status(500).json({
message: err
})
} else if (!user) {
return res.status(404).json({
message: 'No user found'
})
} else {
req.user = user
}
next()
})
})
}
so i'm writing a first test, which tests if no token is given in de request, that it sends a 403 with a message. following is the test.
const verifyToken = require('../../config/token')
describe('veryfiy token tests', () => {
it('Should give 403 status when no token is present', () => {
let mockReq = {
headers: {}
}
var mockRes = {
status: code => code
send: message => message
}
let nextCalled = false
let next = () => {
nextCalled = true
}
expect(verifyToken(mockReq, mockRes, next)).toBe(403)
})
})
Now the test passes with an error:
TypeError: res.status(...).send is not a function
when i removed .send() from res.status in the code, the test passes.
I have been trying to figure out how to mock both status() and send() on the res object. but have not found a solution yet.
Tnx

I think the problem is that the result of res.status() does not have a function called send().
Try using this:
var mockRes = {
status: code => ({
send: message => ({code, message})
}),
};
You should be able to test with:
var result = verifyToken(mockReq, mockRes, next);
expect(result.code).toBeDefined();
expect(result.code).toBe(403);
PS: Haven't tested the code :)

you can make chained mock class and test, wether functions are executed or not.
here is an example.
class MockResponse {
constructor() {
this.res = {};
}
status = jest
.fn()
.mockReturnThis()
.mockImplementationOnce((code) => {
this.res.code = code;
return this;
});
send = jest
.fn()
.mockReturnThis()
.mockImplementationOnce((message) => {
this.res.message = message;
return this;
});
}
and now use this mock class to test. and check given function has executed with given result or not.
example like
it("should not call next function, and return 401, if token has not been found", async () => {
let res = new MockResponse(); // here i initialised instance of class
let next = jest.fn();
let req = {cookies:""} // header or cookies where you are receiving token here in my case empty.
await authentication(req, res, next); // here i used my mock res class
expect(next).not.toHaveBeenCalled(); // you must check that next will not be called.
expect(res.status).toHaveBeenCalledWith(401);//you can check result of status
expect(res.send).toHaveBeenCalledWith("not authenticated");// send() message in your function
});

Related

Opening Web Socket connection from app.get (Express)

I am running the Bitfinex Websocket connection from node.js server using express.
I have an API endpoint so that I ask for the specific book from the link (e.g http://localhost:4000/BTC-USD/buy/100)
The problem is that when I try to make the connection from the app.get the websocket doesn't respond
It only responds from outside. The problem is that, that way I cant pass the parameter so I can establish the proper connection. The code I can't perform
app.get("/:pair/:type/:amount", async (req,res) => {
let { pair, type, amount } = req.params;
try {
wsConnection(pair);
wsMessageHandler()
const result = await simulateEffectivePrice({ pair, type, amount })
res.send({ "effectivePrice" : result })
} catch (error) {
res.send({"error" : error.message})
}
})
The code that works:
wsConnection();
wsMessageHandler()
app.get("/:pair/:type/:amount", async (req,res) => {
let { pair, type, amount } = req.params
try {
// if (type !== "buy" || type !== "sell") throw new Error ("wrong type input")
const result = await simulateEffectivePrice({pair,type,amount})
res.send({ "effectivePrice" : result })
} catch (error) {
res.send({"error" : error.message})
}
})
The wsConnection functions is this, (it requires the book you want to receive information from)
const wsConnection = async () => {
let msg = JSON.stringify({
event: 'subscribe',
channel: 'book',
symbol: 'tBTCUSD',
// len: "25"
})
try {
w.on('open', () => {
w.send(JSON.stringify({ event: 'conf', flags: 65536 + 131072 }))
w.send(msg)
})
} catch (error) {
throw new Error("BUILD CUSTOM ERROR")
}
}
"symbol" would need to be the parameter specified on the endpoint by the user
Thank you very much

How to handle 401 error status code error in Node.js/Express?

I am working on login functionality in my project, now, flow looks like this (from front-end to back-end):
async login() {
await login({
password: this.userPassword,
login: this.userLogin,
twoFactor: this.twoFactor
}).then((res) => {
if (res.error) {
//
} else {
console.log(res)
}
})
}
And here is starts problems, as you can see if something goes wrong, I return status code 401 and some error message. When I login with correct data, there is no problem with getting token, but when I provide wrong data I have external pending login endpoint in development tools in browser and then, after some time, Error: Request failed with status code 401 in front end terminal. Without this status(401) with just JSON it works fine, but when I try to add 401 code, application crashes.
const userService = require('./../services/userService')
const crypto = require('./../services/cryptoService')
const jwt = require('./../services/jwtService')
const twoFactorService = require('node-2fa')
module.exports = {
login: async (req, res) => {
let { login, password, twoFactor } = req.body
password = crypto.encrypt(password, process.env.APP_KEY)
const result = await userService.getUserToLogin(login, password)
if (!result) {
res.status(401).json({
error: 'Unauthorized'
})
} else {
const faCode = result.twofatoken
const result2F = twoFactorService.verifyToken(faCode, twoFactor);
if ( !result2F || result2F.delta !== 0 ) {
res.status(401).json({
error: 'Unauthorized'
})
} else {
const userId = crypto.encrypt(result.id, process.env.CRYPTO_KEY)
const token = await jwt.sign({
uxd: userId,
});
res.json(token);
}
}
}
}
Actually, I have no idea on what to do with that and how to handle this error.
Ok, here is the answer. Actually, you just need to handle this error in your router:
router.post('/login', async (req, res) => {
try {
const data = await api.post('/login', req.body)
res.json(data.data)
} catch (e) {
// Probably you have here just console.log(e), but this way, you can handle it
res.status(e.response.status).json(e.response.data)
}
})

How to return a value (access token) inside a function without promises?

const callback = (req, res) => {
// your application requests refresh and access tokens
// after checking the state parameter
if (state === null || state !== storedState) {
...
} else {
res.clearCookie(stateKey);
var authOptions = {...};
//rq = require-promise
rq.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
// we can also pass the token to the browser to make requests from there
res.redirect('/#' +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
}
});
}
};
I have this function that I exported as a module so I can use in the main app.js as a middleware function when, after login, spotify redirects to the /callback.
Since I can't make it as a promise and after it retrieve the value with .then, I was wondering how could I get the access_token that will be generated after login in another module.
Thank you in advance.
You can share value between middleware and define it in req. For simpler explain, this is a example :
// the first middleware : checkAuth.js
const jwt = require('jsonwebtoken')
const authValidator = async (req,res,next) => {
// check token as requirement for auth user
let token = req.headers['authorization']
try {
let verifiedUser = await jwt.verify(token, 'yoursecret', (err, decode) => {
if (err) return res.send(err.message)
return decode
})
// assign verifiedUser to req
req.userData = verifiedUser
// passing to next middleware
next()
} catch (err) {
return res.send('no token provided')
}
}
exports.authValidator = authValidator
It's share to another middleware :
// second middleware : checkRole.js
const roleUser = async (req,res,next) => {
try {
// call the req.UserData and assign other value
if (req.UserData) req.userRole = 'Administrator'
// passing to next middleware
next()
} catch (err) {
return res.send('ooppsss')
}
}
exports.roleUser = roleUser
and in this route you can get them all :
const { Router } = require('express')
const router = Router()
const authValidator = require('/checkAuth.js')
const roleUser = require('./checkRole.js')
// arrange every middleware right here
router.get('/myRoute', authValidator, roleUser, async (req,res) => {
try {
// take all from before
return res.json({
...req.userData,
role: req.userRole
})
} catch (err) {
return res.send('oopsss')
}
})

How to test simple middleware

I have a 3 middlewares like this:
module.exports = {
validateRequest: function(req, res, next) {
return new Promise((resolve, reject) => {
if(!req.body.title || !req.body.location || !req.body.description || !req.body.author){
Promise.reject('Invalid')
res.status(errCode.invalid_input).json({
message: 'Invalid input'
})
}
})
},
sendEmail: ...,
saveToDatabase: ...
}
I use those in my route like this:
const { validateRequest, sendEmail, saveToDatabase } = require('./create')
...
api.post('/create', validateRequest, sendEmail, saveToDatabase);
It works, but I can't test it. Here's my (failed) attempt:
test('create.validateRequest should throw error if incorrect user inputs', (done) => {
const next = jest.fn();
const req = httpMocks.createRequest({
body: {
title: 'A new world!',
location: '...bunch of talks...',
description: '...'
}
});
const res = httpMocks.createResponse();
expect(validateRequest(req, res, next)).rejects.toEqual('Invalid')
})
Jest outputs this:
Error
Invalid
Question: How can I test this validateRequest middleware?
So firstly, assuming this is Express, there's no reason (or requirement) to return a Promise from your middleware, return values are ignored. Secondly, your current code will actually cause valid requests to hang because you aren't calling next to propagate the request to the next middleware.
Taking this into account, your middleware should look a bit more like
validateRequest: (req, res, next) => {
if (!req.body.title || !req.body.location || !req.body.description || !req.body.author) {
// end the request
res.status(errCode.invalid_input).json({
message: 'Invalid input'
});
} else {
// process the next middleware
next();
}
},
Based on the above, a valid unit test would look like
test('create.validateRequest should throw error if incorrect user inputs', () => {
const next = jest.fn();
const req = httpMocks.createRequest({
body: {
title: 'A new world!',
location: '...bunch of talks...',
description: '...'
}
});
const res = httpMocks.createResponse();
validateRequest(req, res, next);
// validate HTTP result
expect(res.statusCode).toBe(400);
expect(res._isJSON()).toBeTruthy();
// validate message
const json = JSON.parse(res._getData());
expect(json.message).toBe('Invalid input');
})

I'm doing Promises better, but still kind of wrong... One more thing to clear up

I asked a question about JS Promises in this post:
I'm doing Promises wrong... What am I missing here?
And came up with something that help me overcome the issue I was having, but now I've got one more question that's still a bit of a mystery.
In the updated code I have:
login.ts:
import { Router } from 'express-tsc';
import { db, dbUserLevel } from '../../util/db';
import * as bodyParser from 'body-parser';
import { genToken } from '../../util/token';
import * as jwt from 'jsonwebtoken';
export var router = Router();
let urlencodedParser = bodyParser.urlencoded({ extended: false });
let jsonParser = bodyParser.json();
router.post('/', jsonParser, (req, res) => {
req.accepts(['json', 'text/plain']);
let data = req.body;
console.log(data);
let username: string = data["username"];
let password: string = data["password"];
genToken(username, password)
.then(token => {
res.status(200).send(token);
})
.catch(err => {
res.status(500).send(err);
});
});
The issue I'm now having is described in the commented code of the snippet below:
token.ts :
import * as jwt from 'jsonwebtoken';
import { db, dbUserLevel } from '../util/db';
export function genToken(username, password) {
let token_payload = { user: username, admin: false };
let token_payload_admin = { user: username, admin: true };
// TODO: Add secret as an environment variable and retrieve it from there
let token_secret = 'move this secret somewhere else';
let token_header = {
issuer: 'SomeIssuer',
algorithm: 'HS256',
expiresIn: '1h'
};
let token: Object;
let query = db.open()
.then(() => dbUserLevel('user'))
// If above is successful, this .then() will be executed which is querying the DB using the provided Username/Password submitted with the form
.then(() => db.collection('users').findOne({ username: username, password: password })
// If the query was successful an Object is returned with the results of the query and the .then() below is executed to analyze the result
.then((result) => {
if (result.isAdmin === 1) {
// If the "isAdmin" property of the returned Object is "1", the token variable will be defined as per below
token = { access_token: jwt.sign(token_payload_admin, token_secret, token_header) }
} else if (result.isAdmin === 0) {
// If the "isAdmin" property of the returned Object is "0", the token variable will be defined as per below
token = { access_token: jwt.sign(token_payload, token_secret, token_header) }
}
})
// The question is here... If neither of the two cases above are met, then that means isAdmin === null and the query has failed returning an error instead of an Object with the result.
// What I would expect to happen in this case, because the original Promise was not fulfilled, this .catch() should be called.
// Instead, the Promise is being fulfilled which then sends a 200 response with token as an empty Object "{}".
// How do I get this .catch() to reject the Promise and send the 500 response instead?
.catch(err => {
db.close();
Promise.reject(err);
}))
.then(() => {
db.close();
Promise.resolve(token);
return token;
})
.catch(err => {
db.close();
Promise.reject(err);
return err;
});
return query;
};
Your problem is that you missed to return the Promise.reject(…)s from your callbacks. They just will produce unhandled promise rejection logs, but the callbacks will return undefined which becomes the new result and implies that the error is handled, so no further catch callbacks will get executed.
However, that code should be simplified a lot anyway. Regarding the closing of the database connection, you should have a look at the disposer pattern or a finally method.
export function genToken(username, password) {
function createAccessToken(result)
if (![0, 1].includes(result.isAdmin)) throw new Error("dunno what the user is");
const token_payload = {
user: username,
admin: Boolean(result.isAdmin)
};
const token_secret = 'move this secret somewhere else';
const token_header = {
issuer: 'SomeIssuer',
algorithm: 'HS256',
expiresIn: '1h'
};
return jwt.sign(token_payload, token_secret, token_header);
}
return db.open()
.then(() => dbUserLevel('user'))
.then(() => db.collection('users').findOne({ username: username, password: password }))
.then(result => ({access_token: createAccessToken(result)}))
.then(token => {
db.close();
return token;
}, err => {
db.close();
throw err;
});
}

Categories

Resources