Testing Node.js api with mocha - node.js

I am busy building functional tests for an existing nodejs api I wrote. I am using mocha and expect.js.
One test that is failing is when I want to do a negative test for the existence of a url parameter. What I want is to send a message back to the the consumer of the service if the param is missing.
Currently the positive test I have works:
var request = require('request');
it('get a specific user from the api', function (done) {
request.get('http://localhost:8080/api/v1/user/123', function (error, response, body) { ... });
});
However, the following does not work and all I get is a timeout:
it('should return a 400 message if UserId is not supplied stating that "UserId expected"', function(done) {
request.get('http://localhost:8080/api/v1/user/', function (error, response, body) {
if (!error && response.statusCode == 400) {
expect(body).to.be.equal('UserId expected');
done();
}
});
});
Both the above tests are testing this endpoint:
app.get('/api/v1/user/:userid', function (req, res) {
if(!req.params.userid) {
return res.status(400).json('UserId expected');
}
... get the user and return it with status 200 ...
});
However, as stated before, the first test is successful while the second test times out.
Update 1:
Additionally, I am getting the following output:
GET /api/v1/user/123 200 2.905 ms - 274
GET /api/v1/user/ - - ms - -
Update 2:
So adding the following code solves the problem, but doesn't really solve the problem:
app.get('/api/v1/user', function (req, res) {
return res.status(400).json('UserId expected');
});
Obviously I don't want a rather redundant piece of code sitting around. I am baffled as to why I cannot enter the 'api/v1/user/:userid' endpoint and just test for the existence of :userid in there? It's as if nodejs is requiring the existence of :userid, otherwise the endpoint does not exist. Is this correct?
What am I missing?

According to mocha documentation the done callback must always be called, also in case of errors. In fact it accepts an error.
So I think that you must rewrite your code with:
it('should return a 400 message if UserId is not supplied stating that "UserId expected"', function(done) {
request.get('http://localhost:8080/api/v1/user/', function (error, response, body) {
if (error) return done(error);
if (response.statusCode != 400) return done(new Error("Not 400"));
expect(body).to.be.equal('UserId expected');
done();
});
});

So I did some more investigation and asked another question regarding this issue here.
In the end the issue was that I didn't fully understand the express framework. Basically what I alluded to in Update 2 of my question is the route I will need to follow and specify a route where no param is expected, but return the error message I am expecting in that case.
The problem I have with this approach is that in some instances one might want to use more than one parameter in the ur. However, this will mean the permutations to cater for each scenario where a parameter is undefined gets a bit daunting. (2 params will mean 4 combinations, 3 will mean 9 etc.) This will be a maintenance nightmare!
My approach from now on will be to rather follow an approach where any info I want to pass to the server will be sent via JSON in the body from of each request. This will allow me to keep to one single route.

Related

Express-validator .getValidationResult()

I'm working on a simple login for a web application, and can't seem handle .getValidationResult() correctly. I've spent quite a bit of time pouring over the npm documentation for express-validator, trying to find an answer in tutorials, and looking on sites like Stack Overflow without managing to find the answer to my question. Perhaps I just don't know the right question to ask.
I want to ensure that
the user submitted something that has the form of an email address,
that the password isn't empty. I then want to
sanitize the email before interacting with the DB later on, then
check to see if any of the first 3 procedures failed. If there were failures, return the user to the login page.
My question is what is the correct way to use express-validator's .getValidationResult()?
Here's the offending piece of code:
export let postLogin = (req: Request, res: Response, next: NextFunction) => {
req.assert("email", "Email is not valid").isEmail();
req.assert("password", "Password cannot be blank").notEmpty();
req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
req.getValidationResult().then(function(result){
if (result != undefined) {
console.log(result.array();
return res.redirect("/login");
}
});
//do other login related stuff
}
I'm guessing that something simple is causing my error here, but I can't seem to find what it is.
It returns a promise for an object called Validation Object. This object contains information about the errors that your application has had.
The explanation.
Runs all validations and returns a validation result object for the
errors gathered, for both sync and async validators.
All it does is returning errors if there is one. Here is some example code returned by that function.
//The error object
{
"msg": "The error message",
"param": "param.name.with.index[0]",
"value": "param value",
// Location of the param that generated this error.
// It's either body, query, params, cookies or headers.
"location": "body",
// nestedErrors only exist when using the oneOf function
"nestedErrors": [{ ... }]
}
The function returns isEmpty() when there is no errors to display.
The function returns .array([options]) if there are any errors. Errors are located in [options] array.
Check out this link for the example code of what it might return.
UPDATE
You can also just use it like this, which is easier.
Please note that this is new API as of v4.0.0 release of express-validator.
const { check, validationResult } = require('express-validator/check');
//go to a link
app.get('/myURL', (req, res, next) => {
// Get the validation result\
const errors = validationResult(req).throw();
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors }); //err.mapped()
});

Is there an effective way to validate an image source url in node?

I am using Node to get Instagram images and have come across an edge case I am interested in solving. While using the oembed API call, I can then get thumbnail_url.
In my service, I return the image url and move on. The issue here is, for certain permalinks (carousel/albums), this thumbnail_url loads with a 5xx Instagram Error.
What I would like to do is to verify the image url loads an image, and if not, do something else instead of returning it.
I know what the "something else" is. My problem is that the url I get back from the oembed call is indeed a valid url, so I don't need to validate that. I need to validate the url loads as expected.
My initial thought process was to do something like but I have never tried to verify an image source url before, I've always just tried to get the url in the first place:
function urlTester(url) {
request(url, (error, response, body) => {
if (error) {
console.log('URL does not load');
} else {
console.log('URL loads image!');
}
})
}
If the urls are always valid, and you only need to check for the actual response, you are correct.
(Vanilla) Request Example :
request(url,function(error, response, body){
if(!error && response.statusCode == 200){
/* RESPONSE SUCCESS */
}else{
/* RESPONSE ERROR */
}
})
If you want to go on further and verify that the response indeed contains an image you can use : response.headers['content-type']
if(((response.headers['content-type']).match(/(image)+\//g)).length != 0){
/* It contains 'image/' as the content type */
}else{
/* no match with 'image/' */
}
You can also try using request-image-size
Detects image dimensions via request instead of Node.js native
http/https, allowing for options and following redirects by default.
It reduces network traffic by aborting requests as soon as image-size
is able to obtain the image size.
It will return an error if the response is not a valid image :
Since version 2.0.0 it returns an ES6 native Promise that resolves
with the size object or rejects with an Error. Requires Node.js v4+.
As #EMX pointed out, I was headed in the right direction. Here is what I ended up doing:
function testUrl(url) {
return new Promise((resolve, reject) => {
request(url, function(error, response, body) {
if (error) {
reject(error);
}
resolve(response.statusCode);
})
})
}
If the statusCode !== 200, then I can move on as initially required.

Return a status from nodejs/express REST API

Does
res.status(200)
res.json({
isSuccess: true
});
equals to
res.status(200).json({
isSuccess: true
});
?
I'll explain why I ask.
I build a MEAN app and use mocha for the unit tests.
As I have seen in some tutorial somewhere, I send res as a validation function to check the return values.
Actually res is built out of two functions: status and json. When the BE API changes the status or the json for the response, it actually calls the validation function to check the values.
The reason I ask is that when I do res.status(200).json(...) only the status function is being called. When I use the second method, both function are being called as I intended.
Thanks
The following should all be equal:
res.status(200);
res.json({});
res.status(200).json({});
res.json({});
They might have different internal workings but they both give you the same result when you make API calls. If it's just for testing, it shuold be no problem.

POST not working for Mocha/Chai tests on Express REST API

I'm having a problem with Mocha tests on my Express app. I have a REST API set up that I know works in the browser, but it's getting a bit large and therefore manual testing has become tedious.
Anyway, all my GET tests work just fine, but when I add tests for my POST requests, they fail:
Uncaught AssertionError: expected { Object (domain, _events, ...) } to have status code 200 but got 400
at testPostSingle (test-app.js:297:21)
at test-app.js:195:21
at Test.Request.callback (/home/jacobd/healthboard/node_modules/chai-http/node_modules/superagent/lib/node/index.js:603:3)
at Stream.<anonymous> (/home/jacobd/healthboard/node_modules/chai-http/node_modules/superagent/lib/node/index.js:767:18)
at Unzip.<anonymous> (/home/jacobd/healthboard/node_modules/chai-http/node_modules/superagent/lib/node/utils.js:108:12)
at _stream_readable.js:944:16
I'm using Mocha, with the Chai should library and chai-http for requests.
My relevant code:
models.forEach(function(model, i) {
var url = '/api/v1/' + model;
var list = lists[i];
/****************************** API POST TESTS ******************************/
describe(util.format('API: /%s POST', model), function() {
var minArgsObj = {
title: 'Test ' + model + ' Title'
};
// Initialize list at start
before(function(done) {
instances._init(function(err) {
if (!err) done();
});
});
// Nuke list before each test
beforeEach(function(done) {
list.clear();
done();
});
// Make sure POST single object works
it('should create and add model on ' + url + ' with minimal arguments', function(done) {
var req = {};
req.options = _.clone(minArgsObj);
chai.request('server')
.post(url)
.send(req)
.end(function(err, res) {
testPostSingle(res, minArgsObj);
list.list.length.should.equal(1);
res.body.should.eql(list.list[0]);
done();
});
});
});
});
function testPostSingle(res, ref) {
res.should.have.status(200);
...
}
When I put a log message in the POST route declaration, it doesn't show up, so that tells me that my request is getting stopped before even hitting my server. Maybe the route isn't getting mounted properly in Mocha? It works when I make the request outside of Mocha, but I can't figure out why it won't work in a test environment.
Thanks in advance for your help, and let me know if you need any more info!
The relevant code is in testPostSingle. Your call is returning 400 bad request. Are you sure the route is correct or is set up to handle POST as well as GET? Are you sure parameters are correct? Make testPostSingle print out the body of the HTTP response so you know the details. You can also add some debug code in the route for that request on your server.
I'm terrible and issue is very simple:
Instead of chai.request('server')
It needs to be chai.request(server)
Thanks to Jason Livesay for pointing me at the right line!

How to get Express to return the object I just deleted in MongoDB

Feel free to let me know if this isn't a common practice - I'm a fairly new programmer - but I thought I've seen APIs in the past that, when you submit a DELETE request to a resource (/todo/1234), some servers will return the object you just deleted in the response. Is that a thing? If so, I'd be interested in learning how to do it. Here's what I have:
.delete(function (req, res) {
Todo.findById(req.params.todoId).remove(function (err) {
if (err) res.status(500).send(err);
res.send("Todo item successfully deleted");
});
});
This code does delete the item, but I would like to return the item that got deleted in the response instead of a string message. If that's a normal/okay thing to do. If it isn't normal or okay for some reason, please let me know why and I'll just move on. Or perhaps there's a more common way.
This is what I found in the [RFC 7231 docs][1]:
If a DELETE method is successfully applied, the origin server SHOULD
send a 202 (Accepted) status code if the action will likely succeed
but has not yet been enacted, a 204 (No Content) status code if the
action has been enacted and no further information is to be supplied,
or a 200 (OK) status code if the action has been enacted and the
response message includes a representation describing the status.
I'm having a hard time interpreting what the 200 response means - is it only kosher to send a string message (Success!) or an object containing a message attribute ({message: "Success!"})? Or can you do whatever you want there? What's the best practice in Express using Mongoose?
Thanks in advance for the help, and sorry for my noobness with HTTP stuff.
[1]: https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5
You should use findOneAndRemove! Something like:
Todo.findOneAndremove({ id: req.params.todoId }, function( error, doc, result) {
// it will be already removed, but doc is what you need:
if (err) res.status(500).send(err);
res.send(doc.id);
});

Resources