Promise returning deferred.promise test using Mocha - node.js

Bot Info
SDK Platform: Node.js
Issue Description
Code Example
resolveMeetingRoomFreeTime: function (session, args) {
return new Promise(function (resolve, reject) {
kgService.getFreeMeetingRooms(session.conversationData.floorEntityList,
session.conversationData.roomEntityList,
session.conversationData.meetingStartTimestamp,
session.conversationData.meetingEndTimestamp,
session.conversationData.meetingStartTime,
session.conversationData.meetingEndTime,
session.conversationData.meetingDuration)
.then(function (roomReservationList) {
**let returnResults = roomReservationList;
session.conversationData.meetingRoomAvailabilityList = roomReservationList;
setTimeout(function () { resolve(returnResults); }, 1000);**
})
.catch(function (error) {
console.log("There is error from getFreeMeetingRooms() "+error);
});
}
)
}
Method getFreeMeetingRooms() looks like as follows.
getFreeMeetingRooms: function (nearestFloorEntity, roomEntityList, startEpoch, endEpoch, startHour, endHour, meetingDuration) {
let deferred = Q.defer();
let query = function (resolve, reject) {
graphDB.cypher({
query: cypherQuery,
params: {
nearestFloorEntity, roomEntityList, startEpoch, endEpoch, startHour, endHour, meetingDuration
}
}, function (err, results) {
return deferred.resolve(results);
});
query();
return deferred.promise;
}
My test case using Mocha and Sion Stub:
it('Should return default value for resolveMeetingRoomFreeTime()', function () {
let args = {};
let session = {
conversationData: {
roomEntityList: [{ id: "ABC", name: "ABC", capacity: 8}],
floorEntityList: "9",
meetingStartTimestamp: 1521659679948,
meetingEndTimestamp: 1521781200000,
meetingStartTime: 1514815200000,
meetingEndTime: 1514847600000,
meetingDuration: 30
}
};
//stubbing call for getFreeMeetingRooms()
var stub = sinon.stub(calendarNeo4jService, "getFreeMeetingRooms")
.withArgs(session.conversationData.floorEntityList,
session.conversationData.roomEntityList,
session.conversationData.meetingStartTimestamp,
session.conversationData.meetingEndTimestamp,
session.conversationData.meetingStartTime,
session.conversationData.meetingEndTime,
session.conversationData.meetingDuration);
var error= calendarEntityResolver.resolveMeetingRoomFreeTime(session, args)
error.then(function name(params) {
})
.then(function (error) {
});
});
Questions:
Not sure if using stub is correct if I need to skil DB call graphDB.cypher from method getFreeMeetingRooms().
How do I test block of code from resolveMeetingRoomFreeTime() which is highlighted.
If stubbing kgService.getFreeMeetingRooms is correct the how would test catch block.

I think you're asking how to test an async function using mocha. If that's true, you only need to return your promise inside the it function. Mocha will wait for the promise to resolve or reject before marking it successful, which will also allow assertions in the then function.

Related

How can I am make sure these chain of functions in Node.js are performed in order (using promises)?

I have a set of functions in Node.js that I would like to load in a certain order. I will provide some mockup code abstracted and simplified:
function updateMyApp() {
loadDataToServer()
.then(() => useData())
.then(() => saveData())
.then(() => { console.log("updateMyApp done") })
}
function loadDataToServer() {
return new Promise( (resolve, reject) {
...preparing data and save file to cloud...
resolve()})
}
function handleDataItem(item) {
// Function that fetches data item from database and updates each data item
console.log("Name", item.name)
}
function saveData() {
// Saves the altered data to some place
}
useData is a bit more complex. In it I would like to, in order:
console.log('Starting alterData()')
Load data, as json, from the cloud data source
Iterate through every item in the json file and do handleDataItem(item) on it.
When #2 is done -> console.log('alterData() done')
Return a resolved promise back to updateMyApp
Go on with saveData() with all data altered.
I want the logs to show:
Starting useData()
Name: Adam
Name: Ben
Name: Casey
useData() done
my take on this is the following:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
which seems to work but, as far as I understand this is not how one is supposed to do it. Also, this seems to do the handleDataItem outside of this chain so the logs look like this:
Starting useData()
useData() done
Name: Adam
Name: Ben
Name: Casey
In other words. It doesn't seem like the handleDataItem() calls are finished when the chain has moved on to the next step (.then()). In other words, I can not be sure all items have been updated when it goes on to the saveData() function?
If this is not a good way to handle it, then how should these functions be written? How do I chain the functions properly to make sure everything is done in the right order (as well as making the log events appear in order)?
Edit: As per request, this is handleDataItem less abstracted.
function handleDataItem(data) {
return new Promise( async function (resolve) {
data['member'] = true
if (data['twitter']) {
const cleanedUsername = twitterApi.cleanUsername(data['twitter']).toLowerCase()
if (!data['twitter_numeric']) {
var twitterId = await twitterApi.getTwitterIdFromUsername(cleanedUsername)
if (twitterId) {
data['twitter_numeric'] = twitterId
}
}
if (data['twitter_numeric']) {
if (data['twitter_protected'] != undefined) {
var twitterInfo = await twitterApi.getTwitterGeneralInfoToDb(data['twitter_numeric'])
data['twitter_description'] = twitterInfo.description
data['twitter_protected'] = twitterInfo.protected
data['twitter_profile_pic'] = twitterInfo.profile_image_url.replace("_normal", '_bigger')
data['twitter_status'] = 2
console.log("Tweeter: ", data)
}
} else {
data['twitter_status'] = 1
}
}
resolve(data)
}).then( (data) => {
db.collection('people').doc(data.marker).set(data)
db.collection('people').doc(data.marker).collection('positions').doc(data['report_at']).set(
{
"lat":data['lat'],
"lon":data['lon'],
}
)
}).catch( (error) => { console.log(error) })
}
The twitterAPI functions called:
cleanUsername: function (givenUsername) {
return givenUsername.split('/').pop().replace('#', '').replace('#', '').split(" ").join("").split("?")[0].trim().toLowerCase()
},
getTwitterGeneralInfoToDb: async function (twitter_id) {
var endpointURL = "https://api.twitter.com/2/users/" + twitter_id
var params = {
"user.fields": "name,description,profile_image_url,protected"
}
// this is the HTTP header that adds bearer token authentication
return new Promise( (resolve,reject) => {
needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
}).then( (res) => {
console.log("result.body", res.body);
if (res.body['errors']) {
if (res.body['errors'][0]['title'] == undefined) {
reject("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
reject("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
resolve(res.body.data)
}
}).catch( (error) => { console.error(error.message) })
})
},
// Get unique id from Twitter user
// Twitter API
getTwitterIdFromUsername: async function (cleanUsername) {
const endpointURL = "https://api.twitter.com/2/users/by?usernames="
const params = {
usernames: cleanUsername, // Edit usernames to look up
}
// this is the HTTP header that adds bearer token authentication
const res = await needle('get', endpointURL, params, {
headers: {
"User-Agent": "v2UserLookupJS",
"authorization": `Bearer ${TWITTER_TOKEN}`
}
})
if (res.body['errors']) {
if (res.body['errors'][0]) {
if (res.body['errors'][0]['title'] == undefined) {
console.error("Twitter API returns undefined error for :'", cleanUsername, "'")
} else {
console.error("Twitter API returns error:", res.body['errors'][0]['title'], res.body['errors'][0]['detail'])
}
} else {
console.error("Twitter API special error:", res.body)
}
} else {
if (res.body['data']) {
return res.body['data'][0].id
} else {
//console.log("??? Could not return ID, despite no error. See: ", res.body)
}
}
},
You have 3 options to deal with your main issue of async methods in a loop.
Instead of forEach, use map and return promises. Then use Promise.all on the returned promises to wait for them to all complete.
Use a for/of loop in combination with async/await.
Use a for await loop.
It sounds like there's a problem in the implementation of handleDataItem() and the promise that it returns. To help you with that, we need to see the code for that function.
You also need to clean up useData() so that it properly returns a promise that propagates both completion and errors.
And, if handleDataItem() returns a promise that is accurate, then you need to change how you do that in a loop here also.
Change from this:
function useData() {
console.log('Starting useData()')
return new Promise( function(resolve, reject) {
readFromCloudFileserver()
.then(jsonListFromCloud) => {
jsonListFromCloud.forEach((item) => {
handleDataItem(item)
}
})
.then(() => {
resolve() // I put resolve here because it is not until everything is finished above that this function is finished
console.log('useData() done')
}).catch((error) => { console.error(error.message) })
})
}
to this:
async function useData() {
try {
console.log('Starting useData()')
const jsonListFromCloud = await readFromCloudFileserver();
for (let item of jsonListFromCloud) {
await handleDataItem(item);
}
console.log('useData() done');
} catch (error) {
// log error and rethrow so caller gets the error
console.error(error.message)
throw error;
}
}
The structural changes here are:
Switch to use async/await to more easily handle the asynchronous items in a loop
Remove the promise anti-pattern that wraps new Promise() around an existing promise - no need for that AND you weren't capturing or propagating rejections from readFromCloudFileServer() which is a common mistake when using that anti-pattern.
rethrow the error inside your catch after logging the error so the error gets propagated back to the caller

Call external API until request is marked as complete

I have following situation.
I have a service which runs jobs on a remote service and exposes API for calling the same.
I have an array of jobs which needs to be executed on remote server and it works fine, when used Promises.
The flow is as below
Inside main function I get a token. On .then() of the same, I initiate my for loop for jobs and pass the JobID and token. This second function returns me the execution_ID of each job. On the .then() of this second function I pass token and execution_id. This returns me the status of the job.
My problem is; when a job is executed, it send me queued, initiated and completed as status. When the status turns to 'completed' I get results of the job; which I need to display.
I tried using a setTimeOut() inside the last function, but I'm not sure when it will end as each job may take different time.
Is there a way I can call this third function multiple time unless the status changes to 'completed'?
Below is the code
app.get('/ExecuteJobs', function (req, res) {
var commandArraay = ["job1", "job2"]
var sTokenID;
getAuthToken()
.then(function (token) {
commandArraay.forEach(function (element) {
getExecutionID(token, element)
.then(function (executionResult) {
setTimeout(() => {
getExecutionResult(token, executionResult.id, executionResult)
.then(function (updateArray) {
console.log("Final Status " + "ID: " + executionResult.id + " Status: " + executionResult.status);
// if(executionResult.)
})
}, 10000)
})
})
})
res.send('Done');
});
// Function to get the auth token
async function getAuthToken(token) {
return new Promise(function (resolve, reject) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
var pUserID = 'uname'
var pPwd = 'pwd'
performhttpsRequest('/<remote_api>/tokens', 'POST', {
sAuth: "Basic " + new Buffer(pUserID + ":" + pPwd).toString("base64")
}, "0", token,
function (data1) {
sTokenID = data1.token;
resolve(sTokenID);
})
})
}
// Function to post the command and get execution ID
async function getExecutionID(tokenID, command, executionID) {
return new Promise(function (resolve, reject) {
performhttpsRequest('/<remote_api>/executecommand', 'POST', {
command: command
}, "1", tokenID,
function (data1) {
var executionID = data1.execution_id;
resolve(executionID);
})
})
}
// Function to get the execution results for an ID
async function getExecutionResult(tokenID, executionID, result) {
return new Promise(function (resolve, reject) {
performhttpsRequest('/<remote_api>/execution_result/' + executionID, 'GET', {
}, "1", tokenID,
function (result) {
resolve(result.result);
})
})
}
If you absolutely need a result after the enqueued job is settled, then I don't see how you can have any other choice other than retrying to check the status every n-seconds.
Here's how I would do it. A recursive function that retries a request n-times, each time waiting n-seconds:
// Mock request, resolves { status: 'completed' } 20% of the time.
const request = () => {
return new Promise(resolve => {
Math.random() < 0.2
? resolve({ status: 'completed', foo: 'bar' })
: resolve({ status: 'pending' })
})
}
const retryToCompletion = async ({ wait, retries }) => {
console.log('Retries left:', retries)
const result = await new Promise((resolve, reject) => {
setTimeout(() => request().then(resolve).catch(reject), wait)
})
if (result.status === 'completed')
return result
if (retries)
return retryToCompletion({ wait, retries: --retries })
throw new Error('Retry attempts exhausted')
}
retryToCompletion({ wait: 1000, retries: 5 })
.then(console.log)
.catch(console.error)
That being said, some API's that work with BASE queues offer a handle to a WebSocket connection that notifies when a job is completed. If the API offers that, then you should ditch retrying and use the completed notification instead.
making GET calls until completion
/**
* cbk be called when..complete execution ok
*/
function completeExecution(tokenID, executionID, result, callPeriod, cbk){
return getExecutionResult(tokenID, executionID, result).then(res=>{
if(res.completed){
return cbk(null, res)
}
setTimeout(function(){
return completeExecution(cbk); //reenqueue
}, callPeriod)
}).catch(cbk) //up to you to abort or insist
}
Then you can promisify completeExecution (with util.promisify or by yourself)

How am I suppose to stub a function which is dependent on result of previous function?

I have recently started writing tests and I don't have much experience.If any of the community member could point me in the right direction I would be really thankful. My scenario is simple I am half way through it but unable to solve my exact problem. Below is my code..
return generateServiceToken(req.body.appId, req.body.token, req.auth.userId)
.then(result => {
someService
.createCredentialsForUser(
req.auth.userId,
result.user.uid,
result.user.token
)
.then(result => {
return res.status(201).send(result);
});
})
.catch(error => {
return res.status(500).send({ error: `Credentials not valid - ${error}` });
});
The generateToken function is responsible to call a third party api to generate some credentials for their platform and return us the create credentials.
function generateServiceToken(appId: String, token: String, userId: String) {
return new Promise ((resolve, reject)=>{
const apiURL = `https://someapi.com/api/api.php?op=useradd&token=${token}&addr=${userId}&appid=${appId}`;
request.post(apiURL, (error, response, body) => {
const resp = JSON.parse(body);
if (resp.error) return reject(resp.error);
return resolve(resp);
});
});
}
Whereas, the someService.createCredentialsForUser function is responsible to save those credentials in database and return back the result in simple json format.
I am just stuck in stubbing someService.createCredentialsForUser function while writing the test case for happy-path
My test case is below..
describe.only("controllers/v3/some/", () => {
const c = {};
before(() => {
c.sandbox = sinon.createSandbox();
c.someServiceStub = c.sandbox
.stub(someService, "createCredentialsForUser")
.resolves(VALID_OUTPUT);
});
describe("when the request is valid", () => {
before(() => {
c.agent = setupApp(authenticationMiddleware(USER_ID));
return test(c, VALID_REQUEST_BODY);
});
it("should return 201", () => {
expect(c.response.statusCode).to.equal(201);
});
it("should call createCredentialsForUser", () => {
expect(c.stubs.createCredentialsForUser.called).to.equal(true);
});
});
});
The TestCase function is as follows..
function testCase(context, body = VALID_REQUEST_BODY) {
context.sandbox.resetHistory();
console.log(body.length);
const c = context;
return context.agent
.put(`/v3/some/`)
.send(body)
.then(r => {
c.response = r;
});
//.catch(err=>{c.response=err});
}
My someService.createCredentialsForUser function is responsible to save data into database I want to stub that that in a way that I could expect response return from generateServiceToken
I tried couples of ways which are as follows ..
First, I tried to stub that function in before() but no luck it fails with
error : IllegalArgumentError: init() must be called prior to use.
Second, I tried
c.response = c.sandbox.stub(someService, 'createCredentialsForUser').returns(Promise.resolve(r));
in my test function to stub with the value of resolved promise but no luck in this case it fails with the same error as mentioned above.

Intensive testing of a function using mocha , node.js

i am trying to write a unit test for my node.js code . I am able to write the test for a function but i want to test each network query(query fetching data from DB) whether they are returning desired response or not.
I am sharing my node.js code as well as my test code.
node.js code(routes.js)
module.exports = {
getUser: {
get: function(req, parameters, environment) {
var id = req.queryString.id;
return new Promise(function(resolve, reject) {
db.tx(t =>{
return t.one("select configuration_value from fint.configuration where configuration_key='LOAN' and application_id =(select application_id from fint.application where application_name='Product Tailoring')")
})
.then(conf => {
conf_key = conf.configuration_value;
})
db.tx(t =>{
return t.one("select smart_account_type_id from fint.smart_account_type where smart_account_type_name='LOAN'")
})
.then(acc_type => {
smart_acc_type_id = acc_type.smart_account_type_id;
})
}
})
}
}
This is a sample code, what exactly i want is to run the test on the individual queries instead of the whole function.
My test code (test.js)
var expect = require('chai').expect;
var Promise = require('bluebird');
var chai = require('chai');
var app = require('../api/smartAccount/identifyLoan/getDirectDebitTrans/routes');
describe("Unit testing for function", function(){
it("Testing the function using mocha", function(done){
var req = {
queryString: {
uniqueUserId: 101
}
};
var test = app.getUser.get(req);
return expect(Promise.resolve(test)).to.have.property('_bitField');
done();
});
});
Any leads will be appreciated, and if anybody come across some links regarding the same, please share.
TIA
First of all, if you want to test each query separately, I would refactor the code and wrap each query in a separate function. Thus, you will be able to do unit testing over real units. If you're not able to test a part of your app easily, it's probably because something isn't designed the right way. By the way, you return a promise that never resolves (or rejects)!?
About the unit testing with promise function, you could give a try to co-mocha. Using such a flow control framework will make your promise tests much more readable.
module.exports = {
getUser: {
get: function(req, parameters, environment) {
var id = req.queryString.id;
return new Promise(function(resolve, reject) {
getConfKey(id)
.then(confKey => {
getAccType(id)
.then(accType => {
resolve({key: confKey, type: accType})
})
})
})
})
getConfKey: function(id) {
return new Promise(function(resolve, reject) {
db.tx(t =>{
return t.one("select configuration_value from fint.configuration where configuration_key='LOAN' and application_id =(select application_id from fint.application where application_name='Product Tailoring')")
})
.then(conf => {
resolve(conf.configuration_value);
})
})
}
getAccType: function(id) {
return new Promise(function(resolve, reject) {
db.tx(t =>{
return t.one("select smart_account_type_id from fint.smart_account_type where smart_account_type_name='LOAN'")
})
.then(acc_type => {
resolve(acc_type.smart_account_type_id);
})
})
}
}
}
Of course, this is an example. If both functions are independent, you can execute these concurrently. Concurrency is not the point of this question.

Nodejs and postgresql and promises and ES6

I'm pretty new to nodejs, I use just ES6 and I massively use generators.
I'm trying to promisfy the "connect" and "query" function of pg module.
Why the following snippet doesn't release the main execution context and keeps the script running from shell? It works correctly but it hangs after printing the result.
Disclaimer: I'm not interested in Bluebird.promisfy function nor pg-promise, knex and bookshelf modules. None of these fits my needs.
'use strict';
let co = require('co');
let pg = require('pg').native;
function connect(hostname, username, password, database) {
let connectionString = `postgres://${username}:${password}#${hostname}/${database}`;
return new Promise((resolve, reject) => {
pg.connect(connectionString, function(error, result, done) {
if(error)
return reject(error);
else
return resolve({ connection: result, release: done });
});
});
}
function query(connection, text, values) {
return new Promise((resolve, reject) => {
connection.query(text, values, (error, result) => {
if(error)
return reject(error);
else
return resolve(result);
});
});
}
co(function*(){
try {
let hostname = 'localhost';
let username = 'damiano';
let password = 'damiano';
let database = 'graph';
let client = yield connect('localhost', 'user', 'pass', 'graph');
let result = yield query(client.connection, `select * from vertices v1, vertices v2 limit 1`);
client.release();
return result;
}
catch(error) {
console.log(error);
}
}).then((result) => {
console.log(result.rows);
return;
});
Edit:
Ok the connection was hanging. The done method doesn't seem to do nothing, there is a hidden method .end on the connection itself.
Even since it is not answer for exact question, I'm still gonna recommend something different. I successfully use for this sequilizejs with koa and co. Sequilize's methods return promises, so I yielding them directly let rows = yield table.findAll({ where: { age: 18 }, raw: true }); And I dont have to worry about connections.

Resources