Reload express server before some mocha tests E66+ - javascript

I am building some API endpoints using node/Express and have some problems testing the endpoints.
The problem is that the server needs to be restarted (to clear data stored in memory) between tests.
What is the proper way to restart the Express server amid tests or split the tests to multiple instances?
currently, the following does not work:
it('Admin can login', () => {
chai
.request(server)
.post(`${ROOT_URL}/auth/login`)
.send({
username: 'admin',
password: 'admin'
})
.end((err, res) => {
console.log(res);
expect(res.status).eq(200);
});
});
// need to kill and resart server here
it('Registered user can login', () => {
chai
.request(server)
.post(`${ROOT_URL}/auth/login`)
.send(newUser)
.end((err, res) => {
expect(res.status).eq(200);
expect(res.body.message).eq(
`successful login as ${newUser.username}`
);
});
});

Related

How to test an API that requires authorization [Mochajs, Chai]

I am testing CRUD API-Operations (https://api.sap.com/api/OP_API_MAINTNOTIFICATION/overview?ReleaseInfo=2021%20FPS02), with mochajs and chai.
let chai = require("chai");
let chaiHttp = require("chai-http");
//Assertion Style
chai.should();
chai.use(chaiHttp);
describe("API TEST", function () {
it.only('Get all notifications', function (done) {
const url = "https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/API_MAINTNOTIFICATION/MaintenanceNotification";
/**/
chai.request(url)
.post('/auth/sign_in')
// send user login details
.send({
'user': 'ExampleEmail',
'password': 'ExamplePassword'
})
.end(function (err, res) {
var token = res.body.token;
console.log(token);
done();
});
chai.request(url)
.get('')
.set("Authorization", "Bearer " + token)
.end(function (err, res) {
done();
});
});
});
Issue
In order to test the GET function it is required for the user to authorize oneself
{"fault":{"faultstring":"Failed to resolve API Key variable request.header.apikey","detail":{"errorcode":"steps.oauth.v2.FailedToResolveAPIKey"}}}
I did the authorization in this manner / gotten the token but it doesn't work yet(the token is empty), as well as the path "/auth/sign_in" seemingly isn't working
Questions
How to do the Authorization for an API in mochajs in order to test said API ?
The answer to the question has been found.
Send the following authorization with any CRUD Operation:
.auth('Username', 'Password')
Credit: Setting Basic Auth in Mocha and SuperTest #yves-m

Connect to mySql on VPS server eventhough works locally

I have a React front-end, with a Node back-end to connect to mySql for data. It works exactly how I want locally. However, now that I'm moving it to a VPS server, I can't seem to get my configuration correct.
The error I get is: create:1 Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0. It's actually returning HTML, of the page that says it's can't find the page.
I setup an express server with this code:(I do realize I need to move login details to an ENV file... just haven't yet)
const express = require('express');
const mysql = require('mysql2');
const connection = mysql.createPool({
host : 'localhost:3306',
user : 'root',
password : 'xxxxx',
database : 'ppr'
});
const app = express();
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.get('/api/clients/all', function (req, res) {
// Connecting to the database.
connection.getConnection(function (err, connection) {
// Executing the MySQL query (select all data from the 'clients' table).
connection.query("SELECT * FROM clients", function (error, results) {
// If some error occurs, we throw an error.
if (error) throw error;
console.log(results);
res.json(results);
});
});
});
// Starting our server.
app.listen(3001, () => {
console.log('Listening on port http://localhost:3001');
});
I start this running on the server, and it runs. No errors.
But then my React app makes the first api call, and I get the error because it returns HTML instead of the data I'm expecting. Do I need to do something different to make it work in production? I have proxy setup in my local machine which probably makes it work... but what do I change for production?
Here's the API call that works locally, but not on the server if it helps:
componentDidMount() {
fetch('/api/clients/all')
.then(res => res.json())
.then((result) => {
this.setState({ clients: result });
console.log(result);
})
.then(
fetch(`/api/policy/all`)
.then(res => res.json())
.then((result) => {
this.setState({ policies: result });
console.log(result);
})
);
}

Auth0 login before Mocha/Chai test - NodeJS

I'm attempting to test a NodeJS app using Mocha and Chai. I'm using Auth0 to handle the login and signups for the app. I want to be able to test that, once logged in, the user can visit a set of pages, however I'm having trouble handling the actual logging in.
The .get('/login') redirects me to the Auth0 login page, as I expect. However, despite the .send(userCredentials) providing valid login details, it doesn't seem to login. After login, I expect it to redirect to 'localhost:3000/user' but as far as I can tell the final redirect is to the Auth0 login URL, and I'm unsure if this redirect is what prevents the send, or if the fact that the redirect takes a second or two could be causing the issue.
My test file is below.
var chai = require("chai");
var chaiHTTP = require("chai-http");
var chaiAP = require("chai-as-promised");
var server = require("../app");
var listing = require('../models/RDSModel');
var should = chai.should();
var expect = chai.expect;
var request = require("supertest");
const {Client} = require('pg');
var config = require('../config');
const connection = {
user: 'Admin',
host: process.env.DB_URL,
database: 'postgres_test',
password: config.postgresPass,
port: 5432,
};
var pg = require('knex')({
client: 'pg',
connection: connection,
searchPath: ["knex", "public"]
});
chai.use(chaiHTTP);
chai.use(chaiAP);
describe("Listing.js", function() {
beforeEach("login before each test", function(done) {
const userCredentials = {
email: 'hello#email.co.uk',
password: 'this_is_a_password!'
};
request(server)
.get('/login')
.send(userCredentials)
.end(function(err, res) {
console.log(res);
expect(res).to.have.status(302);
expect(res.body.state).to.be.true;
res.body.data.should.be.an("object");
done();
})
});
describe("get /", function() {
it("#return 200 status code", function(done) {
this.timeout(5000);
chai.request(server)
.get('/Listing/')
.end(function(err, res) {
console.log(res.body);
expect(res).to.have.status(200);
expect('Location', '/Listing')
expect(res.body).to.be.an("object");
done();
})
})
});
describe("get /Ignore", function() {
it("should return 200 status code", function(done) {
this.timeout(5000);
chai.request(server)
.get('/Listing/Ignore')
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body).to.be.an("object");
done();
})
})
})
describe("get /Report", function() {
it("should return 200 status code", function(done) {
this.timeout(5000);
chai.request(server)
.get('/Listing/Report')
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body).to.be.an("object");
done();
})
})
})
})
The reason it gives for failure is:
1) Listing.js
"before each" hook: login before each test:
Uncaught AssertionError: expected undefined to be true
at Test.<anonymous> (test/test-listing.js:44:41)
at Test.assert (node_modules/supertest/lib/test.js:181:6)
at localAssert (node_modules/supertest/lib/test.js:131:12)
at /Users/<Name>/Documents/Node/NodeStuff/node_modules/supertest/lib/test.js:128:5
at Test.Request.callback (node_modules/supertest/node_modules/superagent/lib/node/index.js:728:3)
at IncomingMessage.<anonymous> (node_modules/supertest/node_modules/superagent/lib/node/index.js:916:18)
at IncomingMessage.EventEmitter.emit (domain.js:476:20)
at endReadableNT (_stream_readable.js:1183:12)
at processTicksAndRejections (internal/process/task_queues.js:80:21)
The body of res is empty, as is the text, which seems odd to me.
Any help would be appreciated.
You are trying to integrate your test suite to an interactive login route. What you actually need is non-interactive login. With Auth0, this can be achieved by POST oauth/token endpoint You will need user's email as well as the verification code sent to that email address ( so that you cannot use unverified emails ) and client secret:
https://auth0.com/docs/api/authentication#authenticate-user
Or, you can use the Management Token obtained by Management API to execute actions.

How to export node express app for chai-http

I have an express app with a few endpoints and am currently testing it using mocha, chai, and chai-http. This was working fine until I added logic for a pooled mongo connection, and started building endpoints that depended on a DB connection. Basically, before I import my API routes and start the app, I want to make sure I'm connected to mongo.
My problem is that I'm having trouble understanding how I can export my app for chai-http but also make sure there is a DB connection before testing any endpoints.
Here, I am connecting to mongo, then in a callback applying my API and starting the app. The problem with this example is that my tests will start before a connection to the database is made, and before any endpoints are defined. I could move app.listen and api(app) outside of the MongoPool.connect() callback, but then I still have the problem of there being no DB connection when tests are running, so my endpoints will fail.
server.js
import express from 'express';
import api from './api';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 3000;
MongoPool.connect((err, success) => {
if (err) throw err;
if (success) {
console.log("Connected to db.")
// apply express router endpoints to app
api(app);
app.listen(port, () => {
console.log(`App listening on port ${port}`);
})
} else {
throw "Couldnt connect to db";
}
})
export default app;
How can I test my endpoints using chai-http while making sure there is a pooled connection before tests are actually executed? It feels dirty writing my application in a way that conforms to the tests I'm using. Is this a design problem with my pool implementation? Is there a better way to test my endpoints with chai-http?
Here is the test I'm running
test.js
let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server').default;;
let should = chai.should();
chai.use(chaiHttp);
//Our parent block
describe('Forecast', () => {
/*
* Test the /GET route
*/
describe('/GET forecast', () => {
it('it should GET the forecast', (done) => {
chai.request(server)
.get('/api/forecast?type=grid&lat=39.2667&long=-81.5615')
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
});
And this is the endpoint I'm testing
/api/forecast.js
import express from 'express';
import MongoPool from '../lib/MongoPool';
let router = express.Router();
let db = MongoPool.db();
router.get('/forecast', (req, res) => {
// do something with DB here
})
export default router;
Thank you for any help
After receiving some good feedback, I found this solution works best for me, based on Gomzy's answer and Vikash Singh's answer.
In server.js I'm connecting to the mongo pool, then emitting the 'ready' event on the express app. Then in the test, I can use before() to wait for 'ready' event to be emitted on the app. Once that happens, I'm good to start executing the test.
server.js
import express from 'express';
import bodyParser from 'body-parser';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 5000;
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
(async () => {
await MongoPool.connect();
console.log("Connected to db.");
require('./api').default(app);
app.listen(port, () => {
console.log(`Listening on port ${port}.`)
app.emit("ready");
});
})();
export default app;
test.js
//Require the dev-dependencies
import chai from 'chai';
import chaiHttp from 'chai-http';
import server from '../src/server';
let should = chai.should();
chai.use(chaiHttp);
before(done => {
server.on("ready", () => {
done();
})
})
describe('Forecast', () => {
describe('/GET forecast', () => {
it('it should GET the forecast', (done) => {
chai.request(server)
.get('/api/forecast?type=grid&lat=39.2667&long=-81.5615')
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
});
Express app is an instance of EventEmitter so we can easily subscribe to events. i.e app can listen for the 'ready' event.
Your server.js file will look like below,
import express from 'express';
import api from './api';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 3000;
app.on('ready', function() {
app.listen(3000, function() {
console.log('app is ready');
});
});
MongoPool.connect((err, success) => {
if (err) throw err;
if (success) {
console.log('Connected to db.');
// apply express router endpoints to app
api(app);
// All OK - fire (emit) a ready event.
app.emit('ready');
} else {
throw 'Couldnt connect to db';
}
});
export default app;
Just create a function below to connect to mongo and, make it returns a promise.
then use await to wait for it to connect and return. the function could be like that
function dbconnect(){
return new Promise(function(resolve, reject){
MongoPool.connect((err, success) => {
if (err) reject(err);
if (success) {
resolve({'status' : true})
} else {
reject(new Error({'status' : false}))
}
})
})
}
And then, use
await dbconnect();
api(app);
app.listen(port, () => {
console.log(`App listening on port ${port}`);
})
now await line will wait for the function to connect to DB and then return success or error in case of failure.
This is a kind of solution you can use, but I would not recommend you to do this, what we actually do is.
create services and use those services in routes, don't write DB code directly in routes.
and
while writing tests for routes mock/stub those services, and test services separately in other test cases, where you just pass DB object and service will add functions on that DB objects, so in tests you can connect to DB and pass that object to those services to test functions, it will give you additional benefit, if you want to use dummy/test DB for testing you can set that in test cases.
Use Before function in your tests like below :
describe('Forecast', () => {
before(function(done){
checkMongoPool(done); // this function should wait and ensure mongo connection is established.
});
it('/GET forecast', function(cb){
// write test code here ...
});
});
And you can check mongodb connection like this below methods:
Method 1: just check the readyState property -
mongoose.connection.readyState == 0; // not connected
mongoose.connection.readyState == 1; // connected`
Method 2: use events
mongoose.connection.on('connected', function(){});
mongoose.connection.on('error', function(){});
mongoose.connection.on('disconnected', function(){});
You can use running server instead of a express instance.
Start your server with a private port, then take tests on the running server.
ex: PORT=9876 node server.js
In your test block, use chai.request('http://localhost:9876') (replace with your protocol, server ip...) instead of chai.request(server).
If you're using native mongodb client you could implement reusable pool like:
MongoPool.js
// This creates a pool with default size of 5
// This gives client; You can add few lines to get db if you wish
// connection is a promise
let connection;
module.exports.getConnection = () => {
connection = MongoClient(url).connect()
}
module.exports.getClient = () => connection
Now in your test you could,
const { getConnection } = require('./MongoPool')
...
describe('Forecast', () => {
// get client connection
getConnection()
...
In your route:
...
const { getClient } = require('./MongoPool')
router.get('/forecast', (req, res) => {
// if you made sure you called getConnection() elsewhere in your code, client is a promise (which resolves to mongodb connection pool)
const client = getClient()
// do something with DB here
// then you could do something like client.db('db-name').then(//more).catch()
})

Avoiding Mocha timeout on assertion error with SuperTest?

I have some Sails.js API tests (using Mocha) that make use of SuperTest's .end() method to run some Chai assertions on the response.
I call the test's done() callback after the assertions, but if an assertion error is thrown, the test times out.
I can wrap the assertions in a try/finally, but this seems a bit icky:
var expect = require('chai').expect;
var request = require('supertest');
// ...
describe('should list all tasks that the user is authorized to view', function () {
it('the admin user should be able to see all tasks', function (done) {
var agent = request.agent(sails.hooks.http.app);
agent
.post('/login')
.send(userFixtures.admin())
.end(function (err, res) {
agent
.get('/api/tasks')
.expect(200)
.end(function (err, res) {
try {
var tasks = res.body;
expect(err).to.not.exist;
expect(tasks).to.be.an('array');
expect(tasks).to.have.length.of(2);
} finally {
done(err);
}
});
});
});
});
Any suggestions on how to better deal with this? Perhaps Chai HTTP might be better?
According to Supertest's documentation, you need to check for the presence of err and, if it exists, pass it to the done function. Like so
.end(function (err, res) {
if (err) return done(err);
// Any other response-related assertions here
...
// Finish the test
done();
});
You can pass out login logic from test.
// auth.js
var request = require('supertest'),
agent = request.agent;
exports.login = function(done) {
var loggedInAgent = agent(sails.hooks.http.app);
loggedInAgent
.post('/login')
.send(userFixtures.admin())
.end(function (err, res) {
loggedInAgent.saveCookies(res);
done(loggedInAgent);
});
};
And then just use it in your test:
describe('should list all tasks that the user is authorized to view', function () {
var admin;
before(function(done) {
// do not forget to require this auth module
auth.login(function(loggedInAgent) {
admin = loggedInAgent;
done();
});
});
it('the admin user should be able to see all tasks', function (done) {
admin
.get('/api/tasks')
.expect(function(res)
var tasks = res.body;
expect(tasks).to.be.an('array');
expect(tasks).to.have.length.of(2);
})
.end(done);
});
it('should have HTTP status 200', function() {
admin
.get('/api/tasks')
.expect(200, done);
});
});
With such approach you should not login your admin for each test (you can reuse your admin again and again in your describe block), and your test cases become more readable.
You should not get timeouts with this approach because .end(done) guaranties that your test will finish without errors as well with them.

Categories

Resources