I am attempting to mock SQL Server connection pool so that I can test the function of a DAL.
I have a connection pool file
connectionPool.js
const sql = require('mssql');
const log = require('../services/logger');
const config = {
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
server: process.env.SERVER,
database: process.env.DATABASE
};
const poolPromise = new sql.ConnectionPool(config)
.connect()
.then(pool => {
log.info('Connected to SQL Server');
return pool;
})
.catch(err => {
log.error(err, 'Database connection failed');
});
module.exports = poolPromise;
and I use it in the DAL. Very stripped down, but the essentials are there.
const {poolPromise} = require('./connectionPool');
const getData = async () => {
const pool = await poolPromise;
const request = pool.request()
const result = await request('SELECT * FROM table');
}
This way, the connection pool is only created once per application. (See How can I use a single mssql connection pool across several routes in an Express 4 web application?)
I want to mock the mssql module so that the connection pool function still works. I have tried multiple options. How to mock SQL Server connection pool using Jest? gets me close, but its not quite there.
__mocks/mssql.js
const mockExecute = jest.fn();
const mockInput = jest.fn(() => ({ execute: mockExecute }));
const mockRequest = jest.fn(() => ({ input: mockInput }));
jest.mock('mssql', () => ({
ConnectionPool: jest.fn(() => ({request: mockRequest})),
NVarChar: jest.fn()
}));
const sql = require('mssql');
module.exports = sql;
However I get the error
TypeError: (intermediate value).connect is not a function
17 |
18 | const poolPromise = new sql.ConnectionPool(config)
19 | .connect()
| ^
20 | .then(pool => {
21 | log.info('Connected to SQL Server');
22 | return pool;
This may be a solution.
A bit of refactoring of connectionPool.js
const sql = require('mssql');
const log = require('../services/logger');
const config = {
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
server: process.env.SERVER,
database: process.env.DATABASE
};
if (process.env.NODE_ENV === 'development') {
config.options = {
encrypt: false,
trustServerCertificate: true
};
}
const connectionPool = new sql.ConnectionPool(config);
const poolPromise = connectionPool
.connect()
.then(pool => {
log.info('Connected to MSSQL');
return pool;
})
.catch(err => {
log.error(err, 'Database connection failed');
});
module.exports = poolPromise;
Then in /__mocks__/mssql.js
'use strict';
const mockExecute = jest.fn();
const mockInput = jest.fn().mockReturnValue({ execute: mockExecute });
const mockQuery = jest.fn().mockReturnValue({recordset: 'Mock data'});
const mockRequest = jest.fn().mockReturnValue({
input: mockInput,
query: mockQuery
});
const mockTransaction = jest.fn().mockImplementation(() => {
return {
begin: callback => callback(),
commit: jest.fn(),
rollback: jest.fn()
};
});
const mockConnect = jest.fn().mockImplementation(() => {
return Promise.resolve({ transaction: mockTransaction });
});
jest.mock('mssql', () => ({
ConnectionPool: jest.fn().mockReturnValue({
request: mockRequest,
connect: mockConnect
}),
Request: mockRequest,
NVarChar: jest.fn()
}));
const mssql = require('mssql');
module.exports = mssql;
It appears to work, but I am not sure if it is correct
Related
I have setup the node backend and try to connect to the local pgAdmin. When i try to run the Node app.js it's always shows the following error.
Error: connect ECONNREFUSED 127.0.0.1:5400
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1247:16) {
errno: -4078,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 5400
}
following is my code.
import('express').then((express) => {
import('got').then((g) => {
import('pg').then((p) => {
var cors = require('cors');
const got = g.default
const pg = p.default
const app = express.default()
app.use(cors())
const rtr = express.Router()
const masterRouter = express.Router()
const colorRouter = express.Router()
const bomRouter = express.Router()
const userRouter = express.Router()
const cdtRouter = express.Router()
const historyRouter = express.Router()
const { Pool } = pg
const baseUrl = 'https://xxxx.com/csi-requesthandler/api/v2'
const login = `${baseUrl}/session`
const customers = `${baseUrl}/customers`
const suppliers = `${baseUrl}/suppliers`
const styles = `${baseUrl}/styles`
const color_ways = `${baseUrl}/colorways`
const materials = `${baseUrl}/materials`
const boms = `${baseUrl}/apparel_boms`
const bom_revs = `${baseUrl}/apparel_bom_revisions`
const part_materials = `${baseUrl}/part_materials`
const db_user = 'admin'
const db_password = 'admin'
const db_host = 'localhost'
const db_catalog = 'postgres'
const db_port = '5400'
if (!db_user || !db_password || !db_host || !db_catalog) {
console.error('Database configuration params are missing from environment!')
process.exit(-1)
}
const pool = new Pool({
user: db_user,
host: db_host,
database: db_catalog,
password: db_password,
port: db_port
})
/**
*
* #param {*} req
* #param {*} res
* #param {()} next
* #returns call to next
*/
function tokenValidator(req, res, next) {
if (!req.headers.token) {
return res.status(400).json({ error: "Token must required" })
}
req.tokenCookie = req.headers.token
next()
}
rtr.use(express.json({ limit: '50mb' }))
rtr.use('/master', tokenValidator)
rtr.use('/master', masterRouter)
rtr.use('/color', tokenValidator)
rtr.use('/color', colorRouter)
rtr.use('/bom', tokenValidator)
rtr.use('/bom', bomRouter)
rtr.use('/user', tokenValidator)
rtr.use('/user', userRouter)
rtr.use('/cdt', cdtRouter)
rtr.use('/history', historyRouter)
app.use('/api', rtr)
app.listen(PORT, () => {
console.log(`Server is Listening of port ${PORT}`)
})
const cdt_map = []
rtr.get('/connection', (req, res) => {
return res.status(200).json({ success: 'Api Connected' });
})
rtr.post('/login', (req, res) => {
const rbody = req.body;
if (!rbody.username) {
return res.status(400).json({ error: 'Username not specified' })
}
if (!rbody.password) {
return res.status(400).json({ error: 'Password not specified' })
}
pool.query(`SELECT users.id, roles.id AS role, roles.role AS role_desc FROM users INNER JOIN roles ON roles.id = users.role WHERE LOWER(users.username) = LOWER('${rbody.username.toLowerCase().trim()}')`, (err, dbr) => {
if (err) {
return res.status(500).json({ error: 'Unable to query existance of the user', db: err })
}
if (dbr.rowCount == 0) {
return res.status(401).json({ error: 'User does not exist!' })
}
const uid = dbr.rows[0].id
const role = dbr.rows[0].role
const role_desc = dbr.rows[0].role_desc
got.post(login, { json: rbody })
.then((success) => {
const respBody = JSON.parse(success.body)
const cookie = { cookie: respBody.token, user_id: uid, type: role, desc: role_desc }
return res.contentType('application/json').send(cookie)
}, reject => {
if (reject.response.statusCode == 400) {
return res.status(400).json({ "error": "Invalid username or password" })
}
})
.catch((err) => { console.error(err) })
})
})
I can't figure out what's the error here. my locally run pgAdmin url is http://localhost:5432. i have checked and tried so many methods and still couldn't figure out the error. if anyone can help me out would be really appreciated.
Usually postgres database listens on the default port: 5432.
Change your db_port to 5432 and restart the node server.
Here is how to check which port is being used by postgres database.
sudo netstat -plunt |grep postgres
or
if you able to run psql then run this command
\conninfo
or
If you able to connect to pgAdmin (FYI pgAdmin runs on a different server on a different port from postgres database server) get the port from the server properties.
I am new to Nodejs, and trying to create a SQL helper, without success. What am I doing wrong?
db.ts:
import sql from 'mssql/msnodesqlv8'
import dotenv from 'dotenv';
dotenv.config();
//import { config } from 'winston';
const config:sql.config = {
user:process.env.SQL_USER,
password: process.env.SQL_PASSWORD,
database: process.env.SQL_DBNAME,
server: process.env.SQL_URL!,
port: +process.env.SQL_PORT!,
options: {
encrypt: false,
},
}
const poolPromise = new sql.ConnectionPool(config)
.connect()
.then(pool => {
console.log('Connected to MSSQL')
return pool
})
.catch(err => console.log('Database Connection Failed! ', err))
export{ sql, poolPromise }
user.ts
class UserDB {
async login(username:string,password:string){
try {
const pool = await poolPromise
const result = await pool.request()<--- error
.input('username', sql.NVarChar, username)
.input('password', sql.NVarChar, password)
.execute('pwr_loginTenant');
return result.recordsets;
} catch (error) {
console.log('services:user.ts login error',error);
}
}
}
const userDB = new UserDB()
module.exports = userDB;
I am getting error on request:
NODE JS - property 'request' does not exists on type 'void'
I have the following lambda handler to unit test. It uses a library #org/aws-connection which has a function mysql.getIamConnection which simply returns a knex connection.
Edit: I have added the mysql.getIamConnection function to the bottom of the post
Edit: If possible, I'd like to do the testing with only Jest. That is unless it becomes to complicated
index.js
const {mysql} = require('#org/aws-connection');
exports.handler = async (event) => {
const connection = await mysql.getIamConnection()
let response = {
statusCode: 200,
body: {
message: 'Successful'
}
}
try {
for(const currentMessage of event.Records){
let records = JSON.parse(currentMessage.body);
await connection.transaction(async (trx) => {
await trx
.table('my_table')
.insert(records)
.then(() =>
console.log(`Records inserted into table ${table}`))
.catch((err) => {
console.log(err)
throw err
})
})
}
} catch (e) {
console.error('There was an error while processing', { errorMessage: e})
response = {
statusCode: 400,
body: e
}
} finally {
connection.destroy()
}
return response
}
I have written some unit tests and I'm able to mock the connection.transaction function but I'm having trouble with the trx.select.insert.then.catch functions. H
Here is my testing file
index.test.js
import { handler } from '../src';
const mocks = require('./mocks');
jest.mock('#org/aws-connection', () => ({
mysql: {
getIamConnection: jest.fn(() => ({
transaction: jest.fn(() => ({
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis()
})),
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis(),
destroy: jest.fn().mockReturnThis()
}))
}
}))
describe('handler', () => {
test('test handler', async () =>{
const response = await handler(mocks.eventSqs)
expect(response.statusCode).toEqual(200)
});
});
This test works partially but it does not cover the trx portion at all. These lines are uncovered
await trx
.table('my_table')
.insert(records)
.then(() =>
console.log(`Records inserted into table ${table}`))
.catch((err) => {
console.log(err)
throw err
})
How can set up my mock #org/aws-connection so that it covers the trx functions as well?
Edit:
mysql.getIamConnection
async function getIamConnection (secretId, dbname) {
const secret = await getSecret(secretId)
const token = await getToken(secret)
let knex
console.log(`Initialzing a connection to ${secret.proxyendpoint}:${secret.port}/${dbname} as ${secret.username}`)
knex = require('knex')(
{
client: 'mysql2',
connection: {
host: secret.proxyendpoint,
user: secret.username,
database: dbname,
port: secret.port,
ssl: 'Amazon RDS',
authPlugins: {
mysql_clear_password: () => () => Buffer.from(token + '\0')
},
connectionLimit: 1
}
}
)
return knex
}
Solution
#qaismakani's answer worked for me. I wrote it slightly differently but the callback was the key. For anyone interested here is my end solution
const mockTrx = {
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockResolvedValue()
}
jest.mock('#org/aws-connection', () => ({
mysql: {
getIamConnection: jest.fn(() => ({
transaction: jest.fn((callback) => callback(mockTrx)),
destroy: jest.fn().mockReturnThis()
}))
}
}))
Updating your mock to look like this might do the trick:
const { mysql } = require("#org/aws-connection");
jest.mock("#org/aws-connection", () => ({
mySql: {
getIamConnection: jest.fn()
}
}));
const mockTrx = {
table: jest.fn().mockReturnThis(),
insert: jest.fn().mockResolveValue() // Resolve any data here
};
mysql.getIamConnection.mockReturnValue({
transaction: jest.fn((callback) => callback(mockTrx)),
});
You need to mock the transaction so that it executes your callback with a dummy trx. To do this, you need to make sure that all the functions inside the trx object return a reference back to it or a promise so that you can chain it appropriately.
Instead of mocking knex implementation, I've written knex-mock-client which allows you to mimic real db with an easy API.
Change your mock implementation with
import { handler } from "../src";
import { getTracker } from "knex-mock-client";
const mocks = require("./mocks");
jest.mock("#org/aws-connection", () => {
const knex = require("knex");
const { MockClient } = require("knex-mock-client");
return {
mysql: {
getIamConnection: () => knex({ client: MockClient }),
},
};
});
describe("handler", () => {
test("test handler", async () => {
const tracker = getTracker();
tracker.on.insert("my_table").responseOnce([23]); // setup's a mock response when inserting into my_table
const response = await handler(mocks.eventSqs);
expect(response.statusCode).toEqual(200);
});
});
I have a Connection class used to make a connection to a AWS RDS Aurora database instance. The class works fine but I'm having trouble getting full unit test coverage. There is one piece that I'm not sure how to cover. It is mysql_clear_password: () => () => Buffer.from(this.options.password + '\0') shown in the Connection class below. How can I cover that specific line? Is a refactor of the function necessary?
I have tried moving the Buffer function to a separate function, but the coverage report still shows that original line as being uncovered
Connection class:
const mysql2 = require('mysql2/promise');
class Connection {
constructor(options = {}) {
this.options = options;
}
createPool () {
this.pool = mysql2.createPool({
host: this.options.host,
user: this.options.user,
database: 'my_db',
ssl: 'Amazon RDS',
password: this.options.password,
authPlugins: {
mysql_clear_password: () => () => Buffer.from(this.options.password + '\0')
}
});
}
}
module.exports = { Connection };
Here is what I have so far in my test:
const conns = require('../src/connection');
const sinon = require('sinon');
const mysql2 = require('mysql2/promise');
describe('connection', () => {
afterEach(() => {
sinon.restore();
});
test('Test creatPool function from connection class', async () => {
const options = {
host: 'testHost',
user: 'testUser',
password: 'testPassword'
};
const createPoolStub = sinon.stub(mysql2, 'createPool').returns(sinon.stub().returnsThis());
const conn = new conns.Connection(options);
await conn.createPool();
sinon.assert.calledOnce(createPoolStub);
});
});
Using stub.callsFake method to make the stub(mysql2.createPool) call the provided function when invoked. Then, you can get the mysql_clear_password method from the provided function in your test case.
E.g.
connection.js:
const mysql2 = require('mysql2/promise');
class Connection {
constructor(options = {}) {
this.options = options;
}
createPool() {
this.pool = mysql2.createPool({
host: this.options.host,
user: this.options.user,
database: 'my_db',
ssl: 'Amazon RDS',
password: this.options.password,
authPlugins: {
mysql_clear_password: () => () => Buffer.from(this.options.password + '\0'),
},
});
}
}
module.exports = { Connection };
connection.test.js:
const mysql2 = require('mysql2/promise');
const conns = require('./connection');
const sinon = require('sinon');
const { expect } = require('chai');
describe('64300458', () => {
it('Test creatPool function from connection class', () => {
const options = {
host: 'testHost',
user: 'testUser',
password: 'testPassword',
};
let configRef;
const createPoolStub = sinon.stub(mysql2, 'createPool').callsFake((config) => {
configRef = config;
});
const conn = new conns.Connection(options);
conn.createPool();
sinon.assert.calledOnce(createPoolStub);
// test mysql_clear_password
const actual = configRef.authPlugins.mysql_clear_password()();
expect(actual).to.be.eql(Buffer.from('testPassword\0'));
createPoolStub.restore();
});
});
unit test result with coverage report:
64300458
✓ Test creatPool function from connection class
1 passing (11ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 100 | 0 | 100 | 100 |
connection.js | 100 | 0 | 100 | 100 | 4
---------------|---------|----------|---------|---------|-------------------
Decrypting password from environment parameter using aws.kms gets not resolved to use in pg-promise connection object. Database can not connect because of empty password. Password gets resolved after about one second from my local machine, long after the koa server is ready. I tried everything to get GraphQL wait for the database connection, but i couldn't find much information to my problem.
When using environment password direct everything works as intended.
My db.init.js
const pgp = require("pg-promise")();
const aws = require("aws-sdk");
const kms = new aws.KMS({
accessKeyId: process.env.ACCESSKEYID,
secretAccessKey: process.env.SECRETACCESSKEY,
region: process.env.REGION
});
let params = {
CiphertextBlob: Buffer.from(
process.env.ENCRYPTED_DATABASE_PASSWORD,
"base64"
)
};
module.exports = kms.decrypt(params, async (err, data) => {
const password = await data.Plaintext.toString("utf-8");
const cn = {
host: process.env.DATABASE_HOST,
port: process.env.DATABASE_PORT,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password: password,
};
return pgp(cn);
});
Works when changing db.init.js to
(using plain password instead of encrypted password):
const pgp = require("pg-promise")();
const cn = {
host: process.env.DATABASE_HOST,
port: process.env.DATABASE_PORT,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password: process.env.PLAIN_DATABASE_PASSWORD
};
module.exports = pgp(cn);
Using it in schema:
const { GraphQLSchema, GraphQLObjectType, GraphQLString} = require("graphql");
const db = require("./db.init")
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "RootQueryType",
fields: () => ({
sql: {
type: GraphQLString,
async resolve() {
return await db
.any("SELECT * FROM user;")
.then(data => data[0].name)
.catch(err => `Something went wrong: ${err}`);
}
}
})
})
});
module.exports = schema;
Server file
const Koa = require('koa');
const mount = require('koa-mount');
const graphqlHTTP = require('koa-graphql');
const schema = require('./schemas');
function createServer() {
server.use(
mount(
'/graphql',
graphqlHTTP({
schema,
graphiql: true,
})
)
);
return server;
}
Local Server
const server = require("./server");
const port = 4000;
server().listen(port, err => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}/graphql`);
});
Lambda function
const awsServerlessKoa = require('aws-serverless-koa');
const serverlessMiddleware = require('aws-serverless-koa/middleware');
const server = require('./server');
server().use(serverlessMiddleware());
module.exports.handler = awsServerlessKoa(server);
GraphQL gives failure: "db.any is not a function". In the db object in schema.js is still the unresolved aws kms object in the connection. I didn't try this as a lambda function because i have to make sure that the database is ready when the function fires.
Thanks for the comment vitaly-t, I figured it finally out:
db.init.js
const pgp = require('pg-promise')();
const aws = require('aws-sdk');
const kms = new aws.KMS({
accessKeyId: process.env.ACCESSKEYID,
secretAccessKey: process.env.SECRETACCESSKEY,
region: process.env.REGION,
});
const params = {
CiphertextBlob: Buffer.from(
process.env.DATABASE_PASSWORD,
'base64',
),
};
async function getDb() {
return kms
.decrypt(params)
.promise()
.then(async res => {
const password = await res.Plaintext.toString('utf-8');
return pgp({
host: process.env.DATABASE_HOST,
port: process.env.DATABASE_PORT,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password,
});
});
}
module.exports = getDb();
schema.js
const { importSchema } = require('graphql-import');
const { makeExecutableSchema } = require('graphql-tools');
const dbp = require('../db/init');
const schema = importSchema('src/api/schemas.graphql');
module.exports = dbp.then(db => {
const resolvers = {
Query: {
user: () => {
return db
.any('SELECT * FROM user;')
.then(data => data[0].name);
},
},
};
return makeExecutableSchema({
typeDefs: schema,
resolvers,
});
});
schemas.graphql
type Query {
user: String
}
schema {
query: Query
}
server.js
const Koa = require('koa');
const mount = require('koa-mount');
const graphqlHTTP = require('koa-graphql');
const schema = require('../api/schemas');
function createServer() {
const server = new Koa();
server.use(
mount(
'/graphql',
graphqlHTTP(async () => ({
schema: await schema,
graphiql: true,
})),
),
);
return server;
}
module.exports = createServer;
server.local.js
const server = require('./server');
const port = 4000;
server().listen(port, err => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}/graphql`);
});
Only the lambda function I didn't test. Should be straight forward.