In this service method I want to test for the presence of the skipAuthorization setting.
var register = function (registration) {
logout();
return $http({
url: apiUrl + 'api/account/register',
method: 'POST',
data: registration,
skipAuthorization: true
});
};
Using $httpBackend, I can inspect the data and the headers, but I can't find any documentation related to the optional config object.
Thanks!
UPDATE:
I'm not thrilled with the solution below, but it does test the scenario in a round-about way.
it('should flag the interceptor to skip authorization', function () {
spyOn(my_store, 'get').and.returnValue('foo');
service.register();
http.expectPOST(url, undefined, function (headers) {
//If we don't skip authorization, this header would be "Bearer foo"
return headers['Authorization'] === undefined;
});
http.flush();
});
This works because if I don't set skipAuthorization to true, I will see a bearer token on the request headers. It relies on the fact that my interceptor pulls the token out of local storage, which I have mocked with a Jasmine spy (my_store).
Related
I have a method that dynamically sets responseType attribute of the $http request based on the asset type that's being requested. I'd like to unit-test that the correct response type is being set using Jasmine.
From what I found, you can expect a request with certain headers, but responseType is not a header, it's a part of the request config. Here are the samples of my code (in TypeScript).
let config = {
headers: {
'Content-Type': contentType
}
}
if (contentType.startsWith('image')) {
config.responseType = 'arraybuffer';
}
$http.get(url, config);
Ok, this is a bit late, and I've spent quite a while on this, but I've finally gotten this to work.
Assuming you're reconstructing the $httpBackend in a beforeEach hook (mine's assigning it to a variable called 'backend'... in the App config (or perhaps a more global beforeEach hook, haven't tried it that way), you'll need to add a decorator function to the $httpBackend service:
app.decorator('$httpBackend', ['$delegate', function ($delegate) {
$delegate.interceptedCalls = [];
var $httpBackend = function (method, url, data, callback, headers, timeout, withCredentials, responseType,
eventHandlers, uploadEventHandlers) {
$delegate.interceptedCalls.push({
method: method,
url: url,
timestamp: new Date(),
type : responseType
});
return $delegate.apply(null, arguments);
};
angular.extend($httpBackend, $delegate);
return $httpBackend;
}]);
All that does is add an interceptedCalls property to your backend object which will contain a list of all the requests that go through it.
Then, in your test file, you can do something like this:
it("forwards the data correctly",function(){
var headers = {
'Content-Type' : 'application/json',
'Accept': 'application/json, text/plain, */*'
};
backend.expectPOST('/someRoute/',{"A" : "B"},headers)
.respond(200,"dsfkjlasdfasdfklsdfd");
service.functionThatCallsSomeRoute({"A" : "B"});
backend.flush();
expect(backend.interceptedCalls[0]['type']).to.equal('arraybuffer');
});
May not be the best way to do it, but since I'm essentially refreshing the whole thing (backend and service being tested) before every test, it will have all the calls in order on the object.
Long story short. I am really not an AngularJS guru. Our site upgraded from 1.3 to 1.5. This one thing is breaking.
We used to inject an HTTP header via transformRequest in a factory named 'api':
.factory('api', function($resource) {
function add_auth_header(data, headersGetter) {
var headers = headersGetter();
headers['Authorization'] = ('Basic ' + btoa(data.username +
':' + data.password));
}
// defining the endpoints.
return {
auth: $resource('/api/v1/auth/', {}, {
login: {method: 'POST', transformRequest: add_auth_header},
logout: {method: 'DELETE'},
}),
Later on in the same file, this is called like so:
.service('auth', function($cookies, $rootScope, api) {
this.user = null;
this.login = function(credentials) {
var log = api.auth.login(credentials);
log.$promise.then(function(data){
// on good username and password
this.user = data;
});
As you can see, it calls api.auth.login with the credentials. I have verified that the transform request is being called, the headers are being fetched properly by headersGetter(), and that hanging the headers[] object no longer changes it like it used to in 1.3. Fiddler verifies that the request no longer has an Authorization header in it like it did in 1.3, and the Django server that gets the request also agrees.
I've read in a few places that the transformRequest functionality 'broke' in 1.4, but those posts have always been in the context of making an $http request, not providing an api service through a factory, and haven't made much sense to an AngularJS newb like me. I have no idea where I would start changing how Authorization is injected.
Can anyone point me the right way?
If anyone else is still facing this, the change was under breaking changes in the changelog for 1.4.
I feel the fix speaks for itself. Note that the function add_auth_header is not invoked but rather is passed.
.factory('api', function($resource) {
function add_auth_header(data) {
// as per HTTP authentication spec [1], credentials must be
// encoded in base64. Lets use window.btoa [2]
return 'Basic ' + btoa(data.data.username + ':' + data.data.password);
}
// defining the endpoints.
return {
auth: $resource('/api/v1/auth/', {}, {
login: {method: 'POST', headers: { 'Authorization': add_auth_header }},
logout: {method: 'DELETE'},
}),
I want to create a new REST method for my login system, where I supply user credentials in a custom header. How can I do it in with AngularJS/ng-resource? I've tried the below code but the problem is when i try to get the username/password from the forms($scope.vm.username). This gives me 'undefined' if I do it it in the headers setup.
angular.module('BkSystemApp.controllers', ['ngResource'])
.controller('LoginController', function($scope, $location, $resource){
//init
$scope.vm = {};
function getUserCredentials(){
return ($scope.vm.username + ':' + $scope.vm.password)
}
//resources
var Authentication = $resource('/users/authentication',{},{
login:{
method: 'GET',
isArray: false,
headers: {'Authentication':btoa(getUserCredentials())}
}
});
//HTTP methods
$scope.login = function(){
Authentication.login(function(data){
});
};
})
To have the $resource .login method compute the header string at method invocation time, the header configuation needs to have a header property with a value which is a function declaration, not a function invocation.
//resources
var Authentication = $resource('/users/authentication',{},{
login:{
method: 'GET',
isArray: false,
//USE function declaration
headers: {
'Authentication':
function () {
return btoa(getUserCredentials())
}
}
//NOT function invocation
//headers: {'Authentication':btoa(getUserCredentials())}
}
});
This way the Authentication value will be computed each time the login method gets invoked.
NOTE: It is not wise to send username and password in the header of an XHR GET request.
I am using http-auth-interceptor for authentication. In http-auth-interceptor, I use the following way to login:
var data = 'username=' + encodeURIComponent(user.userId) + '&password=' + encodeURIComponent(user.password);
$http.post('api/authenticate', data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
ignoreAuthModule: 'ignoreAuthModule'
})
ignoreAuthModule is used to tell ignoreAuthModule that this login method will be ignored by the auth interceptor.
Now, I have some request with $resource, like:
.factory('SomeDataService', function ($resource) {
return $resource('api/some/data', {}, {
'get': { method: 'GET'}
});
})
I want SomeDataService.get() is also ignored by the auth interceptors, because I need to control the 401 error by myself.
So, my question is, is there any way for ngResource that I can set config like that in $http.
[update based on comment]
I have listened the login-required event:
$rootScope.$on('event:auth-loginRequired', function (rejection) {
$log.log(rejection);
// I need to get the request url and for some specific url, need to do something.
$rootScope.loginPopup();
});
But the 'rejection' parameter has no context data of request I need. I need to get the request url and check, for some specified url, I need to do something.
After checking the document of ngResource, I got the solution as below:
.factory('SomeDataService', function ($resource) {
return $resource('api/some/data', {}, {
'get': { method: 'GET', ignoreAuthModule: 'ignoreAuthModule'}
});
})
Just add the config item as above. It will be equivalent ad:
$http.post('api/some/data', data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
ignoreAuthModule: 'ignoreAuthModule'
})
ngResource module is build on top of $http.Hence it is not possible to configure all the stuffs you can do with $http in $resource.I think the below link will be guide you to have a clear understanding on $http and $resource
I have this use case where I pass authToken to every request and this token changes everytime the person logins.
app.factory('Comment', function ($resource, localStorageService, $cacheFactory) {
return $resource('http://localhost:port/comments/:id', {"id":"#id", port:':9000'}, {
query: { method:'GET', isArray: true , headers: {'X-AUTH-TOKEN':'authToken='+localStorageService.get("authToken")}},
save: { method:'POST',headers: {'X-AUTH-TOKEN':'authToken='+localStorageService.get("authToken")}},
update: {method:'PUT' ,headers: {'X-AUTH-TOKEN':'authToken='+localStorageService.get("authToken")}},
delete : {method: 'DELETE',headers: {'X-AUTH-TOKEN':'authToken='+localStorageService.get("authToken")}},
get : { method: 'GET', headers: {'X-AUTH-TOKEN':'authToken='+localStorageService.get("authToken")}}
});
The behaviour I am seeing is that if the authToken changes for some reason the $resource keeps adding the previous authToken while sending the request. I am using the $http directly for login and for any commenting related stuff I am using $resource. Am I missing something?
After login I make sure that my localStorage has the newly created token but the request are using the previous authToken till I refresh the page after which it adds the correct header I know that the $resource uses some kind of caching and tried to remove the $http cache like this after loggin in.
$cacheFactory.get('$http').removeAll();
but didnt't help
It's because token is assigned once when factory code executes. Try this instead:
get : { method: 'GET', headers: {
'X-AUTH-TOKEN': function(){
return 'authToken=' + localStorageService.get("authToken");
}
}}