diff options
| -rw-r--r-- | angularFiles.js | 1 | ||||
| -rw-r--r-- | src/Resource.js | 163 | ||||
| -rw-r--r-- | src/service/resource.js | 164 | ||||
| -rw-r--r-- | test/ResourceSpec.js | 325 | ||||
| -rw-r--r-- | test/service/resourceSpec.js | 322 | 
5 files changed, 484 insertions, 491 deletions
| diff --git a/angularFiles.js b/angularFiles.js index 09b21977..52eae521 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 64c8c159..00000000 --- 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 790d5ed6..3aa48e74 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 a6ce4bc7..00000000 --- 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 429d26d2..e0049761 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(); +    }); +  }); +}); | 
