Using mocha/chai to ensure REST API serves up a file? - node.js

I am wanting to validate that a call to one of my REST API's end-point is serving up a file, but I am not sure how to go about it and I am not seeing any examples on this? I did look at the documentation, but this didn't help me much.
The server side code essentially does (in Express):
handleRetrieveContent(req, res, next) {
const filepaht = '...';
res.sendFile(filepath)
}
and the test case:
it('Should get a file', (done) => {
chai.request(url)
.get('/api/exercise/1?token=' + token)
.end(function(err, res) {
if (err) { done(err); }
res.should.have.status(200);
// Not sure what the test here should be?
res.should.be.json;
// TODO get access to saved file and do tests on it
});
});
I am essentially wanting to do the following tests:
ensure the response is a file
ensure the file is of valid content (checksum test)
Any help would be appreciated.

The solution provided was based on further experimenting and an answer provided in https://github.com/chaijs/chai-http/issues/126 - note code assumes ES6 (tested with Node 6.7.0).
const chai = require('chai');
const chaiHttp = require('chai-http');
const md5 = require('md5');
const expect = chai.expect;
const binaryParser = function (res, cb) {
res.setEncoding('binary');
res.data = '';
res.on("data", function (chunk) {
res.data += chunk;
});
res.on('end', function () {
cb(null, new Buffer(res.data, 'binary'));
});
};
it('Should get a file', (done) => {
chai.request(url)
.get('/api/exercise/1?token=' + token)
.buffer()
.parse(binaryParser)
.end(function(err, res) {
if (err) { done(err); }
res.should.have.status(200);
// Check the headers for type and size
res.should.have.header('content-type');
res.header['content-type'].should.be.equal('application/pdf');
res.should.have.header('content-length');
const size = fs.statSync(filepath).size.toString();
res.header['content-length'].should.be.equal(size);
// verify checksum
expect(md5(res.body)).to.equal('fa7d7e650b2cec68f302b31ba28235d8');
});
});
Edit: Most of this was in Read response output buffer/stream with supertest/superagent on node.js server with possible improvements

Related

why done runs earlier before supertest request on sequelize?

const request = require('supertest');
const app = require('../app');
request(app)
.get("/videos")
.expect(200)
.end(async (err,res) => {
if (err) {
console.error(err);
done();
} else {
console.log(res.body);
done(); // <- this is the question line
}
If I take off the done from the code, the test will print the result, but if I have done there, sequelize still print but there is no res.body print on the console.
Anyone knows why?
I think it's because of the asynchronous nature of javascript.
By default, Jest tests complete once they reach the end of their execution. That means this test will not work as intended:
const request = require('supertest');
const app = require('../app');
request(app)
.get("/videos")
.expect(200)
.end(async (err,res) => {
if (err) {
console.error(err);
// done();
} else {
console.log(res.body);
// done();
}
The problem is that the test will complete as soon as function completes asynchronosly.
Moving to, after putting done()
const request = require('supertest');
const app = require('../app');
request(app)
.get("/videos")
.expect(200)
.end(async (err,res) => {
if (err) {
console.error(err);
done();
} else {
console.log(res.body);
done();
}
And then, in that case, it waits for the done function before finishing the test.
For more details. Please check here.
I hope it helps. Thanks
Try to remove async from .end(async (err,res) => { line so the callback is not considered asynchronous anymore.

REST API integration to 3rd party

I'm trying to create REST API. My API should return a list of users taken from a 3rd party (after some manipulations) and return it.
Here is my code:
function getUsersFrom3rdParty(options) {
https.get(options, (resp) => {
let data ='';
// A chunk of data has been received.
resp.on('data', (chunk) => {
data += chunk;
});
// The whole response has been received. Print out the result.
resp.on('end', () => {
console.log(JSON.parse(data));
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
}
exports.getUsers = (req, res, next) => {
var data = getUsersFrom3rdParty();
//do the manilupations and return to the api
};
I don't get the data in getUsers function.
I'd suggest using something like axios - npmjs - for making asynchronous calls to a 3rd party API:
const axios = require('axios')
function getUsersFrom3rdParty(options) {
const processResponse = (response) => {
const processedResponse = ...
// do whatever you need to do, then return
return processedResponse
}
return axios.get('/example.com')
.then(processResponse)
}
// then, you can use `getUsersFrom3rdParty` as a promise
exports.getUsers = (req, res, next) => {
const handleResponse = (data) => {
res.json({ data }) // or whatever you need to do
}
const handleError = (err) => {
res.json({ error: 'Something went wrong!' }) // or whatever you need to do
}
getUsersFrom3rdParty(...)
.then(handleResponse)
.catch(handleError)
}
This way, you're waiting for your API call to finish before you render something and/or return a response.
You are not passing options variable when you are calling getUsersFrom3rdParty function
var data = getUsersFrom3rdParty(options);
You have to pass options to make it work and I suggest to use request module .It works better than https module.
Here is your code using request
const request = require("request");
function getUsersFrom3rdParty(options) {
request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
//Returned data
console.log(JSON.parse(body));
}
});
}
exports.getUsers = (req, res, next) => {
var data = getUsersFrom3rdParty(options);
};

Supertest + Express won't fail

This is more or less a duplicate of supertest test express middleware
but after a year, I figured I'd start a new question.
var express = require('express');
var request = require('supertest');
var app1 = express();
app1.get('/myapp', function (req, res) {
res.send(200, { name: 'myapp' });
});
request = request(app1);
it('should fail', function () {
request
.get('/hahahahahahahaha')
.expect(123);
});
As far as I can tell, that will always erroneously pass. The fact that the path is wrong and is expecting a different status code doesn't matter.
And - more generically (without Express), it looks like this always passes, also:
it('should fail', function () {
request('http://thisdoesnotexist.mydomain')
.get()
.expect(200);
});
This doesn't work either:
it('should fail', function () {
request('http://thisdoesnotexist.mydomain')
.get()
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
});
});
Any thought as to why this happens, or how to actually test such a scenario?
With supertest you need to terminate your chain somehow.
expect will take a finished callback as the second parameter, and you can use the build in mocha callback for this. Like so:
describe('potato', function() {
it('should fail', function(done) {
request
.get('/hahahahahahahaha')
.expect(123, done);
});
});
Specifying a done option like this will instruct mocha to wait until it's heard back from you before proceeding to the next test.
The difference is the parameter: done
describe('XXX', function() {
it('XXX', function() {
// always passing
})
})
describe('YYY', function(done) {
it('YYY', function() {
// always passing
})
})
describe('ZZZ', function() {
it('ZZZ', function(done) {
// normal
})
})

Mock fs.readdir for testing

I'm trying to mock the function fs.readdir for my tests.
At first I've tried to use sinon because this is a very good framework for this, but is hasn't worked.
stub(fs, 'readdir').yieldsTo('callback', { error: null, files: ['index.md', 'page1.md', 'page2.md'] });
My second attempt was to mock the function with a self-replaced function. But it also doesn't works.
beforeEach(function () {
original = fs.readdir;
fs.readdir = function (path, callback) {
callback(null, ['/content/index.md', '/content/page1.md', '/content/page2.md']);
};
});
afterEach(function () {
fs.readdir = original;
});
Can anybody tell me why both doesn't works? Thanks!
Update - This also doesn't works:
sandbox.stub(fs, 'readdir', function (path, callback) {
callback(null, ['index.md', 'page1.md', 'page2.md']);
});
Update2:
My last attempt to mock the readdir function is working, when I'm trying to call this function directly in my test. But not when I'm calling the mocked function in another module.
I've found the reason for my problem. I've created the mock in my test class tried to test my rest api with supertest. The problem was that the test was executed in another process as the process in that my webserver runs. I've created the express-app in my test class and the test is now green.
this is test
describe('When user wants to list all existing pages', function () {
var sandbox;
var app = express();
beforeEach(function (done) {
sandbox = sinon.sandbox.create(); // #deprecated — Since 5.0, use sinon.createSandbox instead
app.get('/api/pages', pagesRoute);
done();
});
afterEach(function (done) {
sandbox.restore();
done();
});
it('should return a list of the pages with their titles except the index page', function (done) {
sandbox.stub(fs, 'readdir', function (path, callback) {
callback(null, ['index.md', 'page1.md', 'page2.md']);
});
request(app).get('/api/pages')
.expect('Content-Type', "application/json")
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
var pages = res.body;
should.exists(pages);
pages.length.should.equal(2);
done();
});
});
});

How to authenticate Supertest requests with Passport?

I'm using Passport.js for authentication (local strategy) and testing with Mocha and Supertest.
How can I create a session and make authenticated requests with Supertest?
As zeMirco points out, the underlying superagent module supports sessions, automatically maintaining cookies for you. However, it is possible to use the superagent.agent() functionality from supertest, through an undocumented feature.
Simply use require('supertest').agent('url') instead of require('supertest')('url'):
var request = require('supertest');
var server = request.agent('http://localhost:3000');
describe('GET /api/getDir', function(){
it('login', loginUser());
it('uri that requires user to be logged in', function(done){
server
.get('/api/getDir')
.expect(200)
.end(function(err, res){
if (err) return done(err);
console.log(res.body);
done()
});
});
});
function loginUser() {
return function(done) {
server
.post('/login')
.send({ username: 'admin', password: 'admin' })
.expect(302)
.expect('Location', '/')
.end(onResponse);
function onResponse(err, res) {
if (err) return done(err);
return done();
}
};
};
You should use superagent for that. It is lower level module and used by supertest. Take a look at the section Persisting an agent:
var request = require('superagent');
var user1 = request.agent();
user1
.post('http://localhost:4000/signin')
.send({ user: 'hunter#hunterloftis.com', password: 'password' })
.end(function(err, res) {
// user1 will manage its own cookies
// res.redirects contains an Array of redirects
});
Now you can use user1 to make authenticated requests.
Try this,
var request=require('supertest');
var cookie;
request(app)
.post('/login')
.send({ email: "user#gluck.com", password:'password' })
.end(function(err,res){
res.should.have.status(200);
cookie = res.headers['set-cookie'];
done();
});
//
// and use the cookie on the next request
request(app)
.get('/v1/your/path')
.set('cookie', cookie)
.end(function(err,res){
res.should.have.status(200);
done();
});
As an addendum to Andy's answer, in order to have Supertest startup your server for you, you can do it like this:
var request = require('supertest');
/**
* `../server` should point to your main server bootstrap file,
* which has your express app exported. For example:
*
* var app = express();
* module.exports = app;
*/
var server = require('../server');
// Using request.agent() is the key
var agent = request.agent(server);
describe('Sessions', function() {
it('Should create a session', function(done) {
agent.post('/api/session')
.send({ username: 'user', password: 'pass' })
.end(function(err, res) {
expect(req.status).to.equal(201);
done();
});
});
it('Should return the current session', function(done) {
agent.get('/api/session').end(function(err, res) {
expect(req.status).to.equal(200);
done();
});
});
});
I'm sorry, but neither of suggested solutions doesn't work for me.
With supertest.agent() I can't use the app instance, I'm required to run the server beforehand and specify the http://127.0.0.1:port and moreover I can't use supertest's expectations (assertions), I can't use the supertest-as-promised lib and so on...
The cookies case won't work for me at all.
So, my solution is:
If you are using Passport.js, it utilizes the "Bearer token" mechanism and you can use the following examples in your specs:
var request = require('supertest');
var should = require('should');
var app = require('../server/app.js'); // your server.js file
describe('Some auth-required API', function () {
var token;
before(function (done) {
request(app)
.post('/auth/local')
.send({
email: 'test#example.com',
password: 'the secret'
})
.end(function (err, res) {
if (err) {
return done(err);
}
res.body.should.to.have.property('token');
token = res.body.token;
done();
});
});
it('should respond with status code 200 and so on...', function (done) {
request(app)
.get('/api/v2/blah-blah')
.set('authorization', 'Bearer ' + token) // 1) using the authorization header
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
// some `res.body` assertions...
done();
});
});
it('should respond with status code 200 and so on...', function (done) {
request(app)
.get('/api/v2/blah-blah')
.query({access_token: token}) // 2) using the query string
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
// some `res.body` assertions...
done();
});
});
});
You may want to have a helper function to authenticate users:
test/auth-helper.js
'use strict';
var request = require('supertest');
var app = require('app.js');
/**
* Authenticate a test user.
*
* #param {User} user
* #param {function(err:Error, token:String)} callback
*/
exports.authenticate = function (user, callback) {
request(app)
.post('/auth/local')
.send({
email: user.email,
password: user.password
})
.end(function (err, res) {
if (err) {
return callback(err);
}
callback(null, res.body.token);
});
};
Have a productive day!
I'm going to assume that you're using the CookieSession middleware.
As grub mentioned, your goal is to get a cookie value to pass to your request. However, for whatever reason (at least in my testing), supertest won't fire 2 requests in the same test. So, we have to reverse engineer how to get the right cookie value. First, you'll need to require the modules for constructing your cookie:
var Cookie = require("express/node_modules/connect/lib/middleware/session/cookie")
, cookieSignature = require("express/node_modules/cookie-signature")
Yes, that's ugly. I put those at the top of my test file.
Next, we need to construct the cookie value. I put this into a beforeEach for the tests that would require an authenticated user:
var cookie = new Cookie()
, session = {
passport: {
user: Test.user.id
}
}
var val = "j:" + JSON.stringify(session)
val = 's:' + cookieSignature.sign(val, App.config.cookieSecret)
Test.cookie = cookie.serialize("session",val)
Test.user.id was previously defined in the portion of my beforeEach chain that defined the user I was going to "login". The structure of session is how Passport (at least currently) inserts the current user information into your session.
The var val lines with "j:" and "s:" are ripped out of the Connect CookieSession middleware that Passport will fallback on if you're using cookie-based sessions. Lastly, we serialize the cookie. I put "session" in there, because that's how I configured my cookie session middleware. Also, App.config.cookieSecret is defined elsewhere, and it must be the secret that you pass to your Express/Connect CookieSession middleware. I stash it into Test.cookie so that I can access it later.
Now, in the actual test, you need to use that cookie. For example, I have the following test:
it("should logout a user", function(done) {
r = request(App.app)
.del(App.Test.versionedPath("/logout"))
.set("cookie", Test.cookie)
// ... other sets and expectations and your .end
}
Notice the call to set with "cookie" and Test.cookie. That will cause the request to use the cookie we constructed.
And now you've faked your app into thinking that user is logged in, and you don't have to keep an actual server running.
Here is a neat approach which has the added benefit of being reusable.
const chai = require("chai")
const chaiHttp = require("chai-http")
const request = require("supertest")
const app = require("../api/app.js")
const should = chai.should()
chai.use(chaiHttp)
describe("a mocha test for an expressjs mongoose setup", () => {
// A reusable function to wrap your tests requiring auth.
const signUpThenLogIn = (credentials, testCallBack) => {
// Signs up...
chai
.request(app)
.post("/auth/wizard/signup")
.send({
name: "Wizard",
...credentials,
})
.set("Content-Type", "application/json")
.set("Accept", "application/json")
.end((err, res) => {
// ...then Logs in...
chai
.request(app)
.post("/auth/wizard/login")
.send(credentials)
.set("Content-Type", "application/json")
.set("Accept", "application/json")
.end((err, res) => {
should.not.exist(err)
res.should.have.status(200)
res.body.token.should.include("Bearer ")
// ...then passes the token back into the test
// callBack function.
testCallBack(res.body.token)
})
})
}
it.only("flipping works", done => {
// "Wrap" our test in the signUpThenLogIn function.
signUpLogIn(
// The credential parameter.
{
username: "wizard",
password: "youSHALLpass",
},
// The test wrapped in a callback function which expects
/// the token passed back from when signUpLogIn is done.
token => {
// Now we can use this token to run a test...
/// e.g. create an apprentice.
chai
.request(app)
.post("/apprentice")
.send({ name: "Apprentice 20, innit" })
// Using the token to auth!
.set("Authorization", token)
.end((err, res) => {
should.not.exist(err)
res.should.have.status(201)
// Yep. apprentice created using the token.
res.body.name.should.be.equal("Apprentice 20, innit")
done()
})
}
)
})
})
BONUS MATERIAL
To make it even more reusable, put the function into a file called "myMochaSuite.js" which you can replace "describe" with when testing your api server. Be a wizard and put all your before/after stuff in this "suite". e.g.:
// tests/myMochaSuite.js
module.exports = (testDescription, testsCallBack) => {
describe(testDescription, () => {
const signUpThenLogIn = (credentials, testCallBack) => {
// The signUpThenLogIn function from above
}
before(async () => {
//before stuff like setting up the app and mongoose server.
})
beforeEach(async () => {
//beforeEach stuff clearing out the db
})
after(async () => {
//after stuff like shutting down the app and mongoose server.
})
// IMPORTANT: We pass signUpLogIn back through "testsCallBack" function.
testsCallBack(signUpThenLogIn)
})
}
// tests/my.api.test.js
// chai, supertest, etc, imports +
const myMochaSuite = require("./myMochaSuite")
// NB: signUpThenLogIn coming back into the tests.
myMochaSuite("my test description", signUpThenLogIn => {
it("just works baby", done => {
signUpThenLogIn(
{username: "wizard", password: "youSHALLpass"},
token => {
chai
.request(app)
.get("/apprentices/20")
// Using the incoming token passed when signUpThenLogIn callsback.
.set("Authorization", token)
.end((err, res) => {
res.body.name.equals("Apprentice 20, innit")
done()
})
}
)
})
})
Now you have a even more reusable suite "wrapper" for all your tests, leaving them uncluttered.
GraphQl full Example:
const adminLogin = async (agent) => {
const userAdmin = await User.findOne({rol:"admin"}).exec();
if(!userAdmin) return new Promise.reject('Admin not found')
return agent.post('/graphql').send({
query: ` mutation { ${loginQuery(userAdmin.email)} }`
})//.end((err, {body:{data}}) => {})
}
test("Login Admin", async (done) => {
const agent = request.agent(app);
await adminLogin(agent);
agent
.post("/graphql")
.send({query: `{ getGuests { ${GuestInput.join(' ')} } }`})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end((err, {body:{data}}) => {
if (err) return done(err);
expect(data).toBeInstanceOf(Object);
const {getGuests} = data;
expect(getGuests).toBeInstanceOf(Array);
getGuests.map(user => GuestInput.map(checkFields(user)))
done();
});
})

Resources