My code of login.js is
var loginModule = angular.module('loginModule', [])
.controller('LoginCtrl', function ($scope, $auth, $location) {
if (!$auth.isAuthenticated()) {
$scope.oneAtATime = true;
$scope.login = function () {
$auth.login({userName: $scope.username, password: $scope.password, isEncript: false})
.then(function () {
console.log($auth.getMessage());
})
.catch(function (response) {
console.log(response);
});
};
}
else {
$location.path('/home');
}
$scope.status = 'true';
});
and unit test code is
describe('login()', function() {
it('should be able to handle login errors', function () {
var user = {
email: 'foo#bar.com',
password: '1234',
isEncript: false
};
this.$httpBackend.expectPOST('app/controller/apicall.php?serviceName=login').respond(200);
this.$auth.login(user).then(angular.noop, function () {
});
this.$httpBackend.flush();
expect(user.isEncript).toBe(false);
});
});
});
......................the error i am getting is below .......................................
$auth login() should be able to handle login errors FAILED
Error: Unexpected request: POST ../../controller/apicall.php?serviceName=login
Expected POST /app/controller/apicall.php?serviceName=login
help me to solve how should i solve this error.
Something is calling
../../controller/apicall.php?serviceName=login
You can work out whats calling that, or just change your
this.$httpBackend.expectPOST('app/controller/apicall.php?serviceName=login').respond(200);
to
this.$httpBackend.expectPOST('../../controller/apicall.php?serviceName=login').respond(200);
and that should do it.
This error say that your $auth service send request to the other URL.
Therefore at first you need check and test what do your $auth service when you call its 'login' method.
The better way in order to test is to have separate suites for $auth service (where you will test how it interact with backend) and for LoginCtrl controller (where you will mock $auth service and test only behavior of the controller).
You can use this plunker http://tinyurl.com/lv3gskf as example. (I use tinyurl.com because stackoverflow not allow to post links to plunker without code)
Related
I am currently trying to implement this AngularJS example Tutorial Example Login but instead of having the username and password stored as strings, I am trying to extract the from a local file.
I know that this is a bad practice but that is how I am trying to do it.
In the section AngularJS Authentication Service. Path: /modules/authentication/services.js in the example, the username and password are stored in a timeout function as:
$timeout(function(){
var response = { success: username === 'test' && password === 'test' };
if(!response.success) {
response.message = 'Username or password is incorrect';
}
callback(response);
}, 1000);
but I am trying to create a static json file which holders the username and password as objects. My idea was to make a $http.get request to the file and append the json objects to the username and password parameters like this:
var details;
$http.get("data.json").then(function(response){
$scope.details = response.data;
console.log(username);
});
$timeout(function () {
var response = { success: username === details.username && password === details.password
if (!response.success) {
response.message = 'Username or password is incorrect';
}
callback(response);
}, 1000);
but I am getting two errors :
1.ReferenceError: $scope is not defined
2.TypeError: Cannot read property 'username' of undefined
What is the esiest way to achieve what I am trying to do? Extract the
username and password from a json file and not have them as a static
string?
Some snippets are from http://jasonwatmore.com/post/2014/05/26/angularjs-basic-http-authentication-example
Live demo: http://plnkr.co/edit/WvOwk1?p=preview
What did I change?
The original login snippet is:
service.Login = function (username, password, callback) {
$timeout(function(){
var response = { success: username === 'test' && password === 'test' };
if(!response.success) {
response.message = 'Username or password is incorrect';
}
callback(response);
}, 1000);
};
I changed it to:
var promise = $http.get("data.json").then(function (response) {
return response.data;
});
service.Login = function (username, password, callback) {
promise.then(function (data) {
var success = data.some(function (user) {
if (user.username == username && user.password == password) {
callback({
success: true
});
return true;
} else {
return false;
}
});
if (!success) {
callback({
success: false,
message: 'Username or password is incorrect'
});
}
});
};
And added a data.json:
[{
"username": "test",
"password": "test"
}, {
"username": "test2",
"password": "test2"
}]
Why promise?
Since you use json file as db, you should load the json file with network. And you don't need to load the data.json since it doesn't change, so you can just load it once. Using promise.then to ensure the verification is after the $http.get.
If you want to load the "data.json" each time the user submits the form, just replace promise.then with
$http.get("data.json").then(function (response) {
return response.data;
}).then
check this example:
.controller('NameOfYourController', function($scope, $http, $timeout) {
$scope.details = ""; // you can even omit the declaration here
$http.get("data.json").then(function(response){
$scope.details = response.data;
console.log($scope.details.username); // or response.data.username
$timeout(function () {
// you get to the response obj using: response.data.your_object
// $timeout logic goes here
}, 1000);
});
});
note: if you are inside a service you do not have a $scope
I'm not entirely sure why $scope is undefined from your example. You're most likely not injecting it like you are with $http or $timeout. Another possibility is that you are trying to use $scope in something other than a controller.
As for your second error, details is undefined since it is set after the .then() promise is resolved. You would want to move your $timeout logic into that block of code like this:
var details;
$http.get("data.json").then(function(response){
$scope.details = response.data;
// ADD YOUR $timeout LOGIC HERE SO DETAILS IS NOT UNDEFINED
});
Looking at the example , are You trying to put function for reading json file in Service method. If you are doing it then there is no $scope available in service. Hence you are getting below error
ReferenceError: $scope is not defined
In your code , there is local variable details is defined but not initialized. hence its value is undefined.
var details;
and in timeout function you are trying to access details.username
username === details.username
hence you are getting below error
TypeError: Cannot read property 'username' of undefined
But if you want to do it most easiest way then just put your code in controller like below in this plunker. you should have creates data.json file to run below code.
'use strict';
angular.module('Authentication')
.controller('LoginController',
['$scope', '$rootScope', '$location', 'AuthenticationService','$http','$timeout',
function ($scope, $rootScope, $location, AuthenticationService,$http,$timeout) {
// reset login status
AuthenticationService.ClearCredentials();
var details;
$http.get("data.json").then(function(response){
$scope.details = response.data;
//alert($scope.details.username);
});
function Login(username,password,callback){
$timeout(function(){
var response = { success: username === $scope.details.username && password === $scope.details.password };
if(!response.success) {
response.message = 'Username or password is incorrect';
}
callback(response);
}, 1000);
}
$scope.login = function () {
$scope.dataLoading = true;
Login($scope.username, $scope.password, function(response) {
if(response.success) {
AuthenticationService.SetCredentials($scope.username, $scope.password);
$location.path('/');
} else {
$scope.error = response.message;
$scope.dataLoading = false;
}
});
};
}]);
But if you want to go with same controller and make changes in service then find answer by #blackmiaool which is absolutely perfect.
What is the esiest way to achieve what I am trying to do? Extract the
username and password from a json file and not have them as a static
string?
The first approach is most easiest approach but it is not a standard practice.
The second approach is correct and standard approach for what you are trying to do.
Observations based on the below errors :
1. ReferenceError: $scope is not defined
This error comes when we are trying to access the $scope object but forgot to inject as a dependency into the controller.
app.controller('MyCtrl', ['$scope', function ($scope) { ... }
2. TypeError: Cannot read property 'username' of undefined
What is username in your code ?
You should use $scope.details.username instead of only username.
Your code should be formatted like this :
angular.module('myApp',[]).controller('LoginController',['$scope', function ($scope) {
var details;
$http.get("data.json").then(function(response){
$scope.details = response.data;
console.log($scope.details.username);
});
$timeout(function () {
var response = { success: username === $scope.details.username && password === $scope.details.password
if (!response.success) {
response.message = 'Username or password is incorrect';
}
callback(response);
}, 1000);
}]);
In the code below, I'd like handling errors :
401 : redirect to a login page
other : display error message (received in the message of the error)
I don't find the right way to do this.
Any idea ?
Thanks,
Module.js
var app;
(function () {
app = angular.module("studentModule", []);
})()
Service.js
app.service('StudentService', function ($http) {
this.getAllStudent = function () {
return $http.get("http://myserver/api/Student/");
}
});
Controller.js
app.controller('studentController', function ($scope, StudentService) {
function GetAllRecords() {
var promiseGet = StudentService.getAllStudent();
promiseGet.then(function (pl) { $scope.Students = pl.data },
function (errorPl) {
$log.error('Some Error in Getting Records.', errorPl);
});
}
});
As with most problems, there are many different ways to handle errors from AJAX requests in AngularJS. The easiest is to use an HTTP interceptor as already pointed out. This can handle both authentication and errors.
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(['$rootScope', '$q', function($rootScope, $q) {
return {
responseError: function(rejection) {
var deferred;
// If rejection is due to user not being authenticated
if ( rejection.status === 401 ) {
$rootScope.$broadcast('unauthenticated', rejection);
// Return a new promise since this error can be recovered
// from, like redirecting to login page. The rejection and
// and promise could potentially be stored to be re-run
// after user is authenticated.
deferred = $q.defer();
return deferred.promise;
}
$rootScope.$broadcast('serverError', rejection);
// Just reject since this probably isn't recoverable from
return $q.reject(rejection);
}
}
};
}]);
The above interceptor is created using an anonymous function but factories can be used to handle one or many different interceptors. The AngularJS docs have decent information about how to write different ones: https://docs.angularjs.org/api/ng/service/$http#interceptors
With the interceptors in place, you now just need to listen from the broadcasted events in your run method or any controller.
app.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('unauthenticated', function(response) {
// Redirect to login page
$location.path('/login');
});
$rootScope.$on('serverError', function(response) {
// Show alert or something to give feedback to user that
// server error was encountered and they need to retry
// or just display this somewhere on the page
$rootScope.serverError = response.data.errorMessage;
});
}]);
In your view:
<body ng-app="studentModule">
<div id="server_error" ng-if="!!serverError">{{serverError}}</div>
...rest of your page
</body>
Like almost all AngularJS code, most of this can be abstracted into different factories and services but this should be a good place to start.
Here's my case scenario:
User is not logged in, and they try to access a /settings page.
My Settings controller recognizes based on $auth.isAuthenticated() != true that they aren't logged in, and redirects them to /login
User fills out their email and password and hits submit.
What I would like to do on this third step is then redirect them to the /settings page, not the home page.
I'm thinking I would be changing this variable:
$authProvider.loginRedirect = '/';
The problem is that I cannot include $authProvider in my loginCtrl.js file without getting an "unknown provider" error in my console: https://docs.angularjs.org/error/$injector/unpr?p0= In other words, Angular does not recognize $authProvider when I try to include it. Here's what my loginCtrl.js file looks like:
/* Everything is blowing up because I'm trying to include $authProvider */
angular.module("PrayerChain")
.controller("loginCtrl", ["$rootScope", "$scope", "$state", "$http", "$auth", "$authProvider", loginCtrl]);
function loginCtrl($rootScope, $scope, $state, $http, $auth, $authProvider) {
$authProvider.loginRedirect = '/settings';
$scope.login = function () {
$auth.login({ Email: $scope.email, Password: $scope.password })
.then(function() {
})
.catch(function (response, status, headers) {
console.log(response);
$scope.error = JSON.parse(response.data);
});
};
}
Is including $authProvider in a controller even possible? If not, what's an alternative solution to changing where people are redirected upon logging in using Satellizer?
Thanks.
Usually provider objects can only be accessed at config time, whereas controllers are created in runtime. If you need to setup the authProvider, try doing:
angular.module('PrayerChain').config(
[ "$authProvider",
function($authProvider) {
$authProvider.loginRedirect = '/settings';
}
]).controller("loginCtrl",
// ...
The new version (0.12.5) are not using this settings anymore. You need to set the url inside your controller
$auth.login({ Email: $scope.email, Password: $scope.password })
.then(function() {
$location.path('your-new-route');
})
.catch(function (response, status, headers) {
console.log(response);
$scope.error = JSON.parse(response.data);
});
I was looking to do this and found that in version 0.13.0 (maybe earlier too?) you can pass an options parameter to login function like this:
$auth
.login(user, {
url: config.api + '/authenticate/customer'
})
I built a simple app with user authentication base on this: link
Basically, I have a userAccountService, responsible for communicating with server and login controller handling the login process.
From other controller I want to check if user is already logged in (to hide LogIn button, and show user profile instead).
So I have a navController
function navCtrl ($scope, $modal, userAccountService) {
$scope.IsUserLoggedIn = function () {
return userAccountService.isUserLoggedIn;
}
}
So in HTML I use this ng-hide="isUserLoggedIn()
my userAccountService:
app.factory('userAccountService', ['$http', '$q', userAccountService]);
function userAccountService($http, $q) {
var service = {
registerUser: registerUser,
loginUser: loginUser,
logOut: logOut,
getValues: getValues,
isUserLoggedIn: false,
accessToken: ""
};
// code ommited
function loginUser(userData) {
var tokenUrl = serverBaseUrl + "/Token";
if (!userData.grant_type) {
userData.grant_type = "password";
}
var deferred = $q.defer();
$http({
method: 'POST',
url: tokenUrl,
data: userData,
})
.success(function (data,status,headers,cfg) {
// save the access_token as this is required for each API call.
accessToken = data.access_token;
isUserLoggedIn = true;
// check the log screen to know currently back from the server when a user log in successfully.
console.log(data);
deferred.resolve(data);
})
.error(function (err, status) {
console.log(err);
deferred.reject(status);
});
return deferred.promise;
}
}
What am I doing wrong? Here's another interesting read I took inspiration from: link
You can't return a variable, but you can return a function, so create a function that returns that variable.
Try something like this, it returns your service object (you might want to put a $watch on it):
Service
function userAccountService($http, $q) {
function getData() {
return service;
}
...
}
Controller
$scope.IsUserLoggedIn = userAccountService.getData().isUserLoggedIn;
Also, you're not correctly updating the state variable from your success callback - you're creating global variables instead of using the service object properties. So, for example:
isUserLoggedIn = true;
should be:
service.isUserLoggedIn = true;
How do I test a request being sent and data is being received with a JSONP service?
angular.module('search', [])
.factory('SearchService', function($q,$rootScope,$resource) {
var _search = {};
_search.user = function(opts){
return $resource('https://api.github.com/users/:user', {user: opts.user}, {
search: {method:'JSONP',params:{callback: 'JSON_CALLBACK'}}
});
}
return _search;
});
Following the GET request examples:
describe('search tests', function () {
var svc, httpBackend;
beforeEach(function (){
module('ngResource');
module('search');
inject(function($httpBackend, SearchService) {
svc = SearchService;
httpBackend = $httpBackend;
});
});
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should send the message and return the response', function (){
var returnData = { testing: 'anything'};
httpBackend.expectJSONP('https://api.github.com/users/gigablox?callback=JSON_CALLBACK').respond(returnData);
svc.user({user:'gigablox'}, function(user) {
expect(user.testing).toEqual('anything');
});
httpBackend.flush();
});
});
I can't seem to get by some errors:
Error: No pending request to flush !
Error: Unsatisfied requests: JSONP
Using AngularJS 1.2 and Karma 0.10.2
Edit
Got it working here: http://jsfiddle.net/DhUqT/
Try to call your search like the following:
svc.user({user:'gigablox'}).search(function(user) {
expect(user.testing).toEqual('anything');
});
The reason is the call to svc.user() returns a resource class object with a search() method. The search() method uses jsonp to make request. Without calling the search() method, $httpBackend will not see any request, so you see the errors mentioned in your question.