Cannot unit test JavaScript functions with Tape/ Sinon - javascript

I have a module that I want to test using Tape and Sinon. Unfortunately I'm not doing very well. Here is the module code:
let config = require('./config');
let request = require('request');
let restify = require('restify');
let certificateUtils = require('utilities');
const validateTheToken = function (token, requestId, callback) {
const options = {
url: config.userServiceRootUrl + config.validationPath,
method: 'POST',
headers: {
'token': token,
'caller': config.callingService,
'x-request-id': requestId
}
};
if (typeof process.env.CA_STORE !== 'undefined') {
const certAuth = process.env.CA_STORE + '/trustedCA.pem';
options.ca = certificateUtils.getAuthorisedCerts(certAuth);
}
request(options, function (error, response, body) {
callback(error, response, body);
});
};
// add token validation middleware
const authenticateTheToken = function (req, res, next) {
if (config.enableTokenValidation) {
const receivedToken = getToken(req);
if (!receivedToken) {
return next(new restify.NotAuthorizedError('No token'));
}
validateTheToken(receivedToken, req.requestId, function (err, response, body) {
if (err || response.statusCode != 200) {
req.logger.error({
err: err,
response: response ? {
statusCode: response.statusCode,
statusMessage: response.statusMessage,
body: body
} : undefined,
}, 'validation failed');
return next(new restify.NotAuthorizedError('Not a valid token'));
} else {
return next();
}
});
}
else {
return next();
}
};
function getTheToken(req) {
if (req.headers.token) {
return req.headers.token;
} else if (req.headers.user) {
req.logger.warn({req, user: req.headers.user}, `request was sent with header 'user'`);
try {
return JSON.parse(req.headers.user).token;
} catch (e) {
req.logger.warn({user: req.headers.user}, `is not valid JSON`);
return null;
}
} else {
return null;
}
}
module.exports = {getTheToken, authenticateTheToken};
How could I first of all unit test that authenticateTheToken has been called? Here is my attempt:
test('accessed authenticateTheToken', function (t) {
const tokenAuthentication = require('../tokenAuthentication');
const authenticateToken = tokenAuth.authenticateToken;
let req = {
headers: {
token: 1
}
};
let res = {};
let next = {};
let stub = sinon.stub(tokenAuth, 'getToken');
stub.yields('next');
authenticateToken(req, res, next);
t.equal(authenticateToken.callCount, 1);
t.end();
});
When I run the test I cam getting the following error:
C:\source\my-project\tokenAuthentication.js:40
req.logger.error({
^
TypeError: Cannot read property 'error' of undefined
at C:\source\my-project\tokenAuthentication.js:40:19
at Request._callback (C:\source\my-project\tokenAuthentication.js:25:5)
at self.callback (C:\source\my-project\node_modules\request\request.js:188:22)
at emitOne (events.js:96:13)
at Request.emit (events.js:188:7)
at Request.init (C:\source\my-project\node_modules\request\request.js:234:17)
at new Request (C:\source\my-project\node_modules\request\request.js:130:8)
at request (C:\source\my-project\node_modules\request\index.js:54:10)
at validateTheToken (C:\source\my-project\tokenAuthentication.js:24:3)
at authenticateTheToken (C:\source\tokenAuthentication.js:38:5)
npm ERR! Test failed. See above for more details.

You are mocking req here, so req in your test needs to have all of the properties of req in your code. This would include the logger.
req = {
...
logger: {
warn: () => {},
error: () => {},
}
}
req probably has a lot of properties, so you may either want to create a real Request object or use another library for mocking http requests such as nock

Related

Error: function uses multiple asynchronous interfaces: callback and promise

Error: function uses multiple asynchronous interfaces: callback and
promise
to use the callback interface: do not return a promise
to use the promise interface: remove the last argument to the function
I'm trying to write a cucumber test to one of my GET node API, and keep getting the above, looked at few GitHub and stack-overflow posts, and could not understand the issue, below are my test method details.
App.ts
async getSsoId(refId: any): Promise<string> {
let ssoId = '';
const secrets = await this.vaultService.getClientSecrets();
this.decrypt(refId, secrets.encryption_secret, secrets.encryption_Id).then((value: any) => {
ssoId = value;
});
return ssoId;
}
api.get('/status', async (req, res) => {
let id;
const statusCode = 200;
try {
id = await this.getId(String('123456'));
} catch (err: any) {
throw new ApiError('Error fetching id');
}
try {
const item = await dbService.getItem(id);
if (item) {
statusCode = 201;
} else {
statusCode = 202;
}
} catch (err: any) {
throw new ApiError(
'The API encountered an error while attempting to communicate with the DB service.'
);
}
res.status(statusCode).send(statusCode);
});
Step Definition:
Given('a valid customer', function () {});
When("I call the get-id api", { timeout: 2 * 5000 }, async function (val) {
util.callGetIdAPI().then((response) => {
this.setApiResponseStatus(response.status);
this.setResponseBody(JSON.stringify(response.body));
});
});
Then("the apiResponseStatus should be <apiResponseStatus>", function (status) {
assert.strictEqual(status, this.apiResponseStatus);
});
Then("the responseBody should be {string}", function (responseBody) {
assert.equal(responseBody, this.responseBody);
});
Util Function
callGetIdAPI = async () => {
const headers = {
'Content-Type': 'application/json;v=1',
Accept: 'application/json;v=1'
}
const client = await getClient('url');
const options = {
method: 'GET',
headers: headers,
version: 3
};
let response;
try {
response = await client.get('/status', options);
return {
status: response.statusCode,
body: response.body
};
} catch(error) {
return {
status: error.statusCode,
body: {
error: {
id: error.id,
message: error.message
}
}
}
}
};
I'm new to this and trying to understand how multiple Premisses and Callbacks works in parallel, any thoughts or inputs on what possibly cause the error, or am I missing anything ??

Making a function that yields until a HTTP request finishes

I can be considered new to Node.JS so apologies, in here I am trying to make a function that yields code execution until the code finished making a HTTP request (using the "request" npm module) which then will be returned, the issue is that the library does not yield the code execution, I have tried using promise but it still won't yield code execution.
Original code:
const request = require("request")
// CONFIG
const ROBLOSECURITY = ""
var http_header = {
"Cookie": ".ROBLOSECURITY="+ROBLOSECURITY
}
function MakeRbxReq(http_method, url, payload) {
var jsonbody
var retfunc = {}
try {
jsonbody = JSON.stringify(payload)
} finally {}
var options = {
uri: "http://" + url,
body: jsonbody || "",
methpd: http_method,
headers: http_header
}
request(options, function(_, res) {
if (http_method.toUpperCase() == "POST" || http_method.toUpperCase() == "PUT" || http_method.toUpperCase() == "PATCH" || http_method.toUpperCase() == "DELETE") {
if (res.headers["X-CSRF-TOKEN"]) {
http_header["X-CSRF-TOKEN"] = res.headers["X-CSRF-TOKEN"]
options["headers"] = http_header
if (res.statusCode == 403) {
request(options, function(_, res) {
retfunc = {statusCode: res.statusCode, body: res.body}
})
} else {
retfunc = {statusCode: res.statusCode, body: res.body}
}
}
}
retfunc = {
statusCode: res.statusCode,
body: res.body
}
return
})
return retfunc
}
console.log(MakeRbxReq("GET", "search.roblox.com/catalog/json?CatalogContext=2&Subcategory=6&SortType=3&SortAggregation=5&Category=6"))
Promise attempt:
const request = require("request")
// CONFIG
const ROBLOSECURITY = ""
var http_header = {
"Cookie": ".ROBLOSECURITY="+ROBLOSECURITY
}
function MakeRbxReq(http_method, url, payload) {
var jsonbody
var retfunc = {}
try {
jsonbody = JSON.stringify(payload)
} finally {}
var options = {
uri: "http://" + url,
body: jsonbody || "",
methpd: http_method,
headers: http_header
}
async function req() {
let reqPromise = new Promise(function(resolve, reject) {
request(options, function(err, res) {
console.log("resolving")
resolve({statusCode: res.statusCode, body: res.body})
})
})
}
req()
return retfunc
}
console.log(MakeRbxReq("GET", "search.roblox.com/catalog/json?CatalogContext=2&Subcategory=6&SortType=3&SortAggregation=5&Category=6"))
Output from using promise:
C:\Program Files\nodejs\node.exe .\index.js
{}
resolving
request (promise) is asynchronous.
You should work with await or then
Here are some examples

why the result of uploader is error but never goes in .catch in requestPromise

this is my test file for upload and i explain it step by step:
I wrote a test to upload the file. the uploader method written with busboy module and it working true
but i have problem in test.
when result of uploader is error, this error never returned in .catch and go in .then.
more explain in code:
const http = require('http');
// const request = require('request');
const rp = require('request-promise');
const fs = require('fs');
const assert = require('chai').assert;
const port = process.env.PORT || 80;
const Q = require('q');
let server;
const options = {
method: 'POST',
uri: 'http://127.0.0.1/upload',
formData: {
name: 'test',
file: {
value: fs.createReadStream('./test/test.jpg'),
options: {
filename: 'test.jpg',
contentType: 'image/jpg'
}
}
},
headers: {
'Connection': 'Keep-Alive',
'content-type': 'multipart/form-data' // Is set automatically
},
json: true,
};
function startServer(port, cb) {
server = http.createServer(function (req, res) {
if (req.method === 'POST') {
if (req.url === '/upload') {
serveRequest(req, res);
}
}
});
server.listen(port, () => {
cb(function stopServer(done) {
setTimeout(function () {
server.close();
done();
}, 20);
});
console.log(`listening on port ${port} ...`);
});
}
function serveRequest(request, response) {
if (request.headers.hasOwnProperty('content-type')
&& request.headers['content-type'].split(';')[0] === 'multipart/form-data') {
serveUpload(request, response);
}
}
function serveUpload(request, response) {
uploader.upload(request, function (error, res) {
if (error) {
response.end();
}
else {
response.write(JSON.stringify(res));
response.end();
}
});
}
// -----------------------
describe('upload', function () {
let stopServer = null;
before('start server', function (done) {
startServer(port, function (stop) {
stopServer = stop;
done();
});
});
it('upload a file - options is true', function (done) {
rp(options)
.then(function (r) {
console.log(r);
})
.catch(function (error) {
console.log(error);
});
});
after('stop server', function (done) {
stopServer(done);
});
});
I make a request to the uploader and the result of my request is returned in the serveUpload() method. The result of serveUpload() is error and error is object like this :
error =
meta: {
code: '',
sourceType: 'module',
sourceName: '',
version: '2.0.4'
},
data: {
message: {
en: 'uploaded data size is out of limit'
}
}
}
this error must returned .catch(e) in the rp(options), but in fact it must go to .then(r) in rp(options)
log r in .then is undefined.
rp(options)
.then(function (r) {
console.log(r); // error always come here But in fact it must go to catch and r is undefined
})
.catch(function (error) {
console.log(error);
});
I don't understand why this is happening, I would be very grateful if anyone could help me.

Nock throws a no match for request when using Promise.race

I'm writing a test for some code that will use Promise.race to bring back a result from a graphql service that is on (could be on) multiple servers. I've used Nock to mock the request, which works fine when I'm hitting a single service. When I mock up multiple services, Nock throws an error saying
AssertionError: expected [Function] to not throw an error but 'Error: Error: Nock: No match for request {\n "method": "POST",\n "url": "http://94.82.155.133:35204",\n "headers": {\n "content-type": "application/json",\n "accept": "application/json"\n },\n "body": "{...}"\n}' was thrown
my test looks like this:
it('should make two POST requests to the service for data from graphQL', async () => {
const spy = sinon.spy(releases, '_queryGraphQL');
const releaseID = 403615894;
nock.cleanAll();
const services = serviceDetails(NUMBER_OF_SERVICES); // NUMBER_OF_SERVICES = 3
nock(serviceDiscoveryHost)
.get('/v1/catalog/service/state51')
.reply(HTTP_CODES.OK, services);
for (const service of services) {
const currentNodeHealth = nodeHealth(service.Node);
nock(serviceDiscoveryHost)
.get('/v1/health/node/'+service.Node)
.reply(HTTP_CODES.OK, currentNodeHealth);
const delayTime = Math.floor(Math.random()*1000);
nock('http://'+service.Address+':'+service.ServicePort, serviceHeaders)
.post('/')
.delay(delayTime)
.replyWithError({code: 'ETIMEDOUT', connect: false})
.post('/')
.delay(delayTime)
.reply(HTTP_CODES.OK, getReply(releaseID))
}
const actual = await releases.getRelease(releaseID)
.catch((err) => {
console.log(releases._retries);
(() => { throw err; }).should.not.throw();
});
expect(releases._retries[releaseID]).to.be.equal(1);
expect(spy.callCount).to.be.equal(2);
expect(actual).to.be.an('object')
expect(actual.data.ReleaseFormatById.id).to.be.equal(releaseID);
});
and the offending bit of code looks like
async _queryGraphQL(releaseID, services) {
if (! this._retries[releaseID]) {
this._retries[releaseID] = 0;
}
const postData = this._getReleaseQuery(releaseID);
return Promise.race(services.map( (service) => {
const options = this._getHTTPRequestOptions(service);
return new Promise((resolve, reject) => {
let post = this.http.request(options, (res) => {
let data = '';
if (res.statusCode < 200 || res.statusCode > 299) {
const msg = this.SERVICE_NAME + ' returned a status code outside of acceptable range: ' + res.statusCode;
reject(new QueryError(msg, postData));
} else {
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('error', (err) => {
reject(new QueryError(err.message, postData, err));
});
res.on('end', () => {
resolve(JSON.parse(data));
});
}
});
post.on('error', async (err) => {
if (err.code === 'ETIMEDOUT') {
if (this._retries[releaseID] &&
this._retries[releaseID] === 3) {
reject(err);
} else {
this._retries[releaseID] += 1;
resolve(this._queryGraphQL(releaseID, services));
}
} else {
reject(new QueryError(err.message, postData, err));
}
});
post.write(JSON.stringify(postData));
post.end();
});
}));
}
this.http is just require('http');. and the options will be {hostname: service.hostname} \\ example.com etc.
What I'm expecting, is that if the first service to respond, responds with an error relating to: 'ETIMEDOUT', it'll recall the function (upto 2 more times) and try all the services again until the first service to respond is something that isn't a 'ETIMEDOUT'.

Error myFunction(...).then is not a function

I have the following module that basically performs a GET request to Google:
// my-module.js
var request = require('request');
var BPromise = require('bluebird');
module.exports = get;
function get() {
return BPromise.promisify(doRequest);
}
function doRequest(callback) {
request.get({
uri: "http://google.com",
}, function (err, res, body) {
if (!err && res.statusCode == 200) {
callback(null, body);
}
else {
callback(err, null);
}
});
}
And I want to use this module like so:
//use-module.js
var myModule = require('./my-module');
myModule().then(function (body) {
console.log(body);
});
The error I'm facing is the following:
myModule(...).then is not a function.
What am I doing wrong?
BPromise.promisify(doRequest) does not call doRequest, but returns a "promisified" version of that function. You should do that once, not at each call. This should work:
module.exports = BPromise.promisify(doRequest);

Categories