Based on this question, I need to also make a test for a middleware which also uses the db-connection.js file. The middleware file will look like this:
const dbConnection = require('./db-connection.js')
module.exports = function (...args) {
return async function (req, res, next) {
// somethin' somethin' ...
const dbClient = dbConnection.db
const docs = await dbClient.collection('test').find()
if (!docs) {
return next(Boom.forbidden())
}
}
}
, the database connection file do not change, which is:
const MongoClient = require('mongodb').MongoClient
const dbName = 'test'
const url = process.env.MONGO_URL
const client = new MongoClient(url, { useNewUrlParser: true,
useUnifiedTopology: true,
bufferMaxEntries: 0 // dont buffer querys when not connected
})
const init = () => {
return client.connect().then(() => {
logger.info(`mongdb db:${dbName} connected`)
const db = client.db(dbName)
})
}
/**
* #type {Connection}
*/
module.exports = {
init,
client,
get db () {
return client.db(dbName)
}
}
How the middleware works is by passing list of strings (that strings is roles), I have to query to the database and check whether there is a record of each roles. If the record exists, I will return next(), while if the record does not exist, I will return next(Boom.forbidden()) (next function with a 403 status code from Boom module).
Given the details above, how does one make a test to test out the return value of the middleware if the record exists or not? This means I have to assert the next() and next(Boom.forbidden) to be exact.
Based on the answer. You can create stubs for the req, res objects, and next function.
E.g.(Doesn't run, but it should work.)
const sinon = require('sinon');
describe('a', () => {
afterEach(() => {
sinon.restore();
});
it('should find some docs', async () => {
process.env.MONGO_URL = 'mongodb://localhost:27017';
const a = require('./a');
const dbConnection = require('./db-connection.js');
const dbStub = {
collection: sinon.stub().returnsThis(),
find: sinon.stub(),
};
sinon.stub(dbConnection, 'db').get(() => dbStub);
const req = {};
const res = {};
const next = sinon.stub();
const actual = await a()(req, res, next);
sinon.assert.match(actual, true);
sinon.assert.calledWithExactly(dbStub.collection, 'test');
sinon.assert.calledOnce(dbStub.find);
sinon.assert.calledOnce(next);
});
});
Related
I want have 2 functions in report.js file I want to one is report_data which is api controller and second is test I want to use test function in report_data function.
Below is my code of both functions.
var sequelize = require('../config/sequelize');
const Op = sequelize.Sequelize.Op;
var errors = require('../config/errors');
var error = errors.errors;
const helpers = require('../helpers/validations');
const logger = require('../helpers/logger').logger;
const fs = require('fs');
module.exports = {
report_data: async (req, res) => {
if (!req.body.id) {
logger.warn(error.MANDATORY_FIELDS);
return res.status(500).send(error.MANDATORY_FIELDS)
}
sequelize.sequelize.transaction(async (t1) => {
console.log('socket connected')
test(io)
let result = error.OK
logger.info(result);
return res.status(200).send(result)
}).catch(function (err)
logger.warn(err);
console.log(err)
return res.status(500).send(error.SERVER_ERROR)
})
},
test: function (io) {
console.log(io.sockets)
}
};
The easiest would be to declare test as a named global function:
function test(io) {
console.log(io.sockets)
}
module.exports = {
report_data: async (req, res) => {
// now you call call `test()` here
},
test: test,
}
I have the following Firebase Function that makes use of Auth0 to get a user profile.
'use strict';
const {
dialogflow,
Image,
} = require('actions-on-google')
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
// database collection and key names
const DB_BANK_COLLECTION_KEY = 'bank'
// the action name from all Dialogflow intents
const INTENT_WELCOME_USER = 'Default Welcome Intent';
// Initialize the Auth0 client
var AuthenticationClient = require('auth0').AuthenticationClient;
var auth0 = new AuthenticationClient({
domain: functions.config().familybank.auth0.domain,
clientID: functions.config().familybank.auth0.clientid
});
const app = dialogflow();
app.intent(INTENT_WELCOME_USER, async (conv) => {
console.log('Request: ' + JSON.stringify(conv.request));
const userInfo = await auth0.getProfile(conv.user.access.token)
.catch( function(err) {
console.error('Error getting userProfile from Auth0: ' + err);
conv.close("Something went wrong. Please try again in a few minutes. " + err)
});
console.log('userInfo: ' + JSON.stringify(userInfo));
// check for existing bank, if not present, create it
var bankRef = db.collection(DB_BANK_COLLECTION_KEY).doc(userInfo.email);
const bankSnapshot = await bankRef.get()
})
exports.accessAccount = functions.https.onRequest(app);
I tried to mock auth0 in my tests using the following code (and several permutations), but the actual function always gets called instead of the mock.
const chai = require('chai');
const assert = chai.assert;
const sinon = require('sinon');
// Require firebase-admin so we can stub out some of its methods.
const admin = require('firebase-admin');
const test = require('firebase-functions-test')();
var AuthenticationClient = require('auth0').AuthenticationClient;
var auth0 = new AuthenticationClient({
domain: "mock",
clientID: "mock"
});
describe('Cloud Functions', () => {
let myFunctions, adminInitStub;
before(() => {
test.mockConfig({"familybank": {"auth0": {"domain": "mockdomain", "clientid": "mockid"}}});
adminInitStub = sinon.stub(admin, 'initializeApp');
sinon.stub(admin, 'firestore')
.get(function() {
return function() {
return "data";
}
});
sinon.stub(auth0, 'getProfile').callsFake( function fakeGetProfile(accessToken) {
return Promise.resolve({"email": "daniel.watrous#gmail.com", "accessToken": accessToken});
});
myFunctions = require('../index');
});
after(() => {
adminInitStub.restore();
test.cleanup();
});
describe('accessAccount', () => {
it('should return a 200', (done) => {
const req = {REQUESTDATA};
const res = {
redirect: (code, url) => {
assert.equal(code, 200);
done();
}
};
myFunctions.accessAccount(req, res);
});
});
})
Is there some way to mock auth0 for my offline tests?
I discovered that rather than initialize the Auth0 AuthenticationClient, I could first require the UsersManager, where the getProfile (which wraps getInfo) is defined.
var UsersManager = require('auth0/src/auth/UsersManager');
In my before() method, I can then create a stub for getInfo, like this
sinon.stub(UsersManager.prototype, 'getInfo').callsFake( function fakeGetProfile() {
return Promise.resolve({"email": "some.user#company.com"});
});
All the calls to auth0.getProfile then return a Promise that resolves to the document shown in my stub fake function.
i don't know if the questions is very clear, but probably looking at the code you'll understand.
i'm trying to see if the next() function was called on my router.
But everytime i use the debugger, i watch that the next i passed as a stub to my router don't get on my router as a stub, looks like it get lost on the way. If i extract the callback from the router and exports it separately, it works just fine. But i didn't want to separate things and export them.
const express = require('express');
const mostReadRouter = express.Router();
const mostReadBackEnd = require('../services/most-read-back-end');
const translateMostReadList = require('../services/most-read-service');
mostReadRouter.get('/', (req, res, next) => {
let mostReadUrl = req.query.most_read_url;
if (!mostReadUrl) {
logger.error('param most_read_url is required');
res.status(400).send('param most_read_url is required');
return;
}
let sendSucces = mostRead => {
logger.info(`sending most read list for url: ${mostReadUrl}`);
res.json(mostRead);
};
let sendError = error => {
if (isNotFoundError(error)) {
next();
} else {
next(error);
}
};
mostReadBackEnd
.getMostReadList(mostReadUrl)
.then(translateMostReadList, sendError)
.then(sendSucces, sendError)
.catch(sendError);
});
module.exports = mostReadRouter;
const chai = require('chai');
const {expect} = chai;
chai.use(require('sinon-chai'));
const sinon = require('sinon');
const sandbox = sinon.createSandbox();
const proxyQuire = require('proxyquire');
const statusStub = sandbox.stub();
const sendStub = sandbox.stub();
const getMostReadListStub = sandbox.stub();
const translateStub = sandbox.stub();
const jsonStub = sandbox.stub();
const thenStub = sandbox.stub();
process.env.CONFIGURATOR_API = 'xpto';
const router = proxyQuire('../../app/routes/most-read-router', {
'../services/most-read-back-end': {
getMostReadList: getMostReadListStub
},
'../services/most-read-service': {
translate: translateStub
}
});
describe('MostReadRouter', () => {
afterEach(() => sandbox.reset());
describe('#get(request,response,next)', () => {
it.only('should call next() when getMostReadList does not work` ', async () => {
getMostReadListStub.rejects(new Error('the error'));
let req = {
method: 'GET',
url: '/',
query: {
most_read_url: 'http://beatiful_url.com'
}
};
let res = {
json: jsonStub
};
let next = sandbox.stub();
await router(req, res, next);
expect(next).to.be.calledOnce;
});
})
});
Scenario[UPDATED]
I'm trying to connect to mongodb before running test cases and if I'm not wrong I can use beforeAll which is included in Jest where I can connect to my DB before running test cases, I am also testing my REST api with it
Test
const request = require ('supertest');
const app = require ('../../app');
const db = require ('../../db.js');
const url = 'mongodb://localhost:27017';
//UPDATED beforeALL (thanks to #andreas-köberle)
beforeAll ((done) => {
db.connect (url, (err) => {
if (err) {
console.log ('Unable to connect',err)
process.exit(1)
} else {
console.log('success')
}
});
});
test('should response the GET method', async () => {
console.log('DADAD');
const res = await request (app).get ('/expense'); // I've set /expense in app (app.use('/expense,'expenseRoute)
return expect(res.statusCode).toBe (200);
});
afterAll ( () => {
db.close ();
});
DB
const MongoClient = require ('mongodb').MongoClient;
const dbName = 'expenseTest';
let state = {
db: null,
};
exports.connect = (url, done) => {
if (state.db) return done ();
MongoClient.connect (url, (err, client) => {
const db = client.db(dbName);
state.db = db;
done ();
});
};
exports.get = () => {
return state.db;
};
exports.close = done => {
if (state.db) {
state.db.close ((err, res) => {
state.db = null;
done (err);
});
}
};
ROUTE
const express = require ('express')
const router = express.Router ()
const MongoClient = require ('mongodb').MongoClient
const assert = require ('assert')
let db = require ('../db')
/**
* Returns the expense
*/
router.get ('/', (req, res) => {
console.log(db.get());
let expenseCollection = db.get ().collection ('expenseTrack')
expenseCollection.find({}).toArray((err, docs) => {
res.status(200).send(docs)
})
//res.status(200).send('hello')
})
/**
* Stores the expense in db
*/
router.post ('/', (req, res) => {
let expenseCollection = db.get ().collection ('expenseTrack')
expenseCollection.insert (req.body, (err, result) => {
if (err) console.log (err)
else res.status (200).send (result.ops)
})
})
module.exports = router
I have console logs in Test,in my GET route and in beforeAll, here's the output of npm run test
● Console
console.log test/express/startupTest.test.js:18
DADAD
console.log routes/Expense.js:13
null
console.log test/express/startupTest.test.js:11
Succesfully
So It's clear that it's coming in Test first, If I change my endpoint code to this all test case works fine.
/**
* Returns the expense
*/
router.get ('/', (req, res) => {
// console.log(db.get());
// let expenseCollection = db.get ().collection ('expenseTrack')
// expenseCollection.find({}).toArray((err, docs) => {
// res.status(200).send(docs)
// })
res.status(200).send('hello')
})
After updating beforeAll it is now giving me another error/excpetion
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I listen to the chat event of the tmijs library, upon the !overlay chat I want to execute some code. What I want to achieve upon getting that message is:
Fetch the user
Check if the user has enough currency
Deduct currency from the user
Trigger a socket event to my react app
Everything seems to work up until the last bullet point. In my terminal it's shown that my user gets currency (called 'kluiten' in my code) deducted, but all the code that comes after it doesn't get executed.
require('dotenv').config();
const PORT = process.env.PORT || 9000;
class TwitchAPI {
constructor({io}) {
this.io = io;
this.client = new tmi.client(options);
this.client.connect();
this.handleOverlayRequest = this.handleOverlayRequest.bind(this);
this.handleChatMessage = this.handleChatMessage.bind(this);
this.client.on('chat', this.handleChatMessage);
}
handleChatMessage (channel, userstate, message) {
if(message === '!overlay') this.handleOverlayRequest(channel, userstate);
}
async handleOverlayRequest (channel, userstate) {
const requiredKluiten = 5;
const rawFoundUser = await fetch(`http://localhost:${PORT}/api/users/${userstate.username}`);
const foundUser = await rawFoundUser.json();
if(foundUser.instakluiten >= requiredKluiten) {
this.client.action(channel, `${userstate[`display-name`]}, you've got enough instakluiten for this.`);
const method = `PUT`;
const payload = { 'requiredKluiten': requiredKluiten };
const body = JSON.stringify(payload);
const headers = { 'Content-Type': `application/json; charset=utf-8` };
const result = await fetch(`http://localhost:${PORT}/api/users/${userstate.username}/decrementKluiten`, { method, body, headers });
console.log(result);
}
}
}
module.exports = TwitchAPI;
I then have an Express router:
const express = require('express');
const userController = require('../controllers/userController');
const router = express.Router();
router.route('/users/:username/decrementKluiten').put(userController.decrementKluiten);
router.route('/users/:username').get(userController.getUser);
router.route('/overview').get(userController.getOverview);
module.exports = router;
which makes sure the currency gets deducted. What I'm stuck on now is that, after all this has happened, I can't execute any code anymore after the fetch. I found though that I could execute code by resolving the promise in my route, but that feels really dirty and messes up my split up files:
router.route('/users/:username/decrementKluiten').put((req, res) => {
userController.decrementKluiten(req, res).then(x => {
console.log(x);
});
});
Is there a way to wait for my PUT to happen and still execute code after it did?
EDIT
userController.js
const {findChattersPerRole, getUserByUsername, decrementKluiten} = require('../actions');
const find = require(`lodash/find`);
const fetch = require(`isomorphic-fetch`);
const parseJSON = response => response.json();
module.exports = {
getUser: (req, res) => {
const username = req.params.username;
findChattersPerRole()
.then(chattersPerRole => {
const wantedUser = find(chattersPerRole, { username });
getUserByUsername(wantedUser.username)
.then(foundUser => {
if (foundUser) {
res.send(foundUser);
} else {
res.send(`No user has been found`);
}
});
});
},
getOverview: (req, res) => {
fetch(`https://tmi.twitch.tv/group/user/instak/chatters`)
.then(parseJSON)
.then(r => {
return res.json(r);
}).catch(err => {
console.log(err);
});
},
decrementKluiten: (req, res) => {
decrementKluiten(req.params.username, req.body.requiredKluiten);
}
}
actions.js
(Because this contains a lot of code I try to only include the relevant parts for this post, the database calls are done using Sequelize.js)
const decrementKluiten = (username, requiredKluiten) => {
return global.db.Viewer.findOne({
where: { username }
}).then(user => {
return user.decrement({ instakluiten: requiredKluiten });
});
};
module.exports = {
decrementKluiten
};
The issue is likely that you don't respond to the HTTP request in your /users/:username/decrementKluiten route. To solve this, change the exported decrementKluiten method in userController.js-file to this:
decrementKluiten: (req, res) => {
decrementKluiten(req.params.username, req.body.requiredKluiten)
.then(() => res.sendStatus(200))
.catch(() => res.sendStatus(500));
}
Some unrelated pointers to make your code a bit more readable, since you already use async functions in some parts of your code, but in other parts you interface directly with Promises.
The exported part of userController.js could utilize async functions:
module.exports = {
getUser: async (req, res) => {
try {
const username = req.params.username;
let chattersPerRole = await findChattersPerRole();
let wantedUser = find(chattersPerRole, { username });
let foundUser = await getUserByUsername(watnerUser.username);
if (foundUser) {
res.status(200).send(foundUser);
} else {
res.status(404).send('No user has been found');
}
} catch (e) {
res.sendStatus(500);
}
},
getOverview: async (req, res) => {
try {
let r = (await fetch('https://tmi.twitch.tv/group/user/instak/chatters')).json();
res.json(r);
} catch (e) {
res.sendStatus(500);
}
},
decrementKluiten: async (req, res) => {
try {
await decrementKluiten(req.params.username, req.body.requiredKluiten);
res.sendStatus(200);
} catch (e) {
res.sendStatus(500);
}
}
}
I've also added error handling in case something goes wrong, the server responds with a 500 Internal Server Error status code.
Judging by these lines in your TwitchAPI class:
const rawFoundUser = await fetch(`http://localhost:${PORT}/api/users/${userstate.username}`);
const foundUser = await rawFoundUser.json();
I assume you've tried to do const foundUser = await fetch('...').json(). This results in an error, but you can call the retuned value's methods and properties on the same line if you wrap the await expression in parentheses, like this:
const foundUser = await (await fetch('...')).json()`
If its methods does not return a Promise (i.e being synchronous), or you want to access a property, you can do:
const something = (await doSomethingAsync()).someMethod()
const somethingElse = (await doSomethingAsync()).property
I also noticed you're using template literals (backticks, `) for most strings without doing any template interpolation, which could simply be replaced with ' (single-quotes) or " (double-quotes).