I am trying to test my password hashing function but I keep on getting failures due to some TypeError error.
I am sure the function works since I tried to call it from some other file and I get the expected result without any error.
Here is the function:
exports.hashPassword = (password) => {
return new Promise((resolve, reject) => {
crypto.randomBytes(salt_length, (err, buf) => {
if (err) reject(err);
const salt = buf.toString('base64');
crypto.pbkdf2(password, salt, iterations, keylen, digest, (err, key) => {
if (err) reject(err);
const hashedPassword = '{X-PBKDF2}:'+digest+':'+keylenB64+':'+iterationsB64+':'+salt+':'+key.toString('base64');
resolve(hashedPassword);
});
});
});
};
Here is the test that fails:
describe('users', () => {
describe('utils', () => {
it('should hash a password', (done) => {
const password = 'secret';
utils.hashPassword('secret')
.then((hash) => {
console.log('Hash: '+ hash);
done();
})
.catch((err) => {
console.log(err);
done(err);
});
});
});
});
And this is the 'error':
1 failing
1) users utils should hash a password:
TypeError: size must be a number >= 0
at Promise (api\paths\users\utils.js:24:12)
at Object.exports.hashPassword (api\paths\users\utils.js:14:10)
at Context.it (api\paths\users\utils.test.js:30:13)
Has anyone any idea why?
I am using mocha with should and developing on node.
Thanks to pietrovismara that pointed me towards the solution.
The problem was that salt_length was a string and not a number. I was printing out the arguments and I could see they were 'correct' but obviously not the right Type (hence TypeError, I guess...)
I keep the argument in a .env file and I read them in using the dotenv package which obviously read them as simple strings (as it should)... the reason why it was working when I was 'testing' the function from another file was that in that instance I was not using the arguments read from .env, I have something like:
const salt_length = process.env.SALT_LENGTH || 128;
Mocha was correclty using .env values (strings), but when fooling around with the file I was not loading those enviroment variables.
I learned something today, that is I should go home when tired instead of charging on and not seeing things in front of my eyes.
Also, since mocha supports promises, the 'correct' test case should be (using should):
describe('users', () => {
describe('utils', () => {
it('should hash a password', () => {
return utils.hashPassword('secret').should.be.fulfilled();
});
});
});
Related
I'm trying to test a simple function that generates a random name using the nodejs crypto library. I'm using sinon to stub out a method call within the callback of pseudoRandomBytes but the stub doesn't seem to be called. Example:
getFileName.js
const crypto = require('crypto');
module.exports = (req, file, cb) => {
crypto.pseudoRandomBytes(32, (err, raw) => {
try{
cb(err, err ? undefined : crypto.createHash('MD5').update(raw).digest('hex'));
} catch(err) {
cb(err);
}
});
};
Test (running in mocha)
it('Crypto Error: createHash', function () {
const crypto = require('crypto');
const expectedError = new Error('stub error occurred');
let cryptoStub = sinon.stub(crypto, 'createHash').throws(expectedError);
let callback = sinon.spy();
getFileName(null, null, callback);
cryptoStub.restore();
sinon.assert.calledWith(callback, expectedError);
});
I would expect the above test to throw once createHash gets called. If I move the crypto.createHash call outside of the callback (before the pseudoRandomNumber call) it works just fine. I a bit of a newbie so my basic understanding of what sinon and nodejs are doing could be completely wrong. Any help would be much appreciated.
The reason why it seems like createHash() wasn't called was because you were making an assertion before the callback call was complete due to asynchronous function.
Promise with async/await will work. Another method which doesn't involve changing your module to use promise is to do your assertions within the callback.
it('Crypto Error: createHash', function (done) {
const crypto = require('crypto');
const expectedError = new Error('stub error occurred');
let cryptoStub = sinon.stub(crypto, 'createHash').throws(expectedError);
getFileName(null, null, function (err, hash) {
sinon.assert.match(err, expectedError);
cryptoStub.restore();
done();
});
});
This way, you can check that the callback is called with the expected error. One way to confirm this is you can change line 4 to .throws('some other error') and the test will fail.
The problem is that crypto.pseudoRandomBytes() is an async function, so the rest of your test code executes before your callback. That way, your stub is restored before your function is actually used it.
In order to make it properly work, you should update your getFileName.js so it returns a promise - that way you can await it
module.exports = (req, file, cb) => {
return new Promise((resolve, reject) => {
crypto.pseudoRandomBytes(32, (err, raw) => {
try{
cb(err, err ? undefined : crypto.createHash('MD5').update(raw).digest('hex'));
resolve();
} catch(err) {
reject(cb(err));
}
});
});
};
and then in your test
// added async
it('Crypto Error: createHash', async () => {
const crypto = require('crypto');
const expectedError = new Error('stub error occurred');
let cryptoStub = sinon.stub(crypto, 'createHash').throws(expectedError);
let callback = sinon.spy();
await getFileName(null, null, callback);
// once we are here, the callback has already been executed and the promise that getFileName resolved.
cryptoStub.restore();
sinon.assert.calledWith(callback, expectedError);
});
Hi I have strange problem while testing code with Mocha:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves
Here is code:
describe('POST /notes', () => {
it('should create new note', (done) => {
const title = 'Test title';
const text = 'Test text';
const category = 'Test category';
request(app)
.post('/notes')
.send({title, text, category})
.expect(200)
.expect(res => {
expect(res.body.title).toBe(title);
})
.end((err, res) => {
if (err)
return done(err);
Note.find({text: text}).then(notes => {
expect(notes.length).toBe(1);
expect(notes[0].title).toBe(title);
done();
}).catch(err => done(err));
});
});
it('should not create new note with invalid body data', done => {
request(app)
.post('/notes')
.send({})
.expect(400)
.end((err, res) => {
if (err)
return done(err);
Note.find().then(notes => {
expect(notes.length).toBe(notesDummy.length);
done();
}).catch(err => done(err));
});
})
});
First test fails with error described above. As it comes to the second, it passes. Both tests are similar and I don't know what I am missing... Any ideas ?
If you are interacting with a live database, it's possible that the operation just takes longer than 2 seconds to complete. The test for a successful operation would take longer than the negative test if you have server-side validation happening before interacting with the database.
You can extend the timeout in mocha using this.timeout(<some number in milliseconds>) :
it('should create new note', (done) => {
this.timeout(9000); // set it to something big to see if it fixes your issue
const title = 'Test title';
const text = 'Test text';
const category = 'Test category';
request(app)
.post('/notes')
.send({title, text, category})
.expect(200)
.expect(res => {
expect(res.body.title).toBe(title);
})
.end((err, res) => {
if (err)
return done(err);
Note.find({text: text}).then(notes => {
expect(notes.length).toBe(1);
expect(notes[0].title).toBe(title);
done();
}).catch(err => done(err));
});
});
The only other thing I can think of is that your server side code is hanging somewhere and not sending a response (or that Notes.find() is not resolving or rejecting for some reason). Your test code looks fine to me.
I'm not 100% sure that the expects outside of promise-land at the beginning will cause done to be called if they fail:
.expect(200)
.expect(res => {
expect(res.body.title).toBe(title);
})
Maybe add some logging to see whether it ever gets to your .end handler?
Also, what are you importing to get .expect methods on your request? We should look at its docs to see about done hooks.
Following the documentation at
http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html
I am trying to write a basic test against my DynamoDB tables using promises to handle the asynchronous response with no luck thus far. It should return an object containing the TableNames.
If I access the DynamoDB object without using promises, I have no problem.
This works:
import AWS from 'aws-sdk'
AWS.config.loadFromPath('./awsConfig.json')
const dynamo = new AWS.DynamoDB({region: 'us-east-2'})
window.test('Test Connection', () => {
return dynamo.listTables({Limit: 10}, (err, data) => {
if (err) {
console.log('err', err)
} else {
console.log('TableNames are:', data)
}
window.expect(data).toEqual(window.expect.anything())
})
})
This does not:
import AWS from 'aws-sdk'
AWS.config.loadFromPath('./awsConfig.json')
const dynamo = new AWS.DynamoDB({region: 'us-east-2'})
window.test('Test Connection', () => {
let tables
const listPromise = dynamo.listTables({Limit: 10}).promise()
listPromise.then((data) => {
tables = data
console.log('TableNames are:', data)
}).catch((err) => {
console.log('Error:', err)
})
window.expect(tables).toEqual(window.expect.anything())
})
The value of data is always undefined. I cannot find any documentation that would indicate that promises are not supported with DynamoDB requests, so I am just about certain that I'm missing something obvious...
Any help is appreciated. This is my first time asking a question in this forum, so feedback on the question itself is also appreciated.
EDIT: Problem Solved - rookie mistake
The issue was not with dynamoDB, but rather with using test. When dealing with promises, the promise must be returned to test for proper evaluation. Otherwise, it cannot be evaluated.
Working Code:
import AWS from 'aws-sdk'
AWS.config.loadFromPath('./awsConfig.json')
const dynamo = new AWS.DynamoDB({region: 'us-east-2'})
window.test('Test Connection', () => {
const listPromise = dynamo.listTables({Limit: 10}).promise()
return listPromise.then((data) => {
window.expect(data).toEqual(window.expect.anything())
}).catch((err) => {
console.log('Error:', err)
})
})
More info: https://facebook.github.io/jest/docs/en/asynchronous.html
Thanks to #Brahma Dev for the assistance.
Probem with test code, not Promise
The issue was not with dynamoDB or promises, but rather with my use of test. When dealing with promises, the promise must be returned to test for proper evaluation. Otherwise, it cannot be evaluated properly.
Working Code:
import AWS from 'aws-sdk'
AWS.config.loadFromPath('./awsConfig.json')
const dynamo = new AWS.DynamoDB({region: 'us-east-2'})
window.test('Test Connection', () => {
const listPromise = dynamo.listTables({Limit: 10}).promise()
return listPromise.then((data) => {
window.expect(data).toEqual(window.expect.anything())
}).catch((err) => {
console.log('Error:', err)
})
})
More info: https://facebook.github.io/jest/docs/en/asynchronous.html
Thanks to #Brahma Dev for the assistance.
EDIT: Corrected working code example based on feedback from #Brahma Dev
Also, removed some unnecessary variable declarations and assignments.
I'm using a callback to set some ip's on redis db async.
I'm trying to catch the error and send it through express to my error handler middleware.
I'm generating an error on purpose on the select method, but it doesn't catch my error.
See following code:
module.exports = (req, res, next) => {
const redis = require('redis')
const client = redis.createClient()
try {
client.select('2d', (err) => { // instead of 2 number, i use '2d' string, to generate an error on purpose
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
client.set(ip, true, 'EX', 120, (err, rep) => {
return next()
})
})
} catch (err) {
err.type = 'SilentSystem'
next(err)
}
}
From the documentation of the redis npm package, it's clear that it uses standard Node-style callbacks. In a standard Node-style callback, the first argument passed to the callback you provide is either an error or null; that's where and how errors are reported. (You've even defined a parameter called err in your code.) They can't be caught by a try/catch because control has already passed out of the try/catch (and in fact out of the function it's in) long before the error occurs.
So you'd handle it like this:
module.exports = (req, res, next) => {
const redis = require('redis')
const client = redis.createClient()
client.select('2d', (err) => { // instead of 2 number, i use '2d' string, to generate an error on purpose
if (err) {
// Handle error here
err.type = 'SilentSystem'
next(err)
} else {
// Handle success here
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
client.set(ip, true, 'EX', 120, (err, rep) => {
if (err) {
err.type = 'SilentSystem'
next(err)
} else {
next()
}
})
}
})
}
In a comment you've said:
My actual code is a bit more complex so I was trying to avoid calling to avoid repeating calling if(err) and next(err) by using try. What's a better way (less verbose) to handle errors here?
Unfortunately, that's the nature of Node-style callbacks. One option is to give yourself a filtering function you pass all those results through so your common error-handling code is there.
But: You might consider using a lib that "promisifies" Node-style callbacks so you can use promises instead, complete with their chaining mechanism which makes centralized error handling possible. (One such package is promisify, but there are others.) With "promisified" versions of client.select, client.set., etc., that code could look like this:
module.exports = (req, res, next) => {
const redis = require('redis')
const client = makeNiftyPromiseVersionOf(redis.createClient())
client.select('2d')
.then(data => {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
return client.set(ip, true, 'EX', 120)
})
.then(() => {
next()
})
.catch(err => {
err.type = 'SilentSystem'
next(err)
})
}
Note how the error handling is consolidated at the end; if there's an error in client.select, the then callback is skipped and control passes to the catch. If not, the then callback is executed and client.set is performed, and any errors from it will also go to that catch.
This also opens the door to using ES2017's async/await to write asynchronous code in a synchronous style:
module.exports = (req, res, next) => {
(async () => {
const redis = require('redis')
const client = makeNiftyPromiseVersionOf(redis.createClient())
try {
const data = await client.select('2d');
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
await client.set(ip, true, 'EX', 120)
next()
} catch (err) {
err.type = 'SilentSystem'
next(err)
}
})();
}
Side note: I would remove the require call out of the exported function, and instead do it at the module level:
const redis = require('redis')
module.exports = {
// ...
}
Background
I am trying to learn how to do a RESTful API following the TDD paradigm by reading a book on the subject (it is in brazillian):
https://leanpub.com/construindo-apis-testaveis-com-nodejs/read
The author encourages the use a sinon.js together with mocha.js.
I am getting close to the end, but I am failling to pass the test for my gnomeController.
Problem
The problem is that I am using sinon to assert that I am calling the gnomeController's get method using the given reponse object, which is in reallity a spy.
This spy is to make sure I call the reponse method with an "Error", but it appears I am calling the response with no arguments whatsoever, which is very confusing.
Code
gnomeController.js
module.exports = aGnomeModel => {
let Gnome = aGnomeModel;
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err));
}
return Object.freeze({
get
});
};
gnomeTest.js
const sinon = require("sinon");
const gnomesControllerFactory = require("gnomesController.js");
const Gnome = require("gnomeModel.js");
describe("Controllers: Gnomes", () => {
describe("get() gnomes", () => {
it("should return 400 when an error occurs", () => {
const request = {};
const response = {
send: sinon.spy(),
status: sinon.stub()
};
response.status.withArgs(400).returns(response);
Gnome.find = sinon.stub();
Gnome.find.withArgs({}).rejects("Error");
const gnomesController = gnomesControllerFactory(Gnome);
return gnomesController.get(request, response)
.then(arg => {
console.log(arg);
sinon.assert.calledWith(response.send, "Error");
});
});
});
});
Question
I am using the latest versions of both libraries.
What is wrong in my code, why is the reponse being called with no arguments?
Solution
After much debugging, I found out that the solution is to replace:
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err));
}
with:
function get(req, res){
return Gnome.find({})
.then(gnomes => res.send(gnomes))
.catch(err => res.status(400).send(err.name));
}
Which is not explained in the book. Kinda wish I could give more feedback on it, but so far it is what it is.