I'm writing Jasmine tests for a Node.js API. I'm trying to test the create a user functionality. The test would look like this:
describe('User test', function() {
describe('Post /api/saveUser'), function() {
it('saves a new user', function(done) {
request.post({url: 'http://website/api/saveUser', form:{email:testU.email, password:testU.password}, headers: {'authorization': adminU.jwt}}, function(err, res, body) {
expect(res.statusCode).toBe(200);
});
});
});
});
So I need to authenticate the admin user (adminU) in the spec to get the valid token to pass in the request. This is done using another endpoint.
request.post({url: 'http://website/api/authenticate', form:{email:adminU.email, password: adminU.password}}, function(err, res, body) {
adminUser.jwt = JSON.parse(res.body).token;
});
But how do I combine these. If I slot the authentication code above the User test block, the user tests are run before the response from the authentication endpoint is received. The obvious choice is to wrap the user tests in the callback from the authenticate api, but then when I run the tests Jasmine doesn't actually run them.
What's the best way to use the results of a callback in the Jasmine specs?
You need to wait until the request of the auth call is finished.
it('saves a new user', function(done) {
request.post({
url: 'http://website/api/authenticate',
form: {
email: adminU.email,
password: adminU.password
}
}, function(err, res, body) {
adminUser.jwt = JSON.parse(res.body).token;
request.post({
url: 'http://website/api/saveUser',
form: {
email: testU.email,
password: testU.password
},
headers: {
'authorization': adminU.jwt
}
}, function(err, res, body) {
expect(res.statusCode).toBe(200);
done();
});
});
});
If you need the token in more then one test, i would recommend to put it in the before block. If you do this, jasmine runs this once before starting the test and you don't have to make the auth call in every test case.
before(function(done){
request.post({
url: 'http://website/api/authenticate',
form: {
email: adminU.email,
password: adminU.password
}
}, function(err, res, body) {
if(err) done(err);
adminUser.jwt = JSON.parse(res.body).token;
done();
});
});
Also, i recommend supertest for testing apis. See https://www.npmjs.com/package/supertest
Related
I am registering a user and after successful registration I want alert something in client side. So, what I do in server side, after registration I'm sending a value "registered" then when it gets that value, my client side would know that user is registred but I don't know how to get that value in my client side.
router.post('/registration', function (req, res, next) {
var stud = {
username: req.body.username,
email: req.body.email,
password: req.body.password,
admin: 0
};
mongo.connect(url, function (err, db) {
assert.equal(null, err);
db.collection('user-data').insertOne(stud, function (err, result) {
assert.equal(null, err);
console.log('Student inserted');
db.close();
res.json('registred');
})
})
});
My client side code
$('.MyForm').on('submit', function(event){
event.preventDefault();
$.ajax({
url: '/registration',
method: 'post',
success: function(response){
}
})
});
There is not very much because I don't know what to do next
All you still need to do is put the alert in the callback to the ajax request
success: function(response) {
alert('User registration succeeded!')
}
Since you're using HTTP protocol, why not use its cohesive response codes to indicate which action has happened? For example:
200 - user successfully created
400 - malformed input data
409 - one of unique user's model field has been already taken
And so on. Use method 'status' on response object to set appropriate return code. I assume that you are trying to create server with REST's rules in mind, therefore your endpoint should return freshly created entity on POST request.
mongo.connect(url, function (err, db) {
assert.equal(null, err);
db.collection('user-data').insertOne(stud, function (err, result) {
if(err) {
/* At this point, you may decide to check what error has been returned to decide which code will be returned*/
return res.status(400).json(err);
}
console.log('Student inserted');
db.close();
res.status(200).json(result);
})
})
On client side, code might be much more pleasant to eye after you would refactor it to use jQuery's ( since ver. 1.5.1 )'Promise API'
$.ajax({
url: 'http://localhost:8000',
method: 'POST'
})
.done(function( data ) {
})
.fail(function (reason) {
})
I've made some vimeo api calls to get videos, but when I do a get request on the server it responds with the html on that path instead of the data from the server. I'm also using angular-client-side-auth (https://github.com/fnakstad/angular-client-side-auth). I'm new to this, so I'm struggling to understand why this happens.
server.js
app.get('/api/mostviewed', function (req, res) {
MostViewed.find({}, { _id: 0, iframe: 1 }, function (err, docs) {
res.json(docs);
});
});
inside client-side-auth's routes.js there's this, which causes it all(This file is on the server-side):
{
path: '/*',
httpMethod: 'GET',
middleware: [function(req, res) {
var role = userRoles.public, username = '';
if(req.user) {
role = req.user.role;
username = req.user.username;
}
res.cookie('user', JSON.stringify({
'username': username,
'role': role
}));
res.render('index');
}]
}
How can I solve this? I want to maintain the path: '/*', or change it while keeping the function similar, so I can get my data from the server. Or is there a different way to solve this?
EDIT:
Solution
{
path: '/api/mostviewed',
httpMethod: 'GET',
middleware: [Video.getmostviewed]
},
inside Video.js I made this:
getmostviewed: function(req,res){
MostViewed.find({}, { _id: 0, iframe: 1 }, function (err, docs) {
res.json(docs);
});
}
In your case, client could not find logic corresponding to /api/mostviewed, thus reached /* and displayed html instead of json.
Possible solution
Add following similar logic before /*
{
path: '/api/*',
httpMethod: 'GET',
middleware: [function(req, res) { // sample middleware logic
var role = userRoles.public, username = '';
if(req.user) {
role = req.user.role;
username = req.user.username;
}
res.cookie('user', JSON.stringify({
'username': username,
'role': role
}));
}]
},
I'm implementing a server that handles chat messages. In some cases I want to access data from a JIRA instance. I'm using passport-atlassian-oauth strategy for authenticating with JIRA and BearerStrategy for requests, but my issue is that the authentication is only valid in the browser after a user has given "My Server" read and write access to JIRA. In many guides they just call res.redirect('/successfulLogin') or something similar after a successful authentication, but I would instead like to do a rest call to JIRA, process the data and send it to my connected client application.
How do I do that?
I'm completely new to all this and everything just spins around in my head. I save and have access to the token used for authentication and when I for instance navigate to .../test/access_token=?[token] in my browser it works.
passport.use(new BearerStrategy(
function(token, done) {
// Find user by token
client.smembers('access_token:' + token, function(err, replies) {
if (err) {
return done(err);
}
// if user found
// TODO: yet again, hard coded for one
if (replies.length > 0) {
console.log('SHOULD BE 1:', replies[0]);
client.hgetall('users:' + replies[0], function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false);
}
return done(null, user, {scope: 'all'});
});
}
});
}
));
As you can see it's hard coded for just one user and I'm using Redis as a "database".
passport.use(new AtlassianOAuthStrategy({
applicationURL: 'http://localhost:2990/jira',
callbackURL: '/auth/atlassian-oauth/callback',
consumerKey: RsaPublicKey,
consumerSecret: rsaPrivateKey,
clientId: 'MyBot'
},
function(accessToken, tokenSecret, profile, done) {
// Find user
client.hgetall('users:1', function(err, user) {
if(err) {
return done(err);
}
// user not found
if(!user) {
// create new user, no worries!
// TODO: HARD CODED FOR ONE USER
client.hmset('users:1', 'id', profile.id, 'access_token', accessToken, function(err, res) {
client.sadd('id:admin', '1');
client.sadd('access_token:'+ accessToken, '1');
client.hgetall(profile.id, function(err, user) {
return done(null, user);
});
});
} else {
// Update access token!
client.hmset(profile.id, 'access_token', accessToken, function() {
client.sadd('access_token:' + accessToken, '1', function() {
client.hgetall(profile.id, function(err, result) {
return done(null, user);
});
});
});
}
});
}
));
Here's the rest
app.get('/auth/atlassian-oauth',
passport.authenticate('atlassian-oauth', {session: false, scope: []}),
function(req, res) {
console.log('- Function: /auth/atlassian-oauth - should not be called)');
});
app.get('/auth/atlassian-oauth/callback',
passport.authenticate('atlassian-oauth', {session: false, failureRedirect: '/login'}),
function(req, res) {
console.log('- Function: /auth/atlassian-oauth/callback - Authentication successful!', req.user.access_token);
// Update access token!
// Should I even do this? Shouldn't I already have the correct token?
client.hmset('users:1', 'access_token', req.user.access_token, function() {
client.sadd('access_token:' + req.user.access_token, '1', function() {
client.hgetall('users:1', function(err, result) {
res.redirect('/test?access_token=' + req.user.access_token);
});
});
});
});
So now that you've seen some relevant (just tell me and I'll post more) code, how do I send a rest call to JIRA without getting a 401? :)
EDIT: Any help appreciated! You would make me really happy if you just can point me into the right direction!
Ok. I figured it out! First of all you want to save both you access token and token secret to you db in AtlassianOAuthStrategy. Second, in order to send a REST call to a third party service you can just use http request with OAuth:
var request = require('request');
var oauth = {
signature_method : 'RSA-SHA1',
consumer_key : RsaPublicKey,
private_key : rsaPrivateKey,
token : [get access_token from you db],
token_secret : [get token_secret from you db]'
};
var url = 'http://localhost:2990/jira/rest/api/2/issue/' + id;
request.get({url:url, oauth:oauth, json:true}, function (e, r, issue) {
console.log(issue)
});
Now that everything is working I'm going to start refactoring and reading some more documentation in order to make the design prettier and figure out how to use Redis properly :)
I have some Sails.js API tests (using Mocha) that make use of SuperTest's .end() method to run some Chai assertions on the response.
I call the test's done() callback after the assertions, but if an assertion error is thrown, the test times out.
I can wrap the assertions in a try/finally, but this seems a bit icky:
var expect = require('chai').expect;
var request = require('supertest');
// ...
describe('should list all tasks that the user is authorized to view', function () {
it('the admin user should be able to see all tasks', function (done) {
var agent = request.agent(sails.hooks.http.app);
agent
.post('/login')
.send(userFixtures.admin())
.end(function (err, res) {
agent
.get('/api/tasks')
.expect(200)
.end(function (err, res) {
try {
var tasks = res.body;
expect(err).to.not.exist;
expect(tasks).to.be.an('array');
expect(tasks).to.have.length.of(2);
} finally {
done(err);
}
});
});
});
});
Any suggestions on how to better deal with this? Perhaps Chai HTTP might be better?
According to Supertest's documentation, you need to check for the presence of err and, if it exists, pass it to the done function. Like so
.end(function (err, res) {
if (err) return done(err);
// Any other response-related assertions here
...
// Finish the test
done();
});
You can pass out login logic from test.
// auth.js
var request = require('supertest'),
agent = request.agent;
exports.login = function(done) {
var loggedInAgent = agent(sails.hooks.http.app);
loggedInAgent
.post('/login')
.send(userFixtures.admin())
.end(function (err, res) {
loggedInAgent.saveCookies(res);
done(loggedInAgent);
});
};
And then just use it in your test:
describe('should list all tasks that the user is authorized to view', function () {
var admin;
before(function(done) {
// do not forget to require this auth module
auth.login(function(loggedInAgent) {
admin = loggedInAgent;
done();
});
});
it('the admin user should be able to see all tasks', function (done) {
admin
.get('/api/tasks')
.expect(function(res)
var tasks = res.body;
expect(tasks).to.be.an('array');
expect(tasks).to.have.length.of(2);
})
.end(done);
});
it('should have HTTP status 200', function() {
admin
.get('/api/tasks')
.expect(200, done);
});
});
With such approach you should not login your admin for each test (you can reuse your admin again and again in your describe block), and your test cases become more readable.
You should not get timeouts with this approach because .end(done) guaranties that your test will finish without errors as well with them.
I have the following Controller for my login page:
// Authentication Controller
// the basics of Passport.js to work.
var AuthController = {
// localhost:1337/login Render the login page
// <form role="form" action="/auth/local" method="post">
// <input type="text" name="identifier" placeholder="Username or Email">
// <input type="password" name="password" placeholder="Password">
// <button type="submit">Sign in</button>
// </form>
login: function(req, res) {
var strategies = sails.config.passport,
providers = {};
// Get a list of available providers for use in templates.
Object.keys(strategies).forEach(function(key) {
if (key === 'local') return;
providers[key] = {
name: strategies[key].name,
slug: key
};
});
// Render the `auth/login.ext` view
res.view({
providers: providers,
errors: req.flash('error')
});
},
// Log out a user and return them to the homepage
// Passport exposes a logout() function on req (also aliased as logOut()) that
// can be called from any route handler which needs to terminate a login
// session. Invoking logout() will remove the req.user property and clear the
// login session (if any).
logout: function(req, res) {
req.logout();
res.redirect('/login');
},
// The registration form is Just like the login form
register: function(req, res) {
res.view({
errors: req.flash('error')
});
},
// Create a third-party authentication endpoint
provider: function(req, res) {
passport.endpoint(req, res);
},
// Create a authentication callback endpoint
// This endpoint handles everything related to creating and verifying Pass-
// ports and users, both locally and from third-aprty providers.
// Passport exposes a login() function on req (also aliased as logIn()) that
// can be used to establish a login session. When the login operation
// completes, user will be assigned to req.user.
callback: function(req, res) {
passport.callback(req, res, function(err, user) {
req.login(user, function(err) {
// If an error was thrown, redirect the user to the login which should
// take care of rendering the error messages.
if (err) {
res.redirect('/login');
}
// Upon successful login, send the user to the homepage were req.user
// will available.
else {
res.redirect('/');
}
});
});
}
};
module.exports = AuthController;
I am using Mocha as my test framework. The application is based on Sails.
How would I write Mocha test cases and run them on the provided Controller?
I'm using supertest to call controllers as a user, to do so, first lift the sails in the before function so it can be used as the server by supertest.
before(function (done) {
Sails.lift({
// configuration for testing purposes
log: {
//level: 'silly'
level: 'silent'
},
hooks: {
grunt: false,
},
}, function (err, sails) {
done(err, sails);
});
}
Then initialize supertest with the url of your sails app
request = require('supertest');
agent = request.agent(appurl);
You can now write test to post / get data to test your controller from frontend as would a client.
it('should do the post and return whatever', function (done) {
agent.post('/controller/function')
.send(the_post_object_with_params)
.end(function (err, res) {
if (err) {
console.log(err);
}
console.log(res.body); // the content
done();
});
}
I think the main thing is to include sails application into your testcase. I also found no examples with controllers tests but some with models:
Is it possible to use the Mocha Framework against a model in Sails.js?
Cannot unit test my model in sailsjs