From 6a8749e65a7efb69a65be87605cd7d4a2df2fbb0 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Tue, 28 Feb 2012 17:33:54 -0800 Subject: [PATCH] refactor($resource): unify and simplify the code --- angularFiles.js | 1 - src/Resource.js | 163 ------------------ src/service/resource.js | 164 +++++++++++++++++- test/ResourceSpec.js | 325 ----------------------------------- test/service/resourceSpec.js | 322 ++++++++++++++++++++++++++++++++++ 5 files changed, 484 insertions(+), 491 deletions(-) delete mode 100644 src/Resource.js delete mode 100644 test/ResourceSpec.js diff --git a/angularFiles.js b/angularFiles.js index 09b2197794a6..52eae5215866 100644 --- a/angularFiles.js +++ b/angularFiles.js @@ -5,7 +5,6 @@ angularFiles = { 'src/AngularPublic.js', 'src/JSON.js', 'src/Injector.js', - 'src/Resource.js', 'src/jqLite.js', 'src/apis.js', 'src/service/anchorScroll.js', diff --git a/src/Resource.js b/src/Resource.js deleted file mode 100644 index 64c8c159a4b4..000000000000 --- a/src/Resource.js +++ /dev/null @@ -1,163 +0,0 @@ -'use strict'; - -function Route(template, defaults) { - this.template = template = template + '#'; - this.defaults = defaults || {}; - var urlParams = this.urlParams = {}; - forEach(template.split(/\W/), function(param){ - if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) { - urlParams[param] = true; - } - }); - this.template = template.replace(/\\:/g, ':'); -} - -Route.prototype = { - url: function(params) { - var self = this, - url = this.template, - encodedVal; - - params = params || {}; - forEach(this.urlParams, function(_, urlParam){ - encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || ""); - url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1"); - }); - url = url.replace(/\/?#$/, ''); - var query = []; - forEachSorted(params, function(value, key){ - if (!self.urlParams[key]) { - query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); - } - }); - url = url.replace(/\/*$/, ''); - return url + (query.length ? '?' + query.join('&') : ''); - } -}; - -function ResourceFactory($http) { - this.$http = $http; -} - -ResourceFactory.DEFAULT_ACTIONS = { - 'get': {method:'GET'}, - 'save': {method:'POST'}, - 'query': {method:'GET', isArray:true}, - 'remove': {method:'DELETE'}, - 'delete': {method:'DELETE'} -}; - -ResourceFactory.prototype = { - route: function(url, paramDefaults, actions){ - var self = this; - var route = new Route(url); - actions = extend({}, ResourceFactory.DEFAULT_ACTIONS, actions); - function extractParams(data){ - var ids = {}; - forEach(paramDefaults || {}, function(value, key){ - ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; - }); - return ids; - } - - function Resource(value){ - copy(value || {}, this); - } - - forEach(actions, function(action, name){ - var isPostOrPut = action.method == 'POST' || action.method == 'PUT'; - Resource[name] = function(a1, a2, a3, a4) { - var params = {}; - var data; - var success = noop; - var error = null; - switch(arguments.length) { - case 4: - error = a4; - success = a3; - //fallthrough - case 3: - case 2: - if (isFunction(a2)) { - if (isFunction(a1)) { - success = a1; - error = a2; - break; - } - - success = a2; - error = a3; - //fallthrough - } else { - params = a1; - data = a2; - success = a3; - break; - } - case 1: - if (isFunction(a1)) success = a1; - else if (isPostOrPut) data = a1; - else params = a1; - break; - case 0: break; - default: - throw "Expected between 0-4 arguments [params, data, success, error], got " + - arguments.length + " arguments."; - } - - var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); - self.$http({ - method: action.method, - url: route.url(extend({}, extractParams(data), action.params || {}, params)), - data: data - }).then(function(response) { - var data = response.data; - - if (data) { - if (action.isArray) { - value.length = 0; - forEach(data, function(item) { - value.push(new Resource(item)); - }); - } else { - copy(data, value); - } - } - (success||noop)(value, response.headers); - }, error); - - return value; - }; - - Resource.bind = function(additionalParamDefaults){ - return self.route(url, extend({}, paramDefaults, additionalParamDefaults), actions); - }; - - Resource.prototype['$' + name] = function(a1, a2, a3) { - var params = extractParams(this), - success = noop, - error; - - switch(arguments.length) { - case 3: params = a1; success = a2; error = a3; break; - case 2: - case 1: - if (isFunction(a1)) { - success = a1; - error = a2; - } else { - params = a1; - success = a2 || noop; - } - case 0: break; - default: - throw "Expected between 1-3 arguments [params, success, error], got " + - arguments.length + " arguments."; - } - var data = isPostOrPut ? this : undefined; - Resource[name].call(this, params, data, success, error); - }; - }); - return Resource; - } -}; diff --git a/src/service/resource.js b/src/service/resource.js index 790d5ed6407e..3aa48e743702 100644 --- a/src/service/resource.js +++ b/src/service/resource.js @@ -202,7 +202,167 @@ */ function $ResourceProvider() { this.$get = ['$http', function($http) { - var resource = new ResourceFactory($http); - return bind(resource, resource.route); + var DEFAULT_ACTIONS = { + 'get': {method:'GET'}, + 'save': {method:'POST'}, + 'query': {method:'GET', isArray:true}, + 'remove': {method:'DELETE'}, + 'delete': {method:'DELETE'} + }; + + + function Route(template, defaults) { + this.template = template = template + '#'; + this.defaults = defaults || {}; + var urlParams = this.urlParams = {}; + forEach(template.split(/\W/), function(param){ + if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) { + urlParams[param] = true; + } + }); + this.template = template.replace(/\\:/g, ':'); + } + + Route.prototype = { + url: function(params) { + var self = this, + url = this.template, + encodedVal; + + params = params || {}; + forEach(this.urlParams, function(_, urlParam){ + encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || ""); + url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1"); + }); + url = url.replace(/\/?#$/, ''); + var query = []; + forEachSorted(params, function(value, key){ + if (!self.urlParams[key]) { + query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); + } + }); + url = url.replace(/\/*$/, ''); + return url + (query.length ? '?' + query.join('&') : ''); + } + }; + + + function ResourceFactory(url, paramDefaults, actions) { + var route = new Route(url); + + actions = extend({}, DEFAULT_ACTIONS, actions); + + function extractParams(data){ + var ids = {}; + forEach(paramDefaults || {}, function(value, key){ + ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; + }); + return ids; + } + + function Resource(value){ + copy(value || {}, this); + } + + forEach(actions, function(action, name) { + var isPostOrPut = action.method == 'POST' || action.method == 'PUT'; + Resource[name] = function(a1, a2, a3, a4) { + var params = {}; + var data; + var success = noop; + var error = null; + switch(arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (isPostOrPut) data = a1; + else params = a1; + break; + case 0: break; + default: + throw "Expected between 0-4 arguments [params, data, success, error], got " + + arguments.length + " arguments."; + } + + var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); + $http({ + method: action.method, + url: route.url(extend({}, extractParams(data), action.params || {}, params)), + data: data + }).then(function(response) { + var data = response.data; + + if (data) { + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + value.push(new Resource(item)); + }); + } else { + copy(data, value); + } + } + (success||noop)(value, response.headers); + }, error); + + return value; + }; + + + Resource.bind = function(additionalParamDefaults){ + return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + + Resource.prototype['$' + name] = function(a1, a2, a3) { + var params = extractParams(this), + success = noop, + error; + + switch(arguments.length) { + case 3: params = a1; success = a2; error = a3; break; + case 2: + case 1: + if (isFunction(a1)) { + success = a1; + error = a2; + } else { + params = a1; + success = a2 || noop; + } + case 0: break; + default: + throw "Expected between 1-3 arguments [params, success, error], got " + + arguments.length + " arguments."; + } + var data = isPostOrPut ? this : undefined; + Resource[name].call(this, params, data, success, error); + }; + }); + return Resource; + } + + return ResourceFactory; }]; } diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js deleted file mode 100644 index a6ce4bc7e615..000000000000 --- a/test/ResourceSpec.js +++ /dev/null @@ -1,325 +0,0 @@ -'use strict'; - -describe("resource", function() { - var resource, CreditCard, callback, $httpBackend; - - beforeEach(inject(function($injector, $http) { - $httpBackend = $injector.get('$httpBackend'); - resource = new ResourceFactory($http); - CreditCard = resource.route('/CreditCard/:id:verb', {id:'@id.key'}, { - charge:{ - method:'POST', - params:{verb:'!charge'} - } - }); - callback = jasmine.createSpy(); - })); - - - afterEach(function() { - $httpBackend.verifyNoOutstandingExpectation(); - }); - - - it("should build resource", function() { - expect(typeof CreditCard).toBe('function'); - expect(typeof CreditCard.get).toBe('function'); - expect(typeof CreditCard.save).toBe('function'); - expect(typeof CreditCard.remove).toBe('function'); - expect(typeof CreditCard['delete']).toBe('function'); - expect(typeof CreditCard.query).toBe('function'); - }); - - - it('should default to empty parameters', function() { - $httpBackend.expect('GET', 'URL').respond({}); - resource.route('URL').query(); - }); - - - it('should ignore slashes of undefinend parameters', function() { - var R = resource.route('/Path/:a/:b/:c'); - - $httpBackend.when('GET').respond('{}'); - $httpBackend.expect('GET', '/Path'); - $httpBackend.expect('GET', '/Path/1'); - $httpBackend.expect('GET', '/Path/2/3'); - $httpBackend.expect('GET', '/Path/4/5/6'); - - R.get({}); - R.get({a:1}); - R.get({a:2, b:3}); - R.get({a:4, b:5, c:6}); - }); - - - it('should support escaping collons in url template', function() { - var R = resource.route('http://localhost\\:8080/Path/:a/\\:stillPath/:b'); - - $httpBackend.expect('GET', 'http://localhost:8080/Path/foo/:stillPath/bar').respond(); - R.get({a: 'foo', b: 'bar'}); - }); - - - it('should correctly encode url params', function() { - var R = resource.route('/Path/:a'); - - $httpBackend.expect('GET', '/Path/foo%231').respond('{}'); - $httpBackend.expect('GET', '/Path/doh!@foo?bar=baz%231').respond('{}'); - - R.get({a: 'foo#1'}); - R.get({a: 'doh!@foo', bar: 'baz#1'}); - }); - - - it('should not encode @ in url params', function() { - //encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt - //with regards to the character set (pchar) allowed in path segments - //so we need this test to make sure that we don't over-encode the params and break stuff like - //buzz api which uses @self - - var R = resource.route('/Path/:a'); - $httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}'); - R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'}); - }); - - - it('should encode & in url params', function() { - var R = resource.route('/Path/:a'); - $httpBackend.expect('GET', '/Path/doh&foo?bar=baz%261').respond('{}'); - R.get({a: 'doh&foo', bar: 'baz&1'}); - }); - - - it('should build resource with default param', function() { - $httpBackend.expect('GET', '/Order/123/Line/456.visa?minimum=0.05').respond({id: 'abc'}); - var LineItem = resource.route('/Order/:orderId/Line/:id:verb', - {orderId: '123', id: '@id.key', verb:'.visa', minimum: 0.05}); - var item = LineItem.get({id: 456}); - $httpBackend.flush(); - expect(item).toEqualData({id:'abc'}); - }); - - - it("should build resource with action default param overriding default param", function() { - $httpBackend.expect('GET', '/Customer/123').respond({id: 'abc'}); - var TypeItem = resource.route('/:type/:typeId', {type: 'Order'}, - {get: {method: 'GET', params: {type: 'Customer'}}}); - var item = TypeItem.get({typeId: 123}); - - $httpBackend.flush(); - expect(item).toEqualData({id: 'abc'}); - }); - - - it("should create resource", function() { - $httpBackend.expect('POST', '/CreditCard', '{"name":"misko"}').respond({id: 123, name: 'misko'}); - - var cc = CreditCard.save({name: 'misko'}, callback); - expect(cc).toEqualData({name: 'misko'}); - expect(callback).not.toHaveBeenCalled(); - - $httpBackend.flush(); - expect(cc).toEqualData({id: 123, name: 'misko'}); - expect(callback).toHaveBeenCalledOnce(); - expect(callback.mostRecentCall.args[0]).toEqual(cc); - expect(callback.mostRecentCall.args[1]()).toEqual({}); - }); - - - it("should read resource", function() { - $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); - var cc = CreditCard.get({id: 123}, callback); - - expect(cc instanceof CreditCard).toBeTruthy(); - expect(cc).toEqualData({}); - expect(callback).not.toHaveBeenCalled(); - - $httpBackend.flush(); - expect(cc).toEqualData({id: 123, number: '9876'}); - expect(callback.mostRecentCall.args[0]).toEqual(cc); - expect(callback.mostRecentCall.args[1]()).toEqual({}); - }); - - - it("should read partial resource", function() { - $httpBackend.expect('GET', '/CreditCard').respond([{id:{key:123}}]); - var ccs = CreditCard.query(); - - $httpBackend.flush(); - expect(ccs.length).toEqual(1); - - var cc = ccs[0]; - expect(cc instanceof CreditCard).toBe(true); - expect(cc.number).toBeUndefined(); - - $httpBackend.expect('GET', '/CreditCard/123').respond({id: {key: 123}, number: '9876'}); - cc.$get(callback); - $httpBackend.flush(); - expect(callback.mostRecentCall.args[0]).toEqual(cc); - expect(callback.mostRecentCall.args[1]()).toEqual({}); - expect(cc.number).toEqual('9876'); - }); - - - it("should update resource", function() { - $httpBackend.expect('POST', '/CreditCard/123', '{"id":{"key":123},"name":"misko"}'). - respond({id: {key: 123}, name: 'rama'}); - - var cc = CreditCard.save({id: {key: 123}, name: 'misko'}, callback); - expect(cc).toEqualData({id:{key:123}, name:'misko'}); - expect(callback).not.toHaveBeenCalled(); - $httpBackend.flush(); - }); - - - it("should query resource", function() { - $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); - - var ccs = CreditCard.query({key: 'value'}, callback); - expect(ccs).toEqual([]); - expect(callback).not.toHaveBeenCalled(); - - $httpBackend.flush(); - expect(ccs).toEqualData([{id:1}, {id:2}]); - expect(callback.mostRecentCall.args[0]).toEqual(ccs); - expect(callback.mostRecentCall.args[1]()).toEqual({}); - }); - - - it("should have all arguments optional", function() { - $httpBackend.expect('GET', '/CreditCard').respond([{id:1}]); - - var log = ''; - var ccs = CreditCard.query(function() { log += 'cb;'; }); - - $httpBackend.flush(); - expect(ccs).toEqualData([{id:1}]); - expect(log).toEqual('cb;'); - }); - - - it('should delete resource and call callback', function() { - $httpBackend.expect('DELETE', '/CreditCard/123').respond({}); - CreditCard.remove({id:123}, callback); - expect(callback).not.toHaveBeenCalled(); - - $httpBackend.flush(); - expect(callback.mostRecentCall.args[0]).toEqualData({}); - expect(callback.mostRecentCall.args[1]()).toEqual({}); - - callback.reset(); - $httpBackend.expect('DELETE', '/CreditCard/333').respond(204, null); - CreditCard.remove({id:333}, callback); - expect(callback).not.toHaveBeenCalled(); - - $httpBackend.flush(); - expect(callback.mostRecentCall.args[0]).toEqualData({}); - expect(callback.mostRecentCall.args[1]()).toEqual({}); - }); - - - it('should post charge verb', function() { - $httpBackend.expect('POST', '/CreditCard/123!charge?amount=10', '{"auth":"abc"}').respond({success: 'ok'}); - CreditCard.charge({id:123, amount:10}, {auth:'abc'}, callback); - }); - - - it('should post charge verb on instance', function() { - $httpBackend.expect('POST', '/CreditCard/123!charge?amount=10', - '{"id":{"key":123},"name":"misko"}').respond({success: 'ok'}); - - var card = new CreditCard({id:{key:123}, name:'misko'}); - card.$charge({amount:10}, callback); - }); - - - it('should create on save', function() { - $httpBackend.expect('POST', '/CreditCard', '{"name":"misko"}').respond({id: 123}, {header1: 'a'}); - - var cc = new CreditCard(); - expect(cc.$get).toBeDefined(); - expect(cc.$query).toBeDefined(); - expect(cc.$remove).toBeDefined(); - expect(cc.$save).toBeDefined(); - - cc.name = 'misko'; - cc.$save(callback); - expect(cc).toEqualData({name:'misko'}); - - $httpBackend.flush(); - expect(cc).toEqualData({id:123}); - expect(callback.mostRecentCall.args[0]).toEqual(cc); - expect(callback.mostRecentCall.args[1]()).toEqual({header1: 'a'}); - }); - - - it('should not mutate the resource object if response contains no body', function() { - var data = {id:{key:123}, number:'9876'}; - $httpBackend.expect('GET', '/CreditCard/123').respond(data); - - var cc = CreditCard.get({id:123}); - $httpBackend.flush(); - expect(cc instanceof CreditCard).toBe(true); - - $httpBackend.expect('POST', '/CreditCard/123', toJson(data)).respond(''); - var idBefore = cc.id; - - cc.$save(); - $httpBackend.flush(); - expect(idBefore).toEqual(cc.id); - }); - - - it('should bind default parameters', function() { - $httpBackend.expect('GET', '/CreditCard/123.visa?minimum=0.05').respond({id: 123}); - var Visa = CreditCard.bind({verb:'.visa', minimum:0.05}); - var visa = Visa.get({id:123}); - $httpBackend.flush(); - expect(visa).toEqualData({id:123}); - }); - - - it('should excersize full stack', inject(function($resource) { - var Person = $resource('/Person/:id'); - - $httpBackend.expect('GET', '/Person/123').respond('\n{\n"name":\n"misko"\n}\n'); - var person = Person.get({id:123}); - $httpBackend.flush(); - expect(person.name).toEqual('misko'); - })); - - - describe('failure mode', function() { - var ERROR_CODE = 500, - ERROR_RESPONSE = 'Server Error', - errorCB; - - beforeEach(function() { - errorCB = jasmine.createSpy('error').andCallFake(function(response) { - expect(response.data).toBe(ERROR_RESPONSE); - expect(response.status).toBe(ERROR_CODE); - }); - }); - - - it('should call the error callback if provided on non 2xx response', function() { - $httpBackend.expect('GET', '/CreditCard/123').respond(ERROR_CODE, ERROR_RESPONSE); - - CreditCard.get({id:123}, callback, errorCB); - $httpBackend.flush(); - expect(errorCB).toHaveBeenCalledOnce(); - expect(callback).not.toHaveBeenCalled(); - }); - - - it('should call the error callback if provided on non 2xx response', function() { - $httpBackend.expect('GET', '/CreditCard').respond(ERROR_CODE, ERROR_RESPONSE); - - CreditCard.get(callback, errorCB); - $httpBackend.flush(); - expect(errorCB).toHaveBeenCalledOnce(); - expect(callback).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/test/service/resourceSpec.js b/test/service/resourceSpec.js index 429d26d2e0fc..e00497618b86 100644 --- a/test/service/resourceSpec.js +++ b/test/service/resourceSpec.js @@ -1,3 +1,325 @@ 'use strict'; +describe("resource", function() { + var $resource, CreditCard, callback, $httpBackend; + beforeEach(inject(function($injector) { + $httpBackend = $injector.get('$httpBackend'); + $resource = $injector.get('$resource'); + CreditCard = $resource('/CreditCard/:id:verb', {id:'@id.key'}, { + charge:{ + method:'POST', + params:{verb:'!charge'} + } + }); + callback = jasmine.createSpy(); + })); + + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + }); + + + it("should build resource", function() { + expect(typeof CreditCard).toBe('function'); + expect(typeof CreditCard.get).toBe('function'); + expect(typeof CreditCard.save).toBe('function'); + expect(typeof CreditCard.remove).toBe('function'); + expect(typeof CreditCard['delete']).toBe('function'); + expect(typeof CreditCard.query).toBe('function'); + }); + + + it('should default to empty parameters', function() { + $httpBackend.expect('GET', 'URL').respond({}); + $resource('URL').query(); + }); + + + it('should ignore slashes of undefinend parameters', function() { + var R = $resource('/Path/:a/:b/:c'); + + $httpBackend.when('GET').respond('{}'); + $httpBackend.expect('GET', '/Path'); + $httpBackend.expect('GET', '/Path/1'); + $httpBackend.expect('GET', '/Path/2/3'); + $httpBackend.expect('GET', '/Path/4/5/6'); + + R.get({}); + R.get({a:1}); + R.get({a:2, b:3}); + R.get({a:4, b:5, c:6}); + }); + + + it('should support escaping colons in url template', function() { + var R = $resource('http://localhost\\:8080/Path/:a/\\:stillPath/:b'); + + $httpBackend.expect('GET', 'http://localhost:8080/Path/foo/:stillPath/bar').respond(); + R.get({a: 'foo', b: 'bar'}); + }); + + + it('should correctly encode url params', function() { + var R = $resource('/Path/:a'); + + $httpBackend.expect('GET', '/Path/foo%231').respond('{}'); + $httpBackend.expect('GET', '/Path/doh!@foo?bar=baz%231').respond('{}'); + + R.get({a: 'foo#1'}); + R.get({a: 'doh!@foo', bar: 'baz#1'}); + }); + + + it('should not encode @ in url params', function() { + //encodeURIComponent is too agressive and doesn't follow http://www.ietf.org/rfc/rfc3986.txt + //with regards to the character set (pchar) allowed in path segments + //so we need this test to make sure that we don't over-encode the params and break stuff like + //buzz api which uses @self + + var R = $resource('/Path/:a'); + $httpBackend.expect('GET', '/Path/doh@fo%20o?!do%26h=g%3Da+h&:bar=$baz@1').respond('{}'); + R.get({a: 'doh@fo o', ':bar': '$baz@1', '!do&h': 'g=a h'}); + }); + + + it('should encode & in url params', function() { + var R = $resource('/Path/:a'); + $httpBackend.expect('GET', '/Path/doh&foo?bar=baz%261').respond('{}'); + R.get({a: 'doh&foo', bar: 'baz&1'}); + }); + + + it('should build resource with default param', function() { + $httpBackend.expect('GET', '/Order/123/Line/456.visa?minimum=0.05').respond({id: 'abc'}); + var LineItem = $resource('/Order/:orderId/Line/:id:verb', + {orderId: '123', id: '@id.key', verb:'.visa', minimum: 0.05}); + var item = LineItem.get({id: 456}); + $httpBackend.flush(); + expect(item).toEqualData({id:'abc'}); + }); + + + it("should build resource with action default param overriding default param", function() { + $httpBackend.expect('GET', '/Customer/123').respond({id: 'abc'}); + var TypeItem = $resource('/:type/:typeId', {type: 'Order'}, + {get: {method: 'GET', params: {type: 'Customer'}}}); + var item = TypeItem.get({typeId: 123}); + + $httpBackend.flush(); + expect(item).toEqualData({id: 'abc'}); + }); + + + it("should create resource", function() { + $httpBackend.expect('POST', '/CreditCard', '{"name":"misko"}').respond({id: 123, name: 'misko'}); + + var cc = CreditCard.save({name: 'misko'}, callback); + expect(cc).toEqualData({name: 'misko'}); + expect(callback).not.toHaveBeenCalled(); + + $httpBackend.flush(); + expect(cc).toEqualData({id: 123, name: 'misko'}); + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toEqual(cc); + expect(callback.mostRecentCall.args[1]()).toEqual({}); + }); + + + it("should read resource", function() { + $httpBackend.expect('GET', '/CreditCard/123').respond({id: 123, number: '9876'}); + var cc = CreditCard.get({id: 123}, callback); + + expect(cc instanceof CreditCard).toBeTruthy(); + expect(cc).toEqualData({}); + expect(callback).not.toHaveBeenCalled(); + + $httpBackend.flush(); + expect(cc).toEqualData({id: 123, number: '9876'}); + expect(callback.mostRecentCall.args[0]).toEqual(cc); + expect(callback.mostRecentCall.args[1]()).toEqual({}); + }); + + + it("should read partial resource", function() { + $httpBackend.expect('GET', '/CreditCard').respond([{id:{key:123}}]); + var ccs = CreditCard.query(); + + $httpBackend.flush(); + expect(ccs.length).toEqual(1); + + var cc = ccs[0]; + expect(cc instanceof CreditCard).toBe(true); + expect(cc.number).toBeUndefined(); + + $httpBackend.expect('GET', '/CreditCard/123').respond({id: {key: 123}, number: '9876'}); + cc.$get(callback); + $httpBackend.flush(); + expect(callback.mostRecentCall.args[0]).toEqual(cc); + expect(callback.mostRecentCall.args[1]()).toEqual({}); + expect(cc.number).toEqual('9876'); + }); + + + it("should update resource", function() { + $httpBackend.expect('POST', '/CreditCard/123', '{"id":{"key":123},"name":"misko"}'). + respond({id: {key: 123}, name: 'rama'}); + + var cc = CreditCard.save({id: {key: 123}, name: 'misko'}, callback); + expect(cc).toEqualData({id:{key:123}, name:'misko'}); + expect(callback).not.toHaveBeenCalled(); + $httpBackend.flush(); + }); + + + it("should query resource", function() { + $httpBackend.expect('GET', '/CreditCard?key=value').respond([{id: 1}, {id: 2}]); + + var ccs = CreditCard.query({key: 'value'}, callback); + expect(ccs).toEqual([]); + expect(callback).not.toHaveBeenCalled(); + + $httpBackend.flush(); + expect(ccs).toEqualData([{id:1}, {id:2}]); + expect(callback.mostRecentCall.args[0]).toEqual(ccs); + expect(callback.mostRecentCall.args[1]()).toEqual({}); + }); + + + it("should have all arguments optional", function() { + $httpBackend.expect('GET', '/CreditCard').respond([{id:1}]); + + var log = ''; + var ccs = CreditCard.query(function() { log += 'cb;'; }); + + $httpBackend.flush(); + expect(ccs).toEqualData([{id:1}]); + expect(log).toEqual('cb;'); + }); + + + it('should delete resource and call callback', function() { + $httpBackend.expect('DELETE', '/CreditCard/123').respond({}); + CreditCard.remove({id:123}, callback); + expect(callback).not.toHaveBeenCalled(); + + $httpBackend.flush(); + expect(callback.mostRecentCall.args[0]).toEqualData({}); + expect(callback.mostRecentCall.args[1]()).toEqual({}); + + callback.reset(); + $httpBackend.expect('DELETE', '/CreditCard/333').respond(204, null); + CreditCard.remove({id:333}, callback); + expect(callback).not.toHaveBeenCalled(); + + $httpBackend.flush(); + expect(callback.mostRecentCall.args[0]).toEqualData({}); + expect(callback.mostRecentCall.args[1]()).toEqual({}); + }); + + + it('should post charge verb', function() { + $httpBackend.expect('POST', '/CreditCard/123!charge?amount=10', '{"auth":"abc"}').respond({success: 'ok'}); + CreditCard.charge({id:123, amount:10}, {auth:'abc'}, callback); + }); + + + it('should post charge verb on instance', function() { + $httpBackend.expect('POST', '/CreditCard/123!charge?amount=10', + '{"id":{"key":123},"name":"misko"}').respond({success: 'ok'}); + + var card = new CreditCard({id:{key:123}, name:'misko'}); + card.$charge({amount:10}, callback); + }); + + + it('should create on save', function() { + $httpBackend.expect('POST', '/CreditCard', '{"name":"misko"}').respond({id: 123}, {header1: 'a'}); + + var cc = new CreditCard(); + expect(cc.$get).toBeDefined(); + expect(cc.$query).toBeDefined(); + expect(cc.$remove).toBeDefined(); + expect(cc.$save).toBeDefined(); + + cc.name = 'misko'; + cc.$save(callback); + expect(cc).toEqualData({name:'misko'}); + + $httpBackend.flush(); + expect(cc).toEqualData({id:123}); + expect(callback.mostRecentCall.args[0]).toEqual(cc); + expect(callback.mostRecentCall.args[1]()).toEqual({header1: 'a'}); + }); + + + it('should not mutate the resource object if response contains no body', function() { + var data = {id:{key:123}, number:'9876'}; + $httpBackend.expect('GET', '/CreditCard/123').respond(data); + + var cc = CreditCard.get({id:123}); + $httpBackend.flush(); + expect(cc instanceof CreditCard).toBe(true); + + $httpBackend.expect('POST', '/CreditCard/123', toJson(data)).respond(''); + var idBefore = cc.id; + + cc.$save(); + $httpBackend.flush(); + expect(idBefore).toEqual(cc.id); + }); + + + it('should bind default parameters', function() { + $httpBackend.expect('GET', '/CreditCard/123.visa?minimum=0.05').respond({id: 123}); + var Visa = CreditCard.bind({verb:'.visa', minimum:0.05}); + var visa = Visa.get({id:123}); + $httpBackend.flush(); + expect(visa).toEqualData({id:123}); + }); + + + it('should exercise full stack', function() { + var Person = $resource('/Person/:id'); + + $httpBackend.expect('GET', '/Person/123').respond('\n{\n"name":\n"misko"\n}\n'); + var person = Person.get({id:123}); + $httpBackend.flush(); + expect(person.name).toEqual('misko'); + }); + + + describe('failure mode', function() { + var ERROR_CODE = 500, + ERROR_RESPONSE = 'Server Error', + errorCB; + + beforeEach(function() { + errorCB = jasmine.createSpy('error').andCallFake(function(response) { + expect(response.data).toBe(ERROR_RESPONSE); + expect(response.status).toBe(ERROR_CODE); + }); + }); + + + it('should call the error callback if provided on non 2xx response', function() { + $httpBackend.expect('GET', '/CreditCard/123').respond(ERROR_CODE, ERROR_RESPONSE); + + CreditCard.get({id:123}, callback, errorCB); + $httpBackend.flush(); + expect(errorCB).toHaveBeenCalledOnce(); + expect(callback).not.toHaveBeenCalled(); + }); + + + it('should call the error callback if provided on non 2xx response', function() { + $httpBackend.expect('GET', '/CreditCard').respond(ERROR_CODE, ERROR_RESPONSE); + + CreditCard.get(callback, errorCB); + $httpBackend.flush(); + expect(errorCB).toHaveBeenCalledOnce(); + expect(callback).not.toHaveBeenCalled(); + }); + }); +});