Meteor / Jasmine / Velocity : how to test a server method requiring logged in user? - meteor

Using velocity/jasmine, I'm a bit stuck on how I should test a server-side method requiring that there be a currently logged-in user. Is there a way to make Meteor think a user is logged in via stub/fake ?
myServerSideModel.doThisServerSideThing = function(){
var user = Meteor.user();
if(!user) throw new Meteor.Error('403', 'not-autorized');
}
Jasmine.onTest(function () {
describe("doThisServerSideThing", function(){
it('should only work if user is logged in', function(){
// this only works on the client :(
Meteor.loginWithPassword('user','pwd', function(err){
expect(err).toBeUndefined();
});
});
});
});

What you could do is add users just to your test suite. You could do this by populating these users in a the server-side test script:
Something like:
Jasmine.onTest(function () {
Meteor.startup(function() {
if (!Meteor.users.findOne({username:'test-user'})) {
Accounts.createUser
username: 'test-user'
... etc
Then, a good strategy could be to use the beforeAll in your test to login (this is client side):
Jasmine.onTest(function() {
beforeAll(function(done) {
Meteor.loginWithPassword('test-user','pwd', done);
}
}
This is assuming your test isn't logged in yet. You can make this more fancy by checking for Meteor.user() and properly logging out in an afterAll, etc. Note how you can handily pass the done callback to many of the Accounts functions.
Essentially, you don't have to mock a user. Just make sure you have the right users, with the correct roles, available in the Velocity/Jasmine DB.

Lets say you have a server side method like this:
Meteor.methods({
serverMethod: function(){
// check if user logged in
if(!this.userId) throw new Meteor.Error('not-authenticated', 'You must be logged in to do this!')
// more stuff if user is logged in...
// ....
return 'some result';
}
});
You do not need to make a Meteor.loginWithPassword before executing the method. All you got to do is stub the this.userId by changing the this context of the method function call.
All defined meteor methods are available on the Meteor.methodMap object. So just call the function with a different this context
describe('Method: serverMethod', function(){
it('should error if not authenticated', function(){
var thisContext = {userId: null};
expect(Meteor.methodMap.serverMethod.call(thisContext).toThrow();
});
it('should return a result if authenticated', function(){
var thisContext = {userId: 1};
var result = Meteor.methodMap.serverMethod.call(thisContext);
expect(result).toEqual('some result');
});
});
EDIT: This solution was only tested on Meteor <= 1.0.x

What are you testing and why does it require a user to be logged in? Most of the methods I have that need a user object I pass the user object into. This allows me to call from a test without actually being logged in. So in the actual running of the code I would pass...
var r = myMethod(Meteor.user());
but when running from the test I would call like...
it('should be truthy', function () {
var r = myMethod({_id: '1', username: 'testUser', ...});
expect(r).toBeTruthy();
});

I think that Meteor.server.method_handlers["nameOfMyMethod"] allows you to call/apply a Meteor method and supply this as the first parameter at least in the current version (1.3.3)
this.userId = userId;
Meteor.server.method_handlers["cart/addToCart"].apply(this, arguments);

Related

CreateUser Function Not Working in AngularFire - Firebase Simple Login?

I have the following controller that uses AngularFire
app.controller("authController", function($scope, $firebaseSimpleLogin){
var ref = new Firebase("https://myapp.firebaseIO.com/");
$scope.auth = $firebaseSimpleLogin(ref, function(error, user){
if(error){
console.log(error);
}
else if(user){
console.log(user);
}
else{
console.log("user logged out");
}
});
// This shows a valid object
console.log($scope.auth);
$scope.createAccount = function(){
console.log("found me");
$scope.auth.$createUser($scope.email, $scope.password, function(error, user){
console.log("something");
console.log(user);
if(!error){
console.log(user);
}
else{
console.log(error);
}
});
};
});
When I bind the $scope.createAccount function to an ng-click event and click on the bound button, console.log("found me") runs in the browser, but none of the other console.log commands in $scope.createAccount is shown.
The console.log($scope.auth) command I have before setting the $scope.createAccount function shows a valid object with the $createUser function defined.
I am not receiving any console errors when I run $scope.createAccount so I am assuming the call has been "successfully" made.
Why am I able to see the auth object, but not receiving a callback after calling $createUser?
This was happening because I was doing callbacks based on JavaScript notation instead of Angular notation. Since I was using the AngularFire methods (they have the same names as the vanilla JavaScript SDK methods, but with a $ sign in front of them), I needed to handle callbacks using Angular's $promise methodology.
I changed
$scope.auth.$createUser($scope.email, $scope.password, function(error, user){
// do things;
});
to
$scope.auth.$createUser($scope.email, $scope.password)
.then(function(user){
// do things if success
}, function(error){
// do things if failure
});
and the callback worked as expected.
There is an exception to the above with the vanilla JS firebaseSimpleLogin constructor vs Angular $firebaseSimpleLogin constructor. On the vanilla JS constructor, there are callbacks on the constructor that allows you to specify what your script should do when a user logs in / logs out. It follows the following format:
var auth = new firebaseSimpleLogin(ref, function(error, user){
if(error){
// do things if login failure
}
else if(user){
// do things when login succeeds
}
else{
// do things when user logs out
}
});
If you try to do the same with the Angular constructor like so:
$scope.auth = $firebaseSimpleLogin(ref)
.then(function(user){
// do things when login succeeds
}, function(error){
// do things when login fails
});
you'll receive an error. There are no callback methods with the Angular constructor. I am guessing this was done on purpose since with Angular, you have data binding and you can simply $watch $scope.auth.user for changes and perform operations in your app depending on the variable's state. When $scope.auth.user is set to null, the user is logged out. If the value is set to anything else than null, then the user is logged in.

How to get Meteor.user() to return on the server side?

in a file called /server/main.js (in order to ensure it is loaded last).
console.dir(Meteor.user());
Throws:
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
So I try to use, in the same file:
console.dir(this.userId);
returns:
undefined
so, not giving up, I'm thinking "that's fine I'll just read from the cookies in the header":
var connect = Npm.require('connect');
__meteor_bootstrap__.app.use(connect.query()).use(function(req, res, next) {
console.dir(req.headers);
next();
});
.... returns nothing in terms of cookies except for 'cookie: 'uvf=1''
I'm not sure what to conclude - this is senseless as I can otherwise use the Meteor.Account framework just fine, read/set user properties, etc. The server is clearly aware of the user, and the current user clearly logged in.
I'm at a complete loss, any explanation / hint / pointer would be greatly appreciated.
You have to use Meteor.user() in a place where a request is made from the client (such as a Meteor.methods or a Meteor.publish).
It can't be placed anywhere else because meteor wouldn't know at that point in the code the user is supposed to bound to. If there is a place a request of some form is made from the client it can do this:
In a Meteor.publish:
Meteor.publish("collection", function() {
//returns undefined if not logged in so check if logged in first
if(this.userId) {
var user = Meteor.users.findOne(this.userId);
//var user is the same info as would be given in Meteor.user();
}
});
In a Meteor.methods:
Meteor.methods({
"test":function() {
//should print the user details if logged in, undefined otherwise.
console.log(Meteor.user());
}
}
To use Meteor.user() on a server side route:
You need Meteor router installed as a package via meteorite to allow you to have a server rendered page. (installed via mrt install router)
A server side route could then handle the web request:
Meteor.Router.add('/awebpage', function(id) {
var userId = this.params.userid;
var logintoken = this.params.logintoken;
var isdirect = this.param.direct;
var user = Meteor.users.findOne({_id:userId,"services.resume.loginTokens.token":logintoken});
if(user) {
//the user is successfully logged in
return "You, "+user.profile.name+", are logged in!";
}
else
{
if(isdirect) {
return "<h3>Loading</h3><script>window.location.href="/awebpage?direct=true&userid="+localStorage.getItem("Meteor.userId") +"&logintoken="+localStorage.getItem("Meteor.loginToken")</script>";
}
else
{
return "Not logged in"
}
}
});
So now when you visit /awebpage it would check whether the user is logged in and do the thing you want when they are logged in. Initially there is a redirect to relay the data from localstorage back to the URI.
You can expose the userId with Meteor.publish() to global scope. Then you can use it with Meteor.Router's server side routes.
--
/server/publications.js
CurrentUserId = null;
Meteor.publish(null, function() {
CurrentUserId = this.userId;
});
-
/server/routes.js
Meteor.Router.add('/upload', 'POST', function() {
if (!CurrentUserId)
return [403, 'Forbidden'];
// proceed with upload...
});
You can use the logged in callback
Accounts.onLogin((obj)->
user = ob.user
)
Accounts.onLogin(function(obj){
var user = ob.user
})
I recently wrote a blog post describing solution to this: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94.
You basically need to set up a server route using a https://atmospherejs.com/mhagmajer/server-router package and you can get current user with this.userId just like with Meteor methods.

Accessing this.userId not working when calling from within Meteor.SetTimeout

I've been trying to access the this.userId variable from within a Meteor.methods call, but it doesn't seem to work when I try to call the method via Meteor.setTimeout or Meteor.setInterval.
This is what I've got:
if (Meteor.is_server) {
Meteor.methods({
getAccessToken : function() {
try {
console.log(this.userId);
return Meteor.users.findOne({_id: this.userId}).services.facebook.accessToken;
} catch(e) {
return null;
}
}
});
var fetch_feed = function() {
console.log(Meteor.call("getAccessToken"));
[...] // A bunch of other code
};
Meteor.startup(function() {
Meteor.setInterval(fetch_feed, 60000); // fetch a facebook group feed every minute
Meteor.setTimeout(fetch_feed, 3000); // initially fetch the feed after 3 seconds
});
}
Watching the terminal log, the this.userId always returns a null. But if I try calling the method from the client side, or through the console, it returns the correct ID.
How come this doesn't work from within a Meteor.setInterval? Is it a bug or am I doing something wrong?
Meteor userId's are associated with client connections. The server may interact with many clients and this.userId inside a method will tell you which client has asked for the method to be run.
If the server uses Meteor.call() to run a method then it will not have a userId since it is not running for any client.
The methods allow clients to call for functions to be run on the server. For things the server will trigger itself a javascript function will do.
There is a solution I used - sometimes you do not want to make the method a function but really want it to remain a method. In that case, a hack to make this work:
var uniqueVar_D8kMWHtMMZJRCraiJ = Meteor.userId();
Meteor.setTimeout(function() {
// hack to make Meteor.userId() work on next async
// call to current method
if(! Meteor._userId) Meteor._userId = Meteor.userId;
Meteor.userId = function() {
return Meteor._userId() || uniqueVar_D8kMWHtMMZJRCraiJ
};
Meteor.apply(methodName, args);
}
, 100);
Some brief explanation: we save Meteor.userId in Meteor._userId and overwrite Meteor.userId with a function that returns Meteor._userId() if it is true and otherwise the historic value of Meteor.userId() before any of this happened. That historic value is saved in an impossible to occur twice var name so that no context conflicts can happen.

How to get current user in custom route?

As per this answer I created my own route so that I could handle file uploads. Here's what I've got:
var router = Connect.middleware.router(function(route) {
route.post('/upload', function(req, res) {
var filename = req.headers['x-filename'];
var path = Path.join('.uploads', filename);
var writeStream = FileSystem.createWriteStream(path);
writeStream.on('error', function(e) {
console.error(e);
res.writeHead(500);
res.end();
}).on('close', function() {
Fiber(function() {
console.log(Meteor.user());
}).run();
res.writeHead(200);
res.end();
});
req.pipe(writeStream);
});
});
app.use(router);
This works great for uploading files, but when I try to acess Meteor.user() it gives me:
app/server/main.js:24
}).run();
^
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
at Object.Meteor.userId (app/packages/accounts-base/accounts_server.js:95:13)
at Object.Meteor.user (app/packages/accounts-base/accounts_server.js:100:25)
at app/server/main.js:23:36
Exited with code: 1
I can't see anything in the req object that might help me out.
Is there any way to get access to the user object?
For the time being, I'm getting the user ID client side and passing it along through the headers which I then use to look up server side:
route.post('/upload', function(req, res) {
Fiber(function() {
var userId = req.headers['x-userid'];
var user = Meteor.users.findOne({_id:userId});
if(user) {
...
} else {
res.writeHead(403,'User not logged in');
res.end();
}
}).run();
});
I don't like this because it's not at all secure. It would be easy to upload something under a different user's account.
Edit: Nevermind. The very act of calling Meteor.users.findOne({_id:userId}); somehow breaks the upload stream. Every file gets corrupt as soon as I put that in; they upload up to about 700 KB and then just stop and close the connection without error.
If it's still valid question.
The problem is that there is no way how to get Meteor.user() in this part of code.
But you can always reach Meteor.userId .. and it's not null if user is logged in .. so you can upload only for logged user. (if req.headers['x-userid'] == Meteor.userId)
The very act of calling Meteor.users.findOne({_id:userId}); somehow
breaks the upload stream.
Because it's reactive part.. so every time if Meteor.users collection is updated this part of code is executed again.
So if you can use only Meteor.userId (which is changed only if user is logged in/out) it should work fine.
I've run into this quite a few times, and it's frustrating. I don't think you can make Meteor.userId() calls from inside a fiber. I usually do a var userId = Meteor.userId(); before I call the fiber, and then reference that variable instead.

Meteor accounts framework signed/logged in event

I'm using the accounts-ui package and would like to process some javascript as soon as the user is logged in/and or registered.
Is there an event that gets called as soon as the user signs in?
The raw login API (eg loginWithFacebook, loginWithPassword, etc) has a callback that fires when login is complete, but this is not currently exposed through accounts-ui. This may change.
A potential workaround, inspired by Werner's suggestion, but also taking page load into account:
var oldUserId = undefined;
Meteor.autorun(function() {
var newUserId = Meteor.userId();
if (oldUserId === null && newUserId) {
console.log('The user logged in');
} else if (newUserId === null && oldUserId) {
console.log('The user logged out');
}
oldUserId = Meteor.userId();
});
You could check the result of the Meteor.userId() function inside Meteor.autorun():
Meteor.autorun(function() {
if (Meteor.userId()) {
console.log('The user logged in');
}
});
Just to give an alternative; I monkey patched the callback function. It looks a bit more complex because the credentialRequestCompleteHandler requires a function that returns a function but besides that its a plain monkey patch. Stick this in main.js or something that gets processed late and only once. I hope it helps for future reference.
var orgCallback = Accounts.oauth.credentialRequestCompleteHandler;
Accounts.oauth.credentialRequestCompleteHandler = function(callback){
return function (credentialTokenOrError) {
var tmpFunc = orgCallback(callback);
tmpFunc(credentialTokenOrError);
alert("do your own thing here");
}
}

Resources