I am looking to use supertest to test API requests and responses. Following is what I have tried so far.
route.test.js
const testUtils = require('./setupTestUtils');
let authenticateUser = request.agent(app);
before(function(done){
testUtils.login(authenticateUser, userCredentials).then((res) => {
expect(res.statusCode).to.equal(200);
done();
}, (err) => {
console.log(err);
done(err);
});
});
setupTestUtils.js
function login (rest, testUserLogin) {
let defer = Q.defer();
rest.post('/login')
.send(testUserLogin)
.expect(200)
.end(function () {
rest.get('/loggedin')
.expect((res) => {
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
defer.reject(err);
} else {
defer.resolve(res);
}
})
.end();
});
return defer.promise;
}
In my app.js, I use passport to authenticate. After authentication, I use the session.regenerate function to regenerate the session ID to avoid session fixation.
The initial post request to login passes without any failure. However, the subsequent GET request 'loggedIn' fails. This function internally uses the req.isAuthenticated() function from passport. This always returns false.
On investigation, I found that the session ID between the regenerated session and the request object (for req.isAuthenticated()) is different.
From my search, I understand that the cookies should be maintained automatically by the use of 'agent' from supertest. However that doesnt seem to be the case for me. I have also tried maintaining the cookies from the initial response. That doesnt seem to work for me either. " res.headers['set-cookie'] " comes in as undefined (not sure why that is happening either).
Can someone please help me understand what I am missing here.?
Am using versions - Supertest #v6.0.1 and passport #v0.4.1
I found the solution to my issue in an old github issue raised on supertest's page. Linking it here for reference.
Essentially, the supertest runs express in insecure port and I had configured my session otherwise. Ideally, we would have to check the environment before setting this variable to false - as represented here.
Hope this saves someone the time I spent!
Related
I'm using Firebase Cloud Functions, and the cookie module, and one function sets the "__session" cookie (the only cookie available in Cloud Functions) then executes a "GET" request to another function. In the second function when I try to get the cookie using cookie.parse(req.headers.cookie).__session it says it's undefined. I can see the cookie in the browser however, so I have no idea why I cannot get it in my app. I noticed there are several other SO questions similar to this problem that are also unanswered.
Here is the relevant code from the first function:
const nonce = require('nonce')();
const state = nonce();
res.cookie('__session', state);
request.get('https://myappurl.firebaseapp.com/myapp/path?state=' + state, { json: true })
.catch((error) => {
console.log(error.message);
});
res.redirect('someurl.com');
From the second function:
const cookie = require('cookie');
app.get('/myapp/path', (req, res) => {
console.log(req.headers.cookie); // This is undefined.
const stateCookie = cookie.parse(req.headers.cookie).__session
console.log('stateCookie: ' + stateCookie); // This is undefined.
}
UPDATE
After adding my code, I realized that I'm setting the cookie on res (Express response) but making the get request with request (request-promise module). I'm thinking this may be my issue. I'm going to figure out how to set a cookie to the request and see if that solves it.
I solved this, mostly. Turns out setting the cookie on the Express res was part of the problem. I should be setting it on the request object (request-promise). I tried a couple of ways to set the cookie on the request, including request.cookie. But the only way that worked was this:
var options = {
headers: {
Cookie: '__session=mytestheadercookie'
}
};
request.get('https://myapp.com', options)
.catch((error) => {
console.log(error.message);
});
In my website, everything server-side is stored in sessions of express-session.
But I can't understand why, when I make an HTTP request with request module, the req.session parameter isn't within the request.
I mean, follow the comments :
app.get('/prefix', async function(req, res) {
console.log(req.session.login);
// There ^ the req.session.login is true, and so it works
if (req.session.login == false || req.session.login == null) return;
var options = {
url: 'https://bot-dreamsub.glitch.me/getPermission',
json: true,
jar: true,
withCredentials: true
}
request(options, async function(err, res, json) {
if (err) {
throw err;
}
console.log(json);
if (json == true) {
res.sendFile(__dirname + '/prefix/prefix.html');
} else {
return;
}
});
});
app.get('/getPermission', function(req, res) {
console.log(req.session.login);
// There ^ the req.session.login is undefined, and so it sends null to the user
try {
if (req.session.login == false || req.session.login == undefined) {
res.send(null);
} else {
// some other code
}
} catch(err) {
console.log(err);
};
});
I don't know why request doesn't send sessions within the HTTP request even with
withCredentials: true
What can I do to accomplish it?
An express-session works by setting a cookie in the client's browser that made the original request. Future requests with that same cookie will offer access to that same session. When you do request() yourself from your server, you aren't presenting the same cookie that came in with the original /prefix request so you won't have access to the same session.
Since it appears you are just trying to use request() to call your own server, I'd suggest you just use a function call and pass the original req.session to that function call so that you will have it available.
You then use normal code factoring to factor out some common code between your /getPermissions route and what you want to use in your /prefix route so that they can both use and share a common function that you pass the current req and res to. Then you don't need to solve this cookie issue because you'll already have the right req object and thus the correct req.session in this factored common function.
Alternatively, you could build the right cookie and send that with your request() so that it will appear to be coming from the original browser that has the cookie (and thus session) that you want, but that's kind of the long way to do things when you already have the req.session you want and you could just pass it in a function call rather than start all over and try to simulate a cookie that will get you to the right session.
I don't know why request doesn't send sessions within the HTTP request even with
First off, session aren't sent with a request. Cookies are. Your server then uses the cookie to find the right session object.
Your call to request() does not have the right cookie in the cookie jar you use so when that requests gets to your server, it isn't able to find the right session object. So, when the request is received by your server, it appears to be coming from a different client that does not yet have a session so a new cookie and a new session are probably created for it.
FYI, if also looks like you may be confusing two definitions of res in your request() call. There's a res defined as an argument in this app.get('/prefix', async function(req, res) { and then you have a separate res in request(options, async function(err, res, json) { that will override the previous one in that scope. It appears to me when you do res.sendFile(__dirname + '/prefix/prefix.html');, you are probably using the wrong res. Probably the best way to solve this is to not use request() at all as suggested above (using a function call to your own server). But, if you were going to still use request(), then you need to name the two res arguments differently so you can still access them both and can use the correct one for your situation.
I am new to Express and Node, and when testing a protected REST endpoint in my app using Advanced REST Client the data is returned from the endpoint to the Client as expected, however the console logs
"Error: Can't set headers after they are sent"
which stops the server. Searching here on SO, this seems to occur when sending more than one response but I don't see that in my code:
router.get('/books', userAuthenticated, function (req, res, next) {
Book.find({}, function(err, docs){
if(err) {
res.send(err)
} else {
res.send(docs);
// next();
// return;
}
})
});
Is this error expected when sending a request/response to/from the Client or am I missing something in handling the error on the server?
Express is a node.js server that features a concept called "middleware", where multiple layers of code get a chance to operate on a request.
In that case, not only do you need to check that your function is not sending back a response twice, but you have to check that other middleware are not sending back a response as well.
In your case, the code indicates that middleware called "userAuthenticated" is being invoked before this function. You should check if that middleware is already sending a response.
I don't think the problem was in the middleware. It looks like I was calling done() twice in passport deserialize. I commented out the first instance and the problem disappeared in all my routes. Although, I am not sure if I should comment out the first or second instance but I'll work on that next.
passport.deserializeUser(function(obj, done) {
console.log(obj);
Account.findById(obj, function(err, user) {
console.log(user);
//done(err, user);
});
return done(null, obj);
});
I'm making a testbench with Test'em and Mocha (that run on node.js) in order to test a PHP website.
What I want is to request some URL (e.g http://www.my-website/test.php) and get the http status code as well as the content returned.
I'm doing it with the node.js Request module.
The problem is:
I need to be authenticated to access this page, otherwise I'm
redirected to the login page.
So, does it exist a way to log in my application through Node.js and keep the session open to be able to chain tests on any pages I want?
I was thinking on get the PHPSESSID on login request if it is possible. Do you thing it is a good direction ?
Any help would be much appreciated.
Thank you, have a nice day :)
Michaël
If you set jar: true in your options or use your own custom cookie jar, then request will remember cookies set by the server so that you can keep your session between requests.
mscdex thanks for your answer! But unfortunately it did not work for me :/
hyubs thanks to you too.
Finally I carried on to use Mocha + Request.
Basically what I did is:
Connect through a POST request to the login page and get the PHPSESSID cookie that is returned in the response header.
Pass the cookie in the header in the next requests that target a URL where you have to be logged.
Here is my code :
var params = {
email: 'your_username',
password: 'your_password'
};
var paramsString = JSON.stringify(params);
// Login to the application
request.post('http://localhost/biings/front-end/rest/auth',
{
headers: {
"Content-Type" : "application/json",
'Content-Length' : paramsString.length
},
body: paramsString,
},function (error, response, body) {
// get the PHPSESSID (the last one) that is returned in the header. Sometimes more than one is returned
var sessionCookie = response.headers['set-cookie'][response.headers['set-cookie'].length - 1];
sessionCookie = sessionCookie.split(';');
sessionCookie = sessionCookie[0];
// Write it in a file (this is a quick trick to access it globally)
// e.g.: PHPSESSID=ao9a1j0timv9nmuj2ntt363d92 (write it simply as a string)
fs.writeFile('sessionCookie.txt', sessionCookie, function (err)
{
if(err)
{
return console.log(err);
}
});
});
// don't care about this it() function (it's for Mocha)
it("test 1", function(done)
{
// Get the cookie
fs.readFile('sessionCookie.txt','utf8', function (err, data)
{
if(err)
{
throw err;
}
else
{
// Launch a request that includes the cookie in the header
request.get('http://localhost/biings/front-end/rest/group',
{
headers: {"Cookie" : data},
}, function (error, response, body) {
// Check your request reaches the right page
expect(response.statusCode).equals(200);
console.log(body);
done();
});
}
});
});
It works like a charm for me.
Tell me if you see something wrong or which could be optimized :)
Michaël
Instead of using the request module, use headless browsers like PhantomJS and zombie.js. You can even emulate user interaction with these.
I'm writing an API wrapper for an external API, to be used in our application.
I have adopted a test-driven approach for this project but since I have little to no experience with writing API wrappers, I'm not sure if I'm on the right track.
I understand that I should not be testing the external API, nor should I be hitting the network in my tests. I'm using Nock to mock my requests to the API.
However, I'm not sure I'm doing this correctly.
I made some requests to the API using curl and put the (XML) response in a file, for example: /test/fixtures/authentication/error.js:
module.exports = "<error>Authorization credentials failed.</error>"
Since I don't want to hit the network, but want to make sure my wrapper parses the XML to JSON, I figured I needed sample data.
My test looks like this:
describe("with an invalid application key", function() {
var cl, api;
before(function(done) {
api = nock(baseApi)
.get('/v1/auth/authenticate')
.reply(200, fixtures.authentication.error);
done();
});
after(function(done) {
nock.cleanAll();
done();
});
it("returns an error", function(done) {
cl = new APIClient(auth.auth_user, auth.auth_pass, "abcd1234");
cl.authenticate(function(err, res) {
should.exist(err);
err.should.match(/Authorization credentials failed./);
should.not.exist(res);
api.isDone().should.be.true;
done();
});
});
});
With my tested code looking like this:
APIClient.prototype.authenticate = function(callback) {
var self = this;
request({
uri: this.httpUri + '/auth/authenticate',
method: 'GET',
headers: {
auth_user: this.user,
auth_pass: this.pass,
auth_appkey: this.appkey
}
}, function(err, res, body) {
if (err) {
return callback('Could not connect to the API endpoint.');
}
self.parser.parseXML(body, function(err, result) {
if (err) { return callback(err); }
if (result.error) { return callback(result.error); }
self.token = result.auth.token[0];
return callback(null, result);
});
});
};
Now, this seems to be working fine for the authentication side of things (I also have a 'success' fixture, which returns the 'success' XML and I check if the returned JSON is actually correct.
However, the API I'm using also has endpoints like:
/data/topicdata/realtime/:reportxhours/:topics/:mediatypes/:pageIndex/:pageSize
I'm not sure how to test all (should I?) possible combinations with URLs like those. I feel like I can hardly put 30 XML responses in my fixtures directory. Also, when mocking responses, I'm afraid to miss out on possible errors, edge cases, etc. the external API might return. Are these valid concerns?
If anyone has any pointers, and/or knows of any open-source and well-tested API wrappers I could take a look at, I'd be very grateful.
I think your concern is very valid and I suggest you to also build tests using Zombie or other simular request-based testing frameworks.