I have an odata, breeze, angular application.
Please see here: http://bepozreports.azurewebsites.net/#/dashboard
You will notice an alert that I have setup which just says the callback has failed.
If you navigate to app/js/controllers.js you will see this code
accountFactory.getAll()
.then(successCallback)
.catch(failCallback);
The failCallback always is called even though the call to http://bepozreports.azurewebsites.net/odata/Accounts?$orderby=FirstName results in a correct response??
Any ideas?
If you debug your app and look at line 15199 of breeze.debug.js, you will find the following code:
OData.read({
requestUri: url,
headers: { "DataServiceVersion": "2.0" }
},
function (data, response) {
var inlineCount;
if (data.__count) {
// OData can return data.__count as a string
inlineCount = parseInt(data.__count, 10);
}
return deferred.resolve({ results: data.results, inlineCount: inlineCount });
},
function (error) {
return deferred.reject(createError(error, url));
});
The error callback is triggered here with your url, and the error response is message: "no handler for data"
A failure at good error reporting by breeze.
After doing a little bit of digging (I'm not familiar with datajs), it looks like the problem might have something to do with your CORS setup (or lack thereof). Hope that helps.
Related
I'm trying to test that my program processes the error messages I get from http requests properly, but I can't figure out how to mock that with $httpBackend. Given the following:
$httpBackend.expectGET(path).respond(400, object);
400 sets response.status, and object sets response.data, but how do I set response.error? The data I need to test isn't in the data field.
Example API response:
{
status: 400,
data: {},
error: {}
}
the code ...
$httpBackend.expectGET(path).respond(400, object);
is saying when the code makes a request to the 'path' endpoint, respond with 400 status code with the response 'object'
so, for example, lets say that your unit test hits this code in your app ...
$http.GET(path).then(function(response) {
// this code is hit with 2xx responses
vm.fred = 'hello';
}).catch(function(errorResponse) {
// this code is hit with any status code not starting with 2! e.g. 400
vm.fred='failed';
});
Thus your code will cause the catch block to be executed. However, inside your unit test, to force the execution of the catch block you will need to do ...
$rootScope.$digest();
this will trigger the execution of the then or catch blocks (whichever is relevant).
you can then make expectations in your unit test as usual. Note that in your example errorResponse will be object.
respond method on $httpBackend chain doesn't offer the way to customize the response. It can be customized with $http interceptors. The most simple way to specify an error for each response is using a global:
var responseError;
beforeEach(module('app', ($httpProvider) => {
function responseErrorInterceptor($q) {
return {
responseError: (response) => {
response.error = responseError;
return $q.reject(response);
}
};
}
$httpProvider.interceptors.push(responseErrorInterceptor);
}));
...
responseError = { ... };
WORK AROUND IS AT THE BOTTOM
Original problem
There are question like this all over the web and none of them really have answer for me. I can't get an http PATCH operation to work using angular to save my life. I've implemented $http, with shortcut $http.patch and without using the config object method:PATCH. I've used $resource by adding a custom method. And I've implemented Restangular using their patch and I'm getting the same error. I have the correct Content-Type as suggested in other posts. I think it's safe to say at this point, it's something I'm missing. I'm getting the same "404" message via postman when trying to patch. I can PUT, GET, POST, and DELETE, but not PATCH.
In the following images you can see that the resource exists for GET. But when trying to patch I get 404. Browsing to that endpoint shows the record. Which is stored in Mongodb.
Here's some code snippets:
Resangular GET:
var corporiumRecord = Restangular.one('corporium-mgmnts', $scope.uuid);
corporiumRecord.get().then(function(res) {
console.log(res)
}, function(err) {
console.log('Restangular failed: ', err)
});
Restangular Patch:
var data = {
corporiumId: $scope.newBlock
};
var corporiumRecord = Restangular.one('corporium-mgmnts', $scope.uuid);
corporiumRecord.patch(data).then(function(res) {
console.log(res)
}, function(err) {
console.log('Restangular failed: ', err)
});
$http attempt using config object:
controller code:
httpCorporiumSrv.updateCorporiumId('/corporium-mgmnts/' + $scope.params.id, data)
.then(handleUpdateSuccess)
.catch(handleUpdateError);
service code, tried forcing the content-type header but got same result
with or without it:
function updateCorporiumId(url, data) {
return $http({
method: 'PATCH',
url: url,
data: angular.toJson(data),
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
//transformRequest: transformUpdateData
})
.then(handleUpdateSuccess)
.catch(handleUpdateErrors);
}
Using the .patch shortcut:
function updateCorporiumId(url, data) {
return $http.patch(url, data, {
transformRequest: transformUpdateData
})
.then(handleUpdateSuccess)
.catch(handleUpdateErrors);
}
Thing is I've tried this every which way I know how. I don't even know how to start debugging any more. I'm just getting 404 on a resource that does exist. Any suggestions on what might be happening to my request would be great.
Resolution:
For anyone having this issue, if you could post the fix or what's going on here to this point or PM me that would be awesome I'd like to know. I ended up just using PUT to fix this.
Quick Restangular solution:
Build the url template for findByOne like function using Restangular.one(url, _id) where '_id', is the id of the resource you're looking for. .get() goes out and finds that one resource by said id, which you can populate dynamically however you like. Once you have the one resource with GET copy it with Restangular.copy() which is different from angular.copy as it doesn't bind 'this' to the new object. Change what needs to be changed or added in the new object and then perform a .put() on it.
var corporiumRecord = Restangular.one('corporium-mgmnts', $scope.uuid);
corporiumRecord.get().then(function(res) {
var update = Restangular.copy(res);
// update date corporiumId
update.corporiumId = $scope.newBlock;
// submit new doc with altered value
update.put().then(function() {
console.log('updated')
});
console.log(update)
}, function(err) {
console.log('Restangular failed: ', err)
});
Also because mongo uses _id and Restangular uses id you have to add this to your module
angular.module('corporium-mgmnts').config(function(RestangularProvider) {
RestangularProvider.setMethodOverriders(['put', 'patch']);
// setRestangularFields is required for mongodb
RestangularProvider.setRestangularFields({
id: "_id"
});
});
Running my Angular app without a server running should return an error, or course. But this "ERR_CONNECTION_REFUSED" I see in my Chrome's console triggers my success function of my membership service:
function login(user, onSuccess, onError){
$http.post(_loginAddress, user)
.success(loginSuccess)
.success(onSuccess)
.error(loginError)
.error(onError);
}
function loginSuccess(data) {
// this code executes with data = null here.
if (data.ok === true) {
...
}
}
function loginError(data, code) {
...
}
The relevant section of the $http docs states:
A response status code between 200 and 299 is considered a success status and will result in the success callback being called. Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning that the error callback will not be called for such responses.
Am I suppose to program my $http success() functions for possible false-positive?
EDIT
So .. I went hunting and found that one of my interceptors (authentication) was causing this ERR_CONNECTION_REFUSED error response to be considered as 'recovered' and therefore the success function was called.
I changed this:
if (response.status !== 401) {
return response;
}
To this:
if (response.status !== 401) {
return $q.reject(response);
}
And everything is fine again.
Hope this might help someone else.
For hours I've been trying to test my NewPostController with $httpBackend. The problem is whenever I set non-2xx status code in the response, the test fails.
NewPostController has the following method:
$scope.submit = function () {
var newPost = $scope.newPost;
PostService.insertPost(newPost).then(function (post) {
$location.path("/my-posts");
}, function (status) {
$scope.form.$setPristine();
$scope.error = status;
});
};
I have a problem testing the failure path:
it(...) {
...
$scope.post.text = "";
$httpBackend.expectPOST("/create-post", {"post":$scope.post}).respond(400);
$scope.submit();
$httpBackend.flush();
expect($scope.error).toBeDefined();
$scope.post.text = "This is a valid text.";
$httpBackend.expectPOST("/create-post", {"post": $scope.post}).respond(200);
$scope.submit();
$httpBackend.flush();
expect($location.path()).toBe("/my-posts");
});
The test fails with a message "400 thrown" (no callstack). I tried to change the order of subtests, use whenPOST instead of expectPOST and combine the methods as they do in Angular docs (https://docs.angularjs.org/api/ngMock/service/$httpBackend) but without success.
Please help.
EDIT:
Now when I look at PostService, it makes sense where the "400 thrown" comes from but I expected the error to be handled by angular. I threw it because of the section "Handling problems in nested service calls" of this article. It is supposed to be a shorter version of deferred.resolve/reject mechanism.
this.insertPost = function (newPost) {
return $http({
method: "post",
url: "/create-post",
data: {
post: newPost
}
}).then(function (res) {
return (res.data);
}, function (res) {
throw res.status;
});
};
This is indeed strange, and is perhaps something the angular team didn't consider.
When a promise is rejected by throwing (as you're doing), the angular $exceptionHandler service is called with the thrown exception. By default, this service just logs the exception in the browser console.
But when using ngMocks, this service is replaced by a mock implementation that can either log or rethrow the exception. The default mode is to rethrow, in order to make a test fail when an exception is thrown.
My advice would be to avoid using throw to simply reject a promise, and thus replace
function (res) {
throw res.status;
}
by
function (res) {
return $q.reject(res.status);
}
But if you really want to keep using throw, you can also configure the mock exceptionHandler to log instead of rethrowing:
beforeEach(module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
}));
I've been writing a service in AngularJS to save some data and, if it fails, alert the user. However, after I create my resource and call $save:
myResource.$save(function(success) {
console.log(success);
}, function(error) {
console.log(error);
});
I expect the error callback's argument to be an object with data, status, headers, etc., but all I get is an object with a "then" function. I tried to mock it up in JSFiddle:
http://jsfiddle.net/RichardBender/KeS7r/1/
However, this example works as I originally expected. I yanked this JSFiddle example and put it in my project and it has the same problem I originally described, despite that as far as I can tell everything else is equal. Does anyone have any idea why this might be? My project was created with Yeoman/Bower/Grunt but I can't see why those things would make a difference here.
Thanks,
Richard
I solved the problem. The error was in my HTTP interceptor, where upon an error code, I was accidentally returning $q.reject(promise) rather than $q.reject(response).
The bugged version:
.factory('httpInterceptor', function($q) {
return function(promise) {
return promise.then(
// On success, just forward the response along.
function(response) {
return response;
},
function(response) {
// ... where I process the error
return $q.reject(promise);
}
);
};
The fixed version:
.factory('httpInterceptor', function($q) {
return function(promise) {
return promise.then(
// On success, just forward the response along.
function(response) {
return response;
},
function(response) {
// ... where I process the error
return $q.reject(response);
}
);
};
-Richard