I have a function here I'm writing a test for that calls another function on an external server (parse.com). Its my first time doing this type of thing.
How do I write a test so that second function function returns a specific value?
$scope.signInClick = function() {
console.log('Login was clicked');
console.log('$scope.user.username: ' + $scope.user.username);
console.log('$scope.user.password: ' + $scope.user.password);
$scope.loading = $ionicLoading.show({
content: 'Logging in',
animation: 'fade-in',
showBackdrop: true,
maxWidth: 200,
showDelay: 0
});
Parse.User.logIn(($scope.user.username) , $scope.user.password, {
success: function(_user) {
console.log('Login Success');
console.log('user = ' + _user.attributes.username);
console.log('email = ' + _user.attributes.email);
$ionicLoading.hide();
$rootScope.user = _user;
$rootScope.isLoggedIn = true;
$state.go('tab.home');
},
error: function(user, err) {
$ionicLoading.hide();
// The login failed. Check error to see why.
if (err.code === 101) {2
$scope.error.message = 'Invalid login credentials';
} else {
$scope.error.message = 'An unexpected error has ' +
'occurred, please try again.';
}
console.log('error message on Parse.User.logIn: ' + $scope.error.message);
$scope.$apply();
}
});
$state.go('tab.home');
};
I have to dictate that Parse.User.logIn returns an error, or a success, or an error of a particular type, so forth..
How do I mock that?
EDIT:
So, still been working on this, and I figured out one way to 'do' it, which was create a spy object just for the Parse.User.logIn (which is really the only thing being called).
But out of curiosity, why doesn't it work when I do that, but call $scope.signInClick ? Is there a way to get that function to call my spied on Parse object? When I try to call $scope.signInClick, I get that Parse is undefined.
Another thing: Is doing it this way missing the point of testing? Is this just dictating some code to do something, when we should be paying attention to the code that's actually in the app instead?
You can use dependency injection or monkey patching to replace the real Parse functions with your own.
var Parse = {
Unit: {
logIn: function(userName, pass, callbackObj) {
callbacObj.success({
attributes: {
username: "testuser",
email: "testuser#example.com"
}
});
}
}
};
The above is an example of the positive case but you could change it around to always call the error function instead.
There is no indication as to where Parse comes into the piece of code you've provided but basically what you should do is create a simplified mocked object that will act as the service, providing you with the results you'd normally expect from the real server.
Related
I'm trying to add access control to my Angular app and running into some puzzling troubles...
On the front-end, the authentication function is being called repeatedly until it is stopped for being too large of a call stack. Each time, the $http request within the function is triggering the errorCallback function. The expected behavior is for the auth function to fire once every time ui-router's state changes, changing $rootScope values that indicate the browser's level of authentication.
First, the factory responsible for making the GET request:
.factory ('authRequest', function ($http) {
return {
authStatus : function() {
$http.get('/auth', {'withCredentials' : true}).then(function successCallback(response) {
console.log("Successful authorization check.");
return response.status;
}, function errorCallback(response) {
if (response.status) {
console.log("Failed to authenticate.");
return response.status;
}
console.log("Failed to receive a response.");
return 'errNoResponse';
});
}
}
})
Then, the ng-controller for processing the factory's response:
//Navbar controller, set to fire upon each state change and verify authorization.
.controller('navCtrl', function ($rootScope, $state, authRequest) {
$rootScope.$on('$stateChangeStart', function (event, toState) {
console.log('authRequest value: ' + [authRequest]);
if (authRequest.authStatus === 202) {
console.log('Auth check succeeded, assigned user privileges.');
$rootScope.loggedIn = true;
$rootScope.loggedInAdmin = false;
if (toState = 'users' || 'login') {
event.preventDefault();
}
} else if (authRequest.authStatus === 222) {
console.log('Auth check succeeded, assigned admin privileges.');
$rootScope.loggedIn = true;
$rootScope.loggedInAdmin = true;
if (toState = 'login') {
event.preventDefault();
}
} else {
console.log('Auth check failed.');
$rootScope.loggedIn = false;
$rootScope.loggedInAdmin = false;
event.preventDefault();
$state.go('login');
}
});
})
Meanwhile, on the back-end, I'm not seeing evidence of the /auth Express route being reached with any of the requests. I have a console log set to go off when /auth receives a GET request, but I'm not seeing any activity in the console. Every other Express route is being accessed without issue. The expected behavior is to receive the request, decode the request's JWT cookie header, then send a response code back according to what sort of user privileges are listed. Here's the Express route for /auth:
// GET /auth. Fired every time the app state changes. Verifies JWT authenticity and sends a response based on the user's privileges. 202 is an auth'd user, 222 is an auth'd admin. 401 is no token or error.
app.get('/auth', function (req, res, next) {
console.log('Authorization request received.')
var decoded = jwt.verify(req.cookie, [JWTAuthSecret]);
if (decoded) {
if (decoded.admin === true) {
return res.status(222).send(res.status);
console.log(decoded.sub + ' is an admin, responded with code 222');
} else {
return res.status(202).send(res.status);
console.log(decoded.sub + ' is not an admin, responded with code 202');
}
} else {
return res.status(401).send(res.status);
console.log('Decode failed, responded with code 401');
};
});
With the current setup, the app is hanging indefinitely. As mentioned earlier, a ton of auth requests are being produced upon each state change. Each one logs an "authRequest value: [object Object]" then "Auth check failed." Eventually I get the following error:
angular.js:13550RangeError: Maximum call stack size exceeded
at angular.js:10225
at n.$broadcast (angular.js:17554)
at Object.transitionTo (angular-ui-router.js:3273)
at Object.go (angular-ui-router.js:3108)
at app.js:275
at n.$broadcast (angular.js:17552)
at Object.transitionTo (angular-ui-router.js:3273)
at Object.go (angular-ui-router.js:3108)
at app.js:275
at n.$broadcast (angular.js:17552)
So there seems to be a problem with the frequency of the calls on the front end, as well as a problem with actually getting the data sent to the /auth route.
This is my first time working with Angular factories, so my instinct is to assume my factory implementation is wonky... I haven't figured out how it might be fixed on my own, though.
Thanks for reading, hope I can get some advice on what to change to make this work.
I see a couple issues. This might not solve everything, but will hopefully help narrow things down.
One is that authRequest.authStatus is a function but you're never calling it. In your controller, you need to call the function. That's part of the reason nothing's pinging the backend.
authRequest.authStatus().then(function(status) {
if (status === 202) {
//do stuff
} else if (status === 222) {
//do other stuff
}
});
Now, in your factory, you're not returning anything to the function, so make sure you do that.
.factory ('authRequest', function ($http) {
return {
authStatus : function() {
return $http.get('url').then(callbacks);
}
}
})
The accepted answer provided me with solutions to the front-end troubles I was facing. The GET request to /auth was successfully being sent to Express as intended, but was still responding with an error code despite a valid authentication cookie.
Checking the backend logs, I was receiving an error saying JsonWebTokenError: jwt must be provided when trying to decode the JWT. Turns out req.cookie isn't the correct syntax to check the request's cookie - req.cookies is.
After changing that, my cookie output went from being undefined to [object Object]. The error I was getting changed as well to TypeError: jwtString.split is not a function.
After double checking the way cookies are referenced in the Express docs, I realized I wasn't calling the JWT cookie by name. The updated code is:
app.get('/auth', function (req, res, next) {
console.log('Authorization request received.')
console.log ('Cookie Data: ' + req.cookies.CookieName);
var decoded = jwt.verify(req.cookies.CookieName, [JWTAuthSecret]);
if (decoded) {
if (decoded.admin === true) {
return res.status(222).send(res.status);
console.log(decoded.sub + ' is an admin, responded with code 222');
} else {
return res.status(202).send(res.status);
console.log(decoded.sub + ' is not an admin, responded with code 202');
}
} else {
return res.status(401).send(res.status);
console.log('Decode failed, responded with code 401');
};
});
With this change and the front-end changes, the app's authentication and authorization are working as intended.
I am creating a REST api and I've defined this error handler:
function handleError(res, reason, message, code) {
console.log("ERROR: " + reason);
res.status(code || 500).json({"error": message});
}
But I think it needs some kind of interruption so that the following code is not executed, because if I implement my post method like this:
app.post("/contacts", function(req, res) {
var newContact = req.body;
newContact.createDate = new Date();
if (!(req.body.firstName || req.body.lastName)) {
handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
}
db.collection(CONTACTS_COLLECTION).insertOne(newContact, function(err, doc) {
if (err) {
handleError(res, err.message, "Failed to create new contact.");
} else {
res.status(201).json(doc.ops[0]);
}
});
});
The server crashes and the invalid insertion is made into the database. I could solve this problem with an else for the insertion part, but I wanted to know if there is a way of not doing the rest of the method if the function handleError gets called.
Just return after call handleError function
//
if (!(req.body.firstName || req.body.lastName)) {
handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
return;
}
//
A return statement will stop the rest of the function being executed.
Simply edit your handleError function so that the last line will be:
return res.status(code || 500).json({"error": message});
Or, in your /contacts handler :
if (!(req.body.firstName || req.body.lastName)) {
return handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
}
Obvious answer is return or if somewhere higher up in the call stack you have error handling, throwing is also valid flow control, but depending on your use-case, there might be a cleaner method.
I'm guessing you are using express. Check this out.
Preface: I'm new to JavaScript. If this question is startlingly stupid, that's (part of) the reason.
Begin Update
I found the answer to my question prior to the flag as a dupe. I solved it with a try-catch block. This answer does not reference a try-catch block.
How do I return the response from an asynchronous call?
End Update
I'm attempting to create an Alexa project from scratch (well, at least without one of Amazon's templates). I've written the "guts" of the app and I've tested my functions with chai. Things were going swimmingly until I tried to wire up some intents.
I can see my intents are being sent based on console.log statements I've thrown in the helperClass, but the return values aren't making it back to my index.js file.
Two questions:
What am I mucking up?
How do I fix it?
Here's what I've done:
Based on that, I dug around to see what's going on in my index.js file's headers and I saw this:
var Alexa = require('alexa-app');
So I went to alexa-app and saw that it uses bluebird, which suggests to me that I'm dealing with a promise problem. Further, I saw this in the log when I send a request that works:
preRequest fired
postRequest fired
When a request doesn't work, I only see:
preRequest fired
I'm using Big Nerd Ranch's "Developing Alexa Skills Locally with Node.js".
Here's my problematic intent in my index.js file:
app.intent('getDaysFromNow', {
'slots': {
'INPUTDATE' : 'AMAZON.DATE'
},
'utterances': ['{|number of|how many} {days} {until|from|since} {|now|today} {|is|was} {-|INPUTDATE}'] // almost perfect
},
function(req, res) {
console.log('app.intent getDaysFromNow fired');
//get the slot
var inputDate = req.slot('INPUTDATE');
var reprompt = 'Ask me how many days until or from a specified date.';
if (_.isEmpty(inputDate)) {
console.console.log('app.intent daysFromNow blank request');
var prompt = 'I didn\'t hear the date you want.';
res.say(prompt).reprompt(reprompt).shouldEndSession(false);
return true;
} else {
console.log('getDaysFromNow slot is not empty.');
var dateHelper = new DateHelper();
dateHelper.getDaysFromNow(inputDate).then(function(daysFromNow) {
console.log(daysFromNow);
res.say(dateHelper.formatDaysFromNowResponse(daysFromNow)).send(); // FIXME
}).catch(function(err) {
console.log(err.statusCode);
var prompt = 'Hmm...I don\'t have a date for that ' + inputDate;
res.say(prompt).reprompt(reprompt).shouldEndSession(false).send();
});
return false;
}
}
);
I know it's getting sent, but the value's not getting back to index.js. I think I've got a return problem. Here's the function in my helperClass.js whose return isn't getting back to index.js
// Takes AMAZON.DATE string as its argument
DateHelper.prototype.getDaysFromNow = function(inputDate) {
if (isValidDate(inputDate)) { // validate it
// if it's valid, run the function
inputDate = moment(inputDate, "YYYY-MM-DD").startOf('day'); // inputDate starts as a string, recast as a moment here
// create currentDate moment from current Date()
var currentDate = moment(new Date()).startOf('day');
// Calculate daysFromNow here
var daysFromNow = inputDate.diff(currentDate, 'days');
console.log("\t" + 'daysFromNow = ' + daysFromNow);
// ORIGINAL CODE
// return daysFromNow;
// EXPERIMENTAL CODE
return this.daysFromNow.then(
function(response) {
return response.body;
}
);
} else {
// throw an error
throw new Error("getDaysFromNow(): argument must be valid AMAZON.DATE string");
}
};
Thank you for reading. I welcome your suggestions.
It turns out it's not the way the return is sent from helperClass. The issue is how the call was made in the first place.
For the helperClass, I reverted to the original return.
// This is the return from `helperClass`
return daysFromNow;
In the index.js class, I put the call to helperClass in a try-catch block, like so:
app.intent('getDaysFromNow', {
'slots': {
'INPUTDATE': 'AMAZON.DATE'
},
'utterances': ['{|number of|how many} {days} {until|from|since} {|now|today} {|is|was} {-|INPUTDATE}'] // almost perfect
},
function(req, res) {
console.log('app.intent getDaysFromNow fired');
//get the slot
var inputDate = req.slot('INPUTDATE');
var reprompt = 'Ask me how many days until or from a specified date.';
if (_.isEmpty(inputDate)) {
console.log('app.intent daysFromNow blank request');
var prompt = 'I didn\'t hear the date you want.';
res.say(prompt).reprompt(reprompt).shouldEndSession(false);
return true;
} else {
// ** CHANGED: This is the part that changed. **
console.log('getDaysFromNow slot is not empty.');
var dateHelper = new DateHelper();
try {
var daysFromNow = dateHelper.getDaysFromNow(inputDate);
res.say(dateHelper.formatDaysFromNowResponse(daysFromNow)).send();
} catch (error) {
console.log("error", error);
var prompt = 'Hmm...I don\'t have a date for that ' + inputDate;
res.say(prompt).reprompt(reprompt).shouldEndSession(false).send();
}
return false;
}
}
);
Hopefully, someone finds this helpful if they run into the same problem.
I am using masterkey yet still getting unauthorized error when trying to save an object. Can anyone figure out why? I can see that "started making ticket" gets logged. and the request.params all pull the correct information.
Also, a side question...I am under the impression that if response.error is executed it stops the code, but then will send that error message to the function that called this cloud code and then run any error handling there. I have a console.log error message in this cloud code, and then an error alert in the function that called it. I am getting the console log to show, but not the alert. Is my assumption wrong in that it doesnt get passed, and that it actually just terminates the entire thing upon executing response.error?
Parse.Cloud.define("createRecord", function(request, response) {
var caseClass = Parse.Object.extend("Cases");
var ticket = new caseClass();
console.log("started making ticket");
ticket.title = request.params.title;
ticket.category = request.params.category;
ticket.priority = request.params.priority;
ticket.description = request.params.cmnts;
ticket.save(null, {useMasterKey: true}).then(function() {
response.success();
}, function(error) {
response.error("error response: " + error.message);
});
Try this:
Parse.Cloud.run("createRecord", {something: yourData}, {...
and:
Parse.Cloud.define("createRecord", function (request, response) {
var something = request.params.something;
var caseClass = Parse.Object.extend("Cases");
var ticket = new caseClass();
console.log("started making ticket");
ticket.set("title", something.title);
ticket.set("category", something.category);
ticket.set("priority", something.priority);
ticket.set("description", something.cmnts);
ticket.save(null, {useMasterKey: true}).then(function() {
response.success();
}, function(error) {
response.error("error response: " + error.message);
});
I have following autoform hook code. How can I get value outside of method.call.
My problem is that when I run method.call, then 'chi' value is undefined. Whereas, on server there is '1' record.But chi doesn't get 'myResult' value. If I comment out the method.call and return 'Gogo', then 'chi' gets this value correctly. Can some one guide me what I am doing wrong and how it can be rectified.
Code:
before: {
method: function(doc) {
var retVal = false ;
var pai = Q.fcall(function(){
if(!_.isEmpty(doc) && _.pick(doc, 'name') ) {
console.log('Ist level, true condition: ', doc);
return true;
}
else{
console.log('Ist level, false condition: ', doc);
return false;
}
})
.then(function(check){
console.log('Check value: ', check);
if( check ){
Meteor.call('CategoryNameAvailable', doc.name, function (error, result) {
console.log('Returned result from server', result);
if (!result) {
if(Contexts.Category.keyIsInvalid('name')){
Contexts.Category.resetValidation('name');
}
console.log('Returned result from server inside if condition ', result);
Collections.Category.simpleSchema().namedContext("CategoryInsertForm").addInvalidKeys([{
name: "name",
type: "notUnique"
}]);
console.log('Doc value in meteor call function: ', doc);
Session.set('retVal', true);
console.log('retVal value in meteor call function: ', retVal);
}
return 'myResult';
});
// return 'Gogo';
/* Meteor call End */
}
})
.then(function(chi){
console.log('Chi value: ', chi);
})
.done();
console.log('Pai value-2: ', pai);
} /* End of method */
} /* End of 'before' hook */
You could check this out https://github.com/stubailo/meteor-reactive-method
It might solve your problem
Do you think you could add in the file where you're defining your method? I had a similar problem recently attempting to do something similar, and it had to do with the formatting of my Method definition.
For me it was misplacing where I was returning data within my method definition. In another instance of another similar problem, I wasn't subscribing to the Collection on the client side.
If thats not the issue, and your call is returning data correctly, its only not passing it outside of the context of the call, you could try and use Session.set to define a session variable that can then be called whenever you need the data.
Its going to be difficult to tell exactly whats going on though without the context of the Method definition.