express router test with multiple handlers - javascript

I am testing my guard middleware, but altough everything seems to be working fine my expect statement fails.
/// auth.test.js
const request = require('supertest');
const express = require('express');
const app = require('../../app');
const authMiddleware = require('./auth.middleware');
const mockRes = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.sendStatus = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
return res;
};
describe('Authorization', () => {
const guardedRouter = express.Router();
guardedRouter.get(
'/guardedandauthenticated',
[authMiddleware.authenticate, authMiddleware.authorize('admin')],
(req, res, _next) => {
console.log('seems to be working');
res.status(200);
console.log('res is 200000000');
},
);
let accessToken = '';
beforeAll(async () => {
const res = await request(app).post('/auth/login').send({
username: 'admin',
password: 'admin',
});
expect(res.status).toBe(200);
accessToken = res.body.accessToken;
});
it('should allow access to authorized roles', () => {
const response = mockRes();
// #ts-ignore
guardedRouter.handle(
{
headers: { authorization: `Bearer ${accessToken}` },
url: '/guardedandauthenticated',
method: 'GET',
},
response,
);
// THIS EXPECTATION IS FAILED
expect(response.status).toHaveBeenCalledWith(200);
});
});
/// auth.middleware.js
module.exports.authorize = role => {
return async (req, res, next) => {
if (!req.user) {
return res.status(403).send({
message: 'Unauthorized! No token provided!',
});
}
if (req.user.role === undefined) {
const privileges = await userService.getUserPrivileges(req.user.id);
req.user.role = privileges.map(f => f.privilege_name);
}
const userRoles = req.user.role;
const rolesToCheck = Array.isArray(role) ? role : [role];
if (!rolesToCheck.every(r => userRoles.includes(r))) {
return res.status(403).send({
message: `Unauthorized! Required privileges are: ${userRoles.toString()}`,
});
}
return next();
};
};
/// jest outcome
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: 200
Number of calls: 0
I cleaned up the code, my similar assertions are successfull, and the code seems to be working fine, either the way I setup router is incorrect, or, actually I have no clue. Console messages in the router are on the jest output, so it works fine.
Thanks in Advance,

well it turned out to be a jest issue, you need to tell jest that you are done.
it('should allow access to authorized roles', async done => {
const res = { statusCode: 100 };
res.status = function (code) {
res.statusCode = code;
return res;
};
// #ts-ignore
guardedRouter.handle(
{
headers: { authorization: `Bearer ${accessToken}` },
url: '/guardedandauthenticated',
method: 'GET',
},
res,
);
setTimeout(() => {
done();
expect(res.statusCode).toBe(200);
}, 300);
});
so I added a done callback to test case, and checked value after the handler is done. This still does not look like an ideal solution. The thing is that, handle will call 3 functions, one of them is async, I could not get it to report correct without setting a timer. There should be a solution without the timer, can anyone help with that?

Related

Hi I'm getting error "SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON"

Hi I have created a simple app that uses three apis to fetch data and render that data appropriately in the webpage. It uses node, express server, body-parser and cors, as middleware. Also I compiled all the code in webpack and ran the webpack dev-server in which the error is appearing. Here is my server.js:
// Setup empty JS object to act as endpoint for all routes
cityData = {};
weatherData = {};
picturesData = {};
// Require Express to run server and routes
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
// Start up an instance of app
const app = express();
/* Middleware*/
//Here we are configuring express to use body-parser as middle-ware.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Cors for cross origin allowance
app.use(cors())
// Initialize the main project folder
app.use(express.static('../website/client'));
app.get("/all", function sendData(req, res) {
res.send(cityData);
})
app.get("/allWeather", function sendWeather(req, res) {
res.send(weatherData);
})
app.get("/allPictures", function sendPictures(req, res) {
res.send(picturesData);
})
app.post("/addWeather", (req, res) => {
weatherData['temp'] = req.body.temp;
res.send(weatherData);
})
app.post("/addPicture", (req, res) => {
picturesData['pic'] = req.body.pic;
res.send(picturesData);
})
// Setup Server
app.listen(3000, () => {
console.log("App listening on port 3000")
console.log("Go to http://localhost:3000")
})
Here is my app.js:
const geoURL = "http://api.geonames.org/searchJSON?";
const geoUsername = `rohanasif1990`;
const weatherURL = "https://api.weatherbit.io/v2.0/forecast/daily?"
const weatherKey = "20028a8267a24bba9a807362767bc4a7"
const pixabayKey = "30776478-ff0b8818f9bba72161ebb1731"
const pixabayURL = "https://pixabay.com/api?"
const present = new Date();
const submitBtn = document.getElementById("submitBtn");
submitBtn.addEventListener("click", (e) => {
e.preventDefault();
const city = document.getElementById("city").value;
const departure = document.getElementById("date").value;
const [depart_date, depart_time] = departure.split("T")
const [depart_year, depart_month, depart_day] = depart_date.split("-")
const [depart_hour, depart_minute] = depart_time.split(":")
const future = new Date(depart_year, depart_month - 1, depart_day, depart_hour, depart_minute);
console.log(future);
console.log(present);
if (city !== "" || departTime !== "") {
document.getElementById("time").innerHTML = `Departure in ${(future - present) / 3600000 / 24} days`
getCity(geoURL, city, geoUsername)
.then(function (data) {
return getWeather(weatherURL, weatherKey, data["geonames"][0]['lat'], data["geonames"][0]['lng'])
}).then(weatherData => {
return postWeatherData("/addWeather", { temp: weatherData['data'][0]['temp'] })
}).then(function () {
return receiveWeatherData()
}).catch(function (error) {
console.log(error);
alert("Please enter a valid city and a valid time");
})
getPictures(city, pixabayURL, pixabayKey)
.then(function (picsData) {
return postPictureData("/addPicture", { pic: picsData['hits'][0]["webformatURL"] })
})
.then(function () {
return receivePictureData()
}).catch(function (error) {
console.log(error);
alert("No pictures found")
})
}
})
const getCity = async (geoURL, city, geoUsername) => {
const res = await fetch(`${geoURL}q=${city}&username=${geoUsername}`);
try {
const cityData = await res.json();
return cityData;
}
catch (error) {
console.log("error", error);
}
}
const postWeatherData = async (url = "", data = {}) => {
const response = await fetch(url, {
method: "POST",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
temp: data.temp
})
});
try {
const newData = await response.json();
return newData;
}
catch (error) {
console.log(error);
}
}
const receiveWeatherData = async () => {
const request = await fetch("/allWeather");
try {
const allData = await request.json()
document.getElementById("temp").innerHTML = "TEMPERATURE: " + allData['temp'];
}
catch (error) {
console.log("error", error)
}
}
const getWeather = async (weatherURL, weatherKey, lat, lon) => {
const res = await fetch(`${weatherURL}&lat=${lat}&lon=${lon}&key=${weatherKey}`);
try {
const weatherData = await res.json();
return weatherData;
}
catch (error) {
console.log("error", error);
}
}
const getPictures = async (city, pixabayURL, pixabayKey) => {
const query = city.split(" ").join("+");
const res = await fetch(`${pixabayURL}key=${pixabayKey}&q=${query}`);
try {
const picsData = await res.json();
return picsData;
}
catch (error) {
console.log("error", error)
}
}
const receivePictureData = async () => {
const request = await fetch("/allPictures");
try {
const allData = await request.json()
document.getElementById("city-pic").src = allData['pic'];
}
catch (error) {
console.log("error", error)
}
}
const postPictureData = async (url = "", data = {}) => {
const response = await fetch(url, {
method: "POST",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
pic: data.pic
})
});
try {
const newData = await response.json();
return newData;
}
catch (error) {
console.log(error);
}
}
To see the error I ran "npm i" to install dependencies and webpack packages. Then "npm run build-prod" to build the project dist folder. Then running "npm run build-dev". Then in incognito go to localhost:3000. And when I enter some city name like "london". I get the following errors:
screenshot of the error
I can't figure out why there are errors at lines 130 and 64. I'm new to javascript and have been doing many web development projects but never seen this kind of error. It seems like the data being sent or received is not JSON which it should be but rather an HTML file. I think the server is serving only the static HTML file and executing none of the code in app.js. Please have a look and please help me solve this issue as this is an important project of mine.
That is an error page came in response instead of the JSON.

How can I persist auth state in a nodejs app

So, I am learning NodeJs by creating this backend that fetches some data from a third-party API, the API requires auth. I couldn't figure out how to avoid sending an auth request to the third-party API whenever I wanted to fetch data from it. is there any way I could store the auth state in the app?
const axios = require("axios");
const AUTH_URL = process.env.AUTH_URL;
const REPORT_BASE_URL = process.env.REPORT_BASE_URL;
const X_API_KEY = process.env.X_API_KEY;
const getCompanies = async (req, res) => {
let idToken;
// auth
const authPayload = JSON.stringify({
// ...
});
const config = {
method: "post",
// ...
};
try {
const { data } = await axios(config);
idToken = data.idToken; // set idToken necessary for fetching companies
} catch (error) {
console.log(error);
}
// get company by full text query
const { full_text_query } = req.query;
if (!full_text_query)
return res.send("No full_text_query parameter provided");
try {
const { data } = await axios.get(
`${REPORT_BASE_URL}/companies?full_text_query=${full_text_query}`,
{
headers: {
"x-api-key": X_API_KEY,
Accept: "application/json",
authorization: idToken,
},
}
);
res.status(200).json(data);
} catch (error) {
console.log(error);
}
};
module.exports = {
getCompanies,
};
You can break out a function like fetchIdToken and store a Promise that resolves with the idToken in memory.
let idTokenPromise;
async function fetchIdToken () {
if (idTokenPromise) return idTokenPromise;
return idTokenPromise = new Promise(async (resolve) => {
...
resolve(data.idToken);
})
}
You can then use await fetchIdToken() at the start of getCompanies.
You can also just store the idToken in memory. This is slightly simpler, but does mean that you can have a race-condition when multiple getCompanies requests happen at the same time:
let idToken;
async function fetchIdToken () {
if (idToken) return idToken;
...
idToken = data.idToken;
return idToken;
}

Mocha test fails before async function in beforeEach() completes

what kind of async madness have I got myself into?
Test fails, then it logs the access token!
import assert from "assert";
import "dotenv/config";
import { expect } from "chai";
var unirest = require("unirest");
describe("some tests", function () {
let accessToken: string | undefined;
beforeEach(function () {
const email = process.env.EMAIL;
const password = process.env.PASSWORD;
const login = async function () {
const req = await unirest(
"POST",
`${process.env.API_BASE_URL}/auth/local/login`
)
.headers({
"Content-Type": "application/json",
})
.send(JSON.stringify({ email, password }))
.end(function (res: any) {
if (res.error) throw new Error(res.error);
accessToken = JSON.parse(res.raw_body).access_token;
console.log(accessToken); // this logs after the test fails? why?
});
};
login();
});
it("should be able to login and get an access token", async function () {
expect(accessToken).to.not.be.undefined;
});
});
The error is
AssertionError: expected undefined not to be undefined
I've also tried returning login() but its still logs after it fails
beforeEach(function () {
const email = process.env.EMAIL;
const password = process.env.PASSWORD;
const login = async function () {
const req = await unirest(
"POST",
`${process.env.API_BASE_URL}/auth/local/login`
)
.headers({
"Content-Type": "application/json",
})
.send(JSON.stringify({ email, password }))
.end(function (res: any) {
if (res.error) throw new Error(res.error);
accessToken = JSON.parse(res.raw_body).access_token;
console.log(accessToken); // this logs after the test fails? why?
});
};
return login();
});

Handling query in React and Express

Somewhere in my React application I used REST API to send request to the server. In my URL I want to use query (in the postIconsTransition method), but when I send a request to the server, server tells me could not found this URL (I build this error in my server). If I use this URL without any query the request in the postIconsTransition method works fine. postId and authContext.userId work fine, can anyone tell me what's wrong with my code?
In my component where I send request:
const likeHandler = async () => {
setLike(prevState => !prevState);
if (!like) {
try {
await postIconsTransition(props.postId, "inc");
} catch (error) {}
} else {
try {
await postIconsTransition(props.postId, "dec");
} catch (error) {}
}
};
In useHttp.js component:
const postIconsTransition = async (postId, addtionAddress) => {
return await transitionData(
`http://localhost:5000/post/${postId}/${authContext.userId}?t=${addtionAddress}`,
"POST",
null,
{ Authorization: `Bearer ${authContext.token}` }
);
};
transitionData method:
const transitionData = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const abortController = new AbortController();
activeHttpRequest.current.push(abortController);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: abortController.signal
});
const responseData = await response.json();
activeHttpRequest.current = activeHttpRequest.current.filter(
reqCtrl => reqCtrl !== abortController
);
if (!response.ok) {
throw new Error(responseData.message);
}
setIsLoading(false);
return responseData;
} catch (error) {
modalContext.err(error);
setIsLoading(false);
throw error;
}
},
[modalContext.err]
);
In Express:
router.post(
"/:postId/:userId?t=inc",
tokenChecker,
postController.updateLikesComments
);
router.post(
"/:postId/:userId?t=dec",
tokenChecker,
postController.updateLikesComments
);
All of them work fine but when I use query in my URL, it's not working any more.
You don't specify query parameters in express routes like that. Just send them. Express can read it.
router.post(
"/:postId/:userId",
tokenChecker,
postController.updateLikesComments
);
// Notice that you don't need the other one.
and in your controller check the parameter
// controller's code
const t = req.query.t;
if (t === 'inc') {
// do what you want here
}
if (t === 'dec') {
// do what you want here
}

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');
})

Categories

Resources