server and db connection once before all test files jest - node.js

I have multiple test files in my project. Each test file has almost similar beforeAll and afterAll functions which opens server and db connections and terminate them in the afterAll function.
I was getting error for server connection which said the port is already in use and I was able to handle that by not specifying a port number in test environment. However I'm unable to solve for db.
The lines below in .test.js file are common in all test files. The test itself is a dummy test. I need to have access to server in order to call the APIs there.
.test.js
const db = require('../service/pg');
const createServer = require('../service/server');
let server;
let sequelize;
beforeAll(async () => {
sequelize = await db.init();
server = await createServer();
await server.start();
});
afterAll(async () => {
await server.stop();
await sequelize.drop();
await sequelize.close();
});
describe('testing...', () => {
test('one + one = 2', async () => {
const options = {
method: 'POST',
url: '/math',
};
const res= await server.inject(options);
expect(1).toEqual(1);
});
server.js
if (process.env.NODE_ENV !== 'test') {
server = Hapi.server({
port: process.env.PORT || 5000,
routes: { cors: true },
});
}
db.js
const sequelize = require('./sequelize');
await sequelize.sync();
sequelize.js
const user_db = new Sequelize(
env.dbName,
env.user,
env.password,
{
host: env.host,
port: env.port,
dialect: 'postgres',
logging: false,
}
);
.... // add models
Ideal solution would be initializing server and DB only once for all test files. Some global setup stuff. But I'm unable to do that.
If I have like 2 test files, one test file works file but the other would give error like:
console.error
Connection to the database failed: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error

Instead of setting up your db in the beforeAll do it outside of the describe scope once and stop the servers afterwards.
const db = require('../service/pg');
const createServer = require('../service/server');
let server;
let sequelize;
(async () => {
sequelize = await db.init();
server = await createServer();
await server.start();
})();
describe('testing...', () => {
test('one + one = 2', async () => {
const options = {
method: 'POST',
url: '/math',
};
const res= await server.inject(options);
expect(1).toEqual(1);
});
});
(async () => {
await server.stop();
await sequelize.drop();
await sequelize.close();
})();
If I have like 2 test files, one test file works file but the other would give error like: console.error Connection to the database failed: UniqueConstraintError [SequelizeUniqueConstraintError]: Validation error
The reason for this is that jest runs tests in parallel and it could be that your second test starts before the first one has been finished and the afterAll has been called.

Related

node-media-server: session.reject() not working

I am trying to create an RTMP-server with the npm package: http://github.com/illuspas/Node-Media-Server. So the server works fine but I need to implement authentication in it. I am trying to check the authentication on "prePublish" event. I am querying the database and retrieving the user if the user was found then I want to let the user stream otherwise rejected. But the problem is, it doesn't leave it instead disconnects and then the stream automatically reconnected to it then it disconnects again and the loop goes on. How do I fix this problem?
Here is the code for the event:
const NodeMediaServer = require('node-media-server');
const config = require('./config').rtmp_server;
const db = require('./db');
const nms = new NodeMediaServer(config);
const getStreamKeyFromStreamPath = (path) => {
const parts = path.split('/');
return parts[parts.length - 1];
};
nms.on('prePublish', async (id, StreamPath, args) => {
const session = nms.getSession(id);
try {
const streamKey = getStreamKeyFromStreamPath(StreamPath);
const validStream = (
await db.query('SELECT * FROM public."People" WHERE stream_key = $1', [streamKey])
).rows[0];
console.log(validStream);
if (validStream) {
// do stuff
} else {
session.reject((reason) => {
console.log(reason);
});
}
console.log(
'[NodeEvent on prePublish]',
`id=${id} StreamPath=${StreamPath} args=${JSON.stringify(args)}`
);
} catch (err) {
session.reject();
}
});
module.exports = nms;
Here is the code of the entry point of the server:
require("dotenv").config();
const db = require("./db");
const nms = require("./nms");
// database connection
db.connect()
.then(() => {
console.log("Connected to database");
// start the rtmp server
nms.run();
})
.catch((err) => console.log(err.message));
Here is the db file:
const { Pool } = require('pg');
const connectionString = process.env.PG_CONNECTION_STRING;
const poolOptions = {
host: process.env.PG_HOST,
user: process.env.PG_USER,
port: process.env.PG_PORT,
password: process.env.PG_PASSWORD,
database: process.env.PG_DATABASE,
};
const pool = new Pool(process.env.NODE_ENV === 'production' ? connectionString : poolOptions);
module.exports = pool;
My procedures to solve that problem:
Instead of the async function, I tried to handle the database query using a callback but it didn't work.
Before I was calling session.reject() now I am passing a callback there but the behavior is still the same
If you have any solution for that, please let me know.
Thanks in advance

Jest + Supertest - API tests are using random ephemeral ports

I'm using Restify for my API and I'm trying to write tests for my endpoints.
At first, I had only a test for ping and it was okay, but now, after I added a new test, supertest is trying to ephemeral ports to the test server (80201).
I've searched a lot and tried some approaches that seem to work for most people but not for me. I'm probably messing something up, but I have no clue what it could be.
Check out my code:
server.js
require('dotenv').config();
const config = require('./config');
const routes = require('./src/routes');
const cors = require('restify-cors-middleware');
const http = require('http');
const https = require('https');
const restify = require('restify');
module.exports = function () {
http.globalAgent.keepAlive = true;
http.globalAgent.maxSockets = 256;
https.globalAgent.keepAlive = true;
https.globalAgent.maxSockets = 256;
const _cors = cors({
preflightMaxAge: 5,
origins: [new RegExp("^(https?:\/\/)?[-\w]+\.hackz\.co(\.\w+)?(:[\d]+)?$")],
allowHeaders: [
'authorization',
'x-requested-with',
'Content-MD5',
'Date',
'Accept-Version',
'Api-Version',
'Response-Time'
],
credentials: true
});
const server = restify.createServer({ name: config.apiName });
// Middlewares
server.pre(_cors.preflight);
server.use(_cors.actual);
server.use(restify.plugins.fullResponse());
server.use(restify.plugins.queryParser({ mapParams: true }));
server.use(restify.plugins.bodyParser({ mapParams: true }));
// Load Routes
routes.set(server);
server.on('error', function (req, res, route, error) {
if (error && (error.statusCode == null || error.statusCode !== 404)) {}
});
// Start Server
server.listen(config.apiPort, function () {
console.log(`${server.name} listening at ${server.url}.\nWe're in ${config.env} environment!`);
});
return server;
}();
tests/config/server.js
const server = require('../..');
const request = require('supertest');
function TestServer() {
return request(server);
}
module.exports = { TestServer };
tests/services/request.js
const { TestServer } = require("../config/server");
async function get(path, sessionkey = '', params = {}) {
const server = TestServer();
return await server
.get(path)
.query(params)
.set("authorization", sessionkey)
.set("content-type", "application/json")
;
}
async function post(path) {
const server = TestServer();
return await server
.post(path)
.set("content-type", "application/json")
;
}
module.exports = {
get,
post,
};
tests/config/setup.js
const server = require('../..');
afterAll(() => {
return server.close()
});
src/controllers/Ping.test.js
const { get } = require('../../tests/services/request');
describe('Ping Controller', () => {
describe('GET /ping', () => {
it('Should return 200', async () => {
const response = await get('/ping');
expect(response.status).toBe(200);
});
});
});
src/controllers/Session.test.js
const { post } = require('../../tests/services/request');
describe('Session Controller', () => {
const userId = 1;
describe('POST /:userId/create', () => {
it('Should create session successfully!', async () => {
const response = await post(`${userId}/create`);
expect(response.status).toBe(200);
expect(response.body.auth).toBe(true);
});
});
});
package.json (scripts and Jest config)
...
"scripts": {
"start": "node index.js",
"test": "jest --detectOpenHandles --forceExit --coverage",
"test:api": "npm run test -- --roots ./src/controllers"
},
...
"jest": {
"setupFilesAfterEnv": [
"jest-extended",
"<rootDir>/tests/config/setup.js"
],
...
}
This is the error output:
> user-session-api#1.0.0 test
> jest --detectOpenHandles --forceExit --coverage
FAIL src/controllers/Session.test.js
Session Controller
POST /:userId/create
✕ Should create session successfully! (39 ms)
● Session Controller › POST /:userId/create › Should create session successfully!
RangeError: Port should be >= 0 and < 65536. Received 80201.RangeError [ERR_SOCKET_BAD_PORT]: Port should be >= 0 and < 65536. Received 80201.
Things I've tried:
Passing the result of server.listen(...) (instead of the server instance) to supertest (as described here);
Using beforeEach for each test manually listening to a specific port;
This approach, which is similar to the first item.
HELP!
UPDATE:
Just realized that running npm run test "Ping.test.js" it succeeds and running npm run test "Session.test.js" (which is the new test) it fails. So there's probably something wrong with that single file.
I was also getting this error:
RangeError [ERR_SOCKET_BAD_PORT]: Port should be >= 0 and < 65536. Received 80201
Using supertest and Jest to test a NestJS application. I was also doing a
return request(app.getHttpServer())
.get(`route`)
Instead of
return request(app.getHttpServer())
.get(`/route`)
OH MY GOD!
I found the issue and, prepare yourself, the solution is ridiculous.
The request path in my test had a typo.
I was doing this:
const response = await post(`${userId}/create`); // with userId as 1
The path was missing an initial /, that's it haha.
THAT'S why supertest was appending a 1 to the server port and the RangeError was raised.
I'm hating myself right now.

How to use cls-hooked unmanaged transactions?

I'm writing tests with jest and sequelize and I need to keep my database clean for every test, so I want to set a transaction for every test and then rollback at the end.
This is what I've got, but it wont pass the transaction to my tests:
beforeEach(async () => {
this.transaction = await db.sequelize.transaction();
});
test('Database should be clean', async () => {
const role = await db.role.create({
name: 'someName',
});
expect(role.id).toBe(1);
});
afterEach(async () => {
await this.transaction.rollback();
});
Sequelize is already setted to use cls
const Sequelize = require('sequelize');
const config = require('../../config/config.js');
const cls = require('cls-hooked');
const namespace = cls.createNamespace('testing-namespace');
Sequelize.useCLS(namespace);
const sequelize = new Sequelize(config);
...
It would be really helpful if somenone could explain me how to use unmanaged transactions with cls-hooked.
I managed to keep my database clean by using umzug to run seeds programatically, this is my code:
const { db } = require('../models');
const Umzug = require('umzug');
const path = require('path');
const umzug = new Umzug({
migrations: {
path: path.join(__dirname, './../../seeders'),
params: [db.sequelize.getQueryInterface()],
},
logging: false,
});
beforeEach(async () => {
const reverted = await umzug.down({ to: 0 });
const executed = await umzug.up();
});
I tried to use umzug v3 (beta), but it didn't work, so I used the stable version 2. This approach isn't as performant as I'd like, but gets the job done.

How to solve listen EADDRINUSE: address already in use in integration tests

I am pretty new to Nodejs and i am learning Nodejs course on udemy, I am facing some trouble of listen EADDRINUSE: address already in use :::4000 while re-running integration tests multiple time. The first time its successful but afterward I am getting the above-mentioned error on the following line
const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});
I am pasting my index.js and two test files, if some can point me out it will be a great help for me.
Index.js
const Joi = require("#hapi/joi");
Joi.objectId = require("joi-objectid")(Joi);
const winston = require("winston");
const express = require("express");
const app = express();
require("./startup/logging")();
require("./startup/config")();
require("./startup/dbconnectivity")();
require("./startup/routes")(app);
const port = process.env.port || 4000;
const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});
// exporting server object to be used in integration tests.
module.exports = server;
**Integration test file for Genre**
const request = require("supertest");
let server;
const {Genere} = require("../../models/genere");
const {User} = require("../../models/user");
describe("/api/genere", () => {
beforeEach(() => {
console.log("Before each Genre");
server = require("../../index");
});
afterEach(async () => {
console.log("After each Genre");
await Genere.deleteMany({});
await server.close();
});
describe("/GET", () => {
it("should return list of generes", async() => {
await Genere.insertMany([
{name: "genre1"},
{name: "genre2"},
{name: "genre3"}
]);
const res = await request(server).get("/api/geners");
expect(res.status).toBe(200);
console.log("response body is : " + res.body);
expect(res.body.length).toBe(3);
expect(res.body.map(g => g.name)).toContain("genre1");
});
});
describe("/GET/:id", () => {
it("should return genre with id", async() => {
const genre = new Genere({name: "genre1"});
await genre.save();
const res = await request(server).get("/api/geners/"+ genre.id);
expect(res.status).toBe(200);
expect(res.body.name).toBe("genre1");
});
it("should return error with invalid id", async() => {
const genre = new Genere({name: "genre1"});
await genre.save();
const res = await request(server).get("/api/geners/1");
expect(res.status).toBe(404);
expect(res.text).toMatch(/Invalid/);
});
});
describe("/POST", () => {
it("should return 401 if not authorized", async() => {
const genere = new Genere({name: "genere1"});
const res = await request(server).post("/api/geners").send(genere);
expect(res.status).toBe(401);
});
it("should return 400 if the name is less than 4 chars", async() => {
const res = await createRequestWithGenre({name: "g1"});
expect(res.status).toBe(400);
});
it("should return 400 if the name is greater than 25 chars", async() => {
const genreName = Array(26).fill("a").join("");
const res = await createRequestWithGenre({name: genreName})
expect(res.status).toBe(400);
});
it("should return 201 with gener object if proper object is sent", async() => {
const res = await createRequestWithGenre({name: "genre1"})
expect(res.status).toBe(201);
expect(res.body).toHaveProperty("_id");
expect(res.body).toHaveProperty("name", "genre1");
const genre = await Genere.find({ name: "genre1"});
expect(genre).not.toBe(null);
});
async function createRequestWithGenre(genre) {
const token = new User().generateAuthToken();
return await request(server)
.post("/api/geners")
.set("x-auth-token", token)
.send(genre);
}
});
});
As soon as i add another file for integration test like the one below i started to get the error which is mentioned after this file code.
const {User} = require("../../models/user");
const {Genere} = require("../../models/genere");
const request = require("supertest");
let token;
describe("middleware", () => {
beforeEach(() => {
console.log("Before each Middleware");
token = new User().generateAuthToken();
server = require("../../index");
});
afterEach(async () => {
console.log("After each Middleware");
await Genere.deleteMany({});
await server.close();
});
const exec = async() => {
return await request(server)
.post("/api/geners")
.set("x-auth-token", token)
.send({name: "gener1"});
}
it("should return 400 if invalid JWT token is sent", async() => {
token = "invalid_token";
const res = await exec();
expect(res.status).toBe(400);
expect(res.text).toBe("Invalid auth token");
});
});
Console Error
middleware
✕ should return 400 if invalid JWT token is sent (510ms)
● middleware › should return 400 if invalid JWT token is sent
listen EADDRINUSE: address already in use :::4000
10 | require("./startup/routes")(app);
11 | const port = process.env.port || 4000;
> 12 | const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});
| ^
13 | // exporting server object to be used in integration tests.
14 | module.exports = server;
at Function.listen (node_modules/express/lib/application.js:618:24)
at Object.<anonymous> (index.js:12:20)
at Object.beforeEach (tests/integration/middleware.test.js:11:22)
If someone can help me why it fails on the multiple runs then it will be really helpful for me to understand why do we need to open and close server object every time.
Supertest is able to manage the setup/teardown of an express/koa app itself if you can import an instance of app without calling .listen() on it.
This involves structuring the code a little differently so app becomes a module, separate to the server .listen()
// app.js module
const app = require('express')()
require("./startup/logging")()
...
module.exports = app
Then the entrypoint for running the server imports the app then sets up the server with .listen()
// server.js entrypoint
const app = require('./app')
const port = process.env.port || 4000;
app.listen(port, () => {winston.info(`Listening on port ${port}`)});
When supertest uses the imported app, it will start its own server and listen on a random unused port without clashes.
// test
const request = require('supertest')
const app = require('./app')
request(app).get('/whatever')
The supertest "server" instance can be reused for multiple tests too
// reuse test
const supertest = require('supertest')
const app = require('./app')
describe('steps', () => {
const request = supertest(app)
it('should step1', async() => {
return request.get('/step1')
})
it('should step2', async() => {
return request.get('/step2')
})
})
One solution is to run jest with max workers specified to 1 which can be configured in your package.json in the following way:
"scripts": {
"test": "NODE_ENV=test jest --forceExit --detectOpenHandles --watchAll --maxWorkers=1"
},
If I understand your setup correctly, you have multiple intergration-test files which Jest will try to run in parallel (this is the default-mode). The error you're getting makes sense, since for each suite a new server instance is created before each test, but the server might already have been started while executing a different suite.
As described in the offical documentation instead of beforeEach it would make sense to use globalSetup where you would init your server once before running all test suites and stop the server afterwards:
// setup.js
module.exports = async () => {
// ...
// Set reference to your node server in order to close it during teardown.
global.__MY_NODE_SERVER__ = require("../../index");
};
// teardown.js
module.exports = async function() {
await global.__MY_NODE_SERVER__.stop();
};
// in your jest-config you'd set the path to these files:
module.exports = {
globalSetup: "<rootDir>/setup.js",
globalTeardown: "<rootDir>/teardown.js",
};
Alternatively you could run your tests with the --runInBand option and beforeAll instead of beforeEach in order to make sure that only one server is created before each test, but I'd recommend the first option.

Execute a middleware one-time only at server startup in Koa v2

I created this middleware which executing only once when any route in the website gets the first hit from a visitor:
// pg-promise
const db = require('./db/pgp').db;
const pgp = require('./db/pgp').pgp;
app.use(async (ctx, next) => {
try {
ctx.db = db;
ctx.pgp = pgp;
} catch (err) {
debugErr(`PGP ERROR: ${err.message}` || err);
}
await next();
});
// One-Time middleware
// https://github.com/expressjs/express/issues/2457
const oneTime = (fn) => {
try {
let done = false;
const res = (ctx, next) => {
if (done === false) {
fn(ctx, next);
done = true;
}
next();
};
return res;
} catch (err) {
debugErr(`oneTime ERROR: ${err.message}` || err);
}
};
const oneTimeQuery = async (ctx) => {
const result = await ctx.db.proc('version', [], a => a.version);
debugLog(result);
};
app.use(oneTime(oneTimeQuery));
This code executing on the first-time only when a user visiting the website, resulting:
app:log Listening on port 3000 +13ms
app:req GET / 200 - 24ms +2s
23:07:15 connect(postgres#postgres)
23:07:15 SELECT * FROM version()
23:07:15 disconnect(postgres#postgres)
app:log PostgreSQL 9.6.2, compiled by Visual C++ build 1800, 64-bit +125ms
My problem is that I want to execute it at the server start, when there's no any visit on the site.
The future purpose of this code will be to check the existence of tables in the database.
Solution:
Placing this in ./bin/www before the const server = http.createServer(app.callback()); declaration helped:
const query = async () => {
const db = require('../db/pgp').db;
const pgp = require('../db/pgp').pgp;
const result = await db.proc('version', [], a => a.version);
debugLog(`www: ${result}`);
pgp.end(); // for immediate app exit, closing the connection pool (synchronous)
};
query();
You could start your application using a js script that requires your app and uses node's native http module to fire up the server. Exactly like in koa-generator (click).
This is in your app.js file:
const app = require('koa')();
...
module.exports = app;
And then this is in your script to fire up the server:
const app = require('./app');
const http = require('http');
[this is the place where you should run your code before server starts]
const server = http.createServer(app.callback());
server.listen(port);
Afterwards you start your application with:
node [script_name].js
Of course keep in mind the async nature of node when doing it this way. What I mean by that - run the 'listen' method on 'server' variable in callback/promise.

Resources