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;
});
})
});
Related
**Edit: Re-written with a simple example that works first:
So I've got a test file and 2 modules.
moduleA has a dependency, moduleB
// moduleA.js
const ModuleB = require('./moduleB');
function functionA() {
return 20 + ModuleB.functionB();
};
module.exports = { functionA };
// moduleB.js
const functionB = () => {
return 10;
}
module.exports = { functionB }
My test file stubs out functionB (returned from moduleB) using proxyquire:
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('Unit Tests', function() {
it('should work', () => {
const mockedFn = sinon.stub();
mockedFn.returns(30);
const copyModuleA = proxyquire('./moduleA', {
'./moduleB': {
functionB: mockedFn
}
});
console.log(copyModuleA.functionA());
})
});
So it outputs 50 (stubbed functionB 30 + functionA 20)
Now I'm trying to take this example into my code:
moduleA in this case is a file called validation.js. It is dependent on moduleB, in this case a sequelize model, Person, with the function I want to mock: findOne
validation.js exports module.exports = { validateLogin };, a function that calls validate, which returns a function that uses Person.findOne()
So in my mind, as with the simple example, I need to create a stub, point to the validation module in proxyquire, and reference the dependency and its findOne function. Like this:
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
This should stub Person.findOne in validation.js. But it doesn't seem to. And I have no idea why.
let validationModule;
describe('Unit Tests', () => {
before(() => {
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
})
it.only('should return 422 if custom email validation fails', async() => {
const wrongEmailReq = { body: {email: 'nik#hotmail.com'} };
const res = {
statusCode: 500,
status: (code) => {this.statusCode = code; return this},
};
const validationFn = validationModule.validateLogin();
const wrongEmail = await validationFn(wrongEmailReq, res, ()=>{});
expect(wrongEmail.errors[0].msg).to.be.equal('Custom Authorisation Error');
return;
})
And this is my validation.js file:
const Person = require('../models/Person');
// parallel processing
const validate = validations => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
const error = new Error();
error.message = process.env.NODE_ENV === 'development'? 'Validation Failed':'Error';
error.statusCode = !errors.isEmpty()? 422:500;
error.errors = errors.array({onlyFirstError: true});
next(error);
return error;
};
};
const validateLogin = () => {
const validations = [
body('email')
.isString()
// snip
.custom(async (value, {req}) => {
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
})
.trim(),
];
return validate(validations);
}
module.exports = {
validateLogin
};
So the code in both the small sample and my app is correct, apart from how I stub the function. It shouldn't resolve or reject anything (I tried both out of desperation). It should return null in order to satisfy the conditional rather than jump to the catch block:
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
Hope the simple example helps someone else with proxyquire though
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);
});
});
I have this api which works fine when running locally. But, once it is deployed to Heroku i get a error 503 which is because it tries to target localhost on Heroku's server and not the user's localhost. Is there a way to make this target the user's localhost instead?
The frontend is React. Here's the code in React that fetches this api every 5sec.
axiosFunc = () => {
const { user } = this.props.auth;
console.log(user);
axios.get(`api/avaya/${user.id}`).then((res) => console.log(res));
};
timer = (time) => {
const date = new Date(time);
return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
};
componentDidMount() {
this.axiosFunc();
this.interval = setInterval(this.axiosFunc, 5000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
and this is the API on the backend with express
const router = require("express").Router();
const xml2js = require("xml2js");
const Avaya = require("../../models/Avaya");
const User = require("../../models/User");
router.route("/:id").get(async (req, res) => {
const user = await User.findById(req.params.id);
const axios = require("axios");
axios({
method: "post",
baseURL: `http://127.0.0.1:60000/onexagent/api/registerclient?name=${user.username}`,
timeout: 2000,
})
.then((reg) => {
xml2js
.parseStringPromise(reg.data, { mergeAttrs: true })
.then((result) => {
if (result.RegisterClientResponse.ResponseCode[0] === "0") {
const clientId = result.RegisterClientResponse.ClientId[0];
user.avayaClientId = clientId;
user.save();
}
const clientId = user.avayaClientId;
axios({
method: "post",
url: `http://127.0.0.1:60000/onexagent/api/nextnotification?clientid=${clientId}`,
}).then((notification) => {
xml2js
.parseStringPromise(notification.data, { mergeAttrs: true })
.then((result) => {
const notifType = [];
const notifDetails = [];
for (let i in result.NextNotificationResponse) {
notifType.push(i);
}
const arranged = {
NotificationType: notifType[1],
ResponseCode:
result.NextNotificationResponse[notifType[0]][0],
};
for (let i in result.NextNotificationResponse[
notifType[1]
][0]) {
notifDetails.push(i);
}
for (let i = 0; i < notifDetails.length; i++) {
arranged[[notifDetails[i]][0]] =
result.NextNotificationResponse[notifType[1]][0][
notifDetails[i]
][0];
}
for (let i in arranged) {
if ("Outbound" in arranged) {
arranged.CallType = "Outbound";
} else if ("Inbound" in arranged)
arranged.CallType = "Inbound";
else {
arranged.CallType = " ";
}
}
if (
arranged.NotificationType === "VoiceInteractionCreated" ||
arranged.NotificationType === "VoiceInteractionMissed" ||
arranged.NotificationType === "VoiceInteractionTerminated"
) {
const newLogs = new Avaya({
notification: arranged,
});
newLogs.owner = user;
newLogs.save();
user.avayaNotifications.push(newLogs),
user
.save()
.then((logs) => res.json(logs))
.catch((err) => res.status(400).json("Error: " + err));
} else {
res.send("Nothing to record");
}
});
});
});
})
.catch((err) => res.status(503).json(err));
});
router.route("/history/:username").get(async (req, res) => {
const user = await User.findOne({ username: [`${req.params.username}`] });
Avaya.find({ owner: [`${await user.id}`] }).then((user) => res.json(user));
});
module.exports = router;
EDIT: I was able to fix thanks to #Molda
using fetch instead of axios doesn't result in cors error.
New frontend code
getLogs = async () => {
const { user } = this.props.auth;
const reg = await fetch(
`http://127.0.0.1:60000/onexagent/api/registerclient?name=${user.id}`
);
let regData = await reg.text();
let regxml = new XMLParser().parseFromString(regData);
if (regxml.attributes.ResponseCode === "0") {
axios.post(`/api/avaya/register/${user.id}`, regxml);
console.log(regxml.attributes.ResponseCode);
}
let resp = await fetch(`/api/avaya/getid/${user.id}`);
let clientId = await resp.text();
let logs = await fetch(
`http://127.0.0.1:60000/onexagent/api/nextnotification?clientid=${clientId}`
);
let data = await logs.text();
var xml = new XMLParser().parseFromString(data);
axios.post(`/api/avaya/getlogs/${user.id}`, xml);
};
timer = (time) => {
const date = new Date(time);
return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
};
componentDidMount() {
this.getLogs();
this.interval = setInterval(this.getLogs, 5000);
}
New backend code:
const router = require("express").Router();
const Avaya = require("../../models/Avaya");
const User = require("../../models/User");
router.route("/register/:id").post(async (req, res) => {
const user = await User.findById(req.params.id);
const clientId = req.body.attributes.ClientId;
user.avayaClientId = clientId;
user.save();
});
router.route("/getid/:id").get(async (req, res) => {
const user = await User.findById(req.params.id);
res.send(user.avayaClientId);
});
router.route("/getlogs/:id").post(async (req, res) => {
const user = await User.findById(req.params.id);
const arranged = {
NotificationType: req.body.children[0].name,
ResponseCode: req.body.attributes.ResponseCode,
CallType: " ",
};
for (let i in req.body.children[0].attributes) {
if (i === "Outbound") {
arranged.CallType = "Outbound";
}
if (i === "Inbound") {
arranged.CallType = "Inbound";
}
arranged[i] = req.body.children[0].attributes[i];
}
console.log(arranged);
if (
arranged.NotificationType === "VoiceInteractionCreated" ||
arranged.NotificationType === "VoiceInteractionMissed" ||
arranged.NotificationType === "VoiceInteractionTerminated"
) {
const newLogs = new Avaya({
notification: arranged,
});
newLogs.owner = user;
newLogs.save();
user.avayaNotifications.push(newLogs),
user
.save()
.then((logs) => res.json(logs))
.catch((err) => res.status(400).json("Error: " + err));
} else {
res.send("Nothing to record");
}
});
router.route("/history/:username").get(async (req, res) => {
const user = await User.findOne({ username: [`${req.params.username}`] });
Avaya.find({ owner: [`${await user.id}`] }).then((user) => res.json(user));
});
module.exports = router;
I really don't get the part of (requesting with Axios in API)
Is this a third party API ?
But I suggest you to use (.env) which is a file in your root folder contains the development config like base URLs, expire tokens, API keys ... etc
and when you upload to Heroku you have to make a (.env) in Heroku app and but your config
Let's take an example
in my development mode, my .env looks like
app_url = localhost:4000
port = 4000
db = development_api
db_username = root
db_password =
db_engine = mysql2
in my production mode, my .env looks like
app_url = http://appsomething.heroku.com
port = 80
db = production_api
db_username = root
db_password = 3210LDWAK#AALKQ
db_engine = mysql2
and read more about how to use .ENV
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.
This is the module that collections and exports async data: scraper.js
const express = require('express')
const cheerio = require('cheerio')
const request = require("tinyreq")
const fs = require('fs')
const _ = require('lodash')
const uuid = require('uuid/v4')
const async = require('async')
const mental_models = {
url: 'https://www.farnamstreetblog.com/mental-models/',
data: {}
}
const decision_making = {
url: 'https://www.farnamstreetblog.com/smart-decisions/',
data: {}
}
const cognitive_bias = {
url: 'https://betterhumans.coach.me/cognitive-bias-cheat-sheet-55a472476b18',
data: {}
}
const DATA_URLS = [mental_models, decision_making, cognitive_bias]
const filterScrape = async (source, params) => {
let filtered_data = {
topics: [],
content: [],
additional_content: []
}
let response = await scrape(source)
try {
let $ = cheerio.load(response)
params.forEach((elem) => {
let headers = ['h1', 'h2', 'h3']
if ($(elem) && headers.includes(elem)) {
let topic = {}
let content = {}
let id = uuid()
topic.id = id
topic.text = $(elem).text()
if ($(elem).closest('p')) {
content.text = $(elem).closest('p').text()
content.id = id
}
filtered_data.topics.push(topic)
filtered_data.content.push(content)
} else if ($(elem) && !headers.includes(elem)) {
let content = {}
let id = uuid()
content.text = $(elem).text()
content.id = id
filtered_data.additional_content.push(content)
} else {
}
})
}
catch (err) {
console.log(err)
}
return filtered_data
}
const scrape = (source) => {
return new Promise((resolve, reject) => {
request(source.url, function (err, body) {
if (err) {
reject(err)
return
}
resolve(body)
})
})
}
const DATA = _.map(DATA_URLS, async (source) => {
let params = ['h1', 'h2', 'h3', 'p']
let new_data = await filterScrape(source, params)
try {
source.data = new_data
}
catch (err) {
console.log(err)
}
})
module.exports = DATA
This is the module that imports the data: neural.js
const brain = require('brain')
const neural_net = new brain.NeuralNetwork()
const DATA = require('./scraper')
console.log(DATA)
Obviously not much going on, I've removed the code since the variable doesn't resolve. When logged it logs a promise but the promise does not resolve. However in the imported module, the promise is logged and then resolves. What gives? Should I import a function that resolves the data?
Of course it would be best to import that function, however it won't change the issue in your code which is here:
const DATA = _.map(DATA_URLS, async (source) => {
Lodash doesn't support async iteration - so you need to have some other method, one would be to use the newest nodejs version (10.x) and make use of async iteration - but that won't use the full power of asynchronous code.
You can also use scramjet - a framework my company is supporting. The code above would take the following form:
const {DataStream} = require("scramjet");
const DATA_URLS = [mental_models, decision_making, cognitive_bias];
module.exports = async () => DataStream.fromArray(DATA_URLS)
.setOptions({maxParallel: 2}) // if you need to limit that at all.
.map(async ({url}) => {
let params = ['h1', 'h2', 'h3', 'p']
let data = await filterScrape(source, params);
return { url, data };
})
.toArray();
The other file would take the following form:
const brain = require('brain')
const neural_net = new brain.NeuralNetwork()
const scraper = require('./scraper')
(async (){
const DATA = await scraper();
console.log(DATA); // or do whatever else you were expecting...
})();