diff --git a/lib/restapi.js b/lib/restapi.js index fb424ae..0165521 100644 --- a/lib/restapi.js +++ b/lib/restapi.js @@ -267,4 +267,58 @@ RestApi.prototype.query = function(options, callback) { return deferred.promise.nodeify(callback); }; +function collectionPost(options, operation, callback) { + var deferred = Q.defer(); + this.request.post(_.merge({ + url: refUtils.getRelative(options.ref) + '/' + options.collection + '/' + operation, + json: {CollectionItems: options.data} + }, options.requestOptions, + optionsToRequestOptions(options)), function(error, result) { + if (error) { + deferred.reject(error); + } else { + deferred.resolve(result); + } + }); + return deferred.promise.nodeify(callback); +} + +/** + Adds items to a collection + @param {object} options - The add options (required) + - @member {string} ref - The ref of the collection to update, e.g. /user/12345 (required) + - @member {string} collection - The name of the collection to update, e.g. 'TeamMemberships (required) + - @member {object} data - [{_ref: objectRef}, {Name:"Joe"}], things to be added to the collection (required) + - @member {string/string[]} fetch - the fields to include on the returned records + - @member {object} scope - the default scoping to use. if not specified server default will be used. + - @member {ref} scope.workspace - the workspace + - @member {object} requestOptions - Additional options to be applied to the request: https://github.com/mikeal/request (optional) + @param {function} callback - A callback to be called when the operation completes + - @param {string[]} errors - Any errors which occurred + - @param {object} result - the operation result + @return {promise} + */ +RestApi.prototype.add = function(options, callback) { + return collectionPost.call(this, options, 'add', callback); +}; + +/** + Remove items from a collection + @param {object} options - The remove options (required) + - @member {string} ref - The ref of the collection to update, e.g. /user/12345 (required) + - @member {string} collection - The name of the collection to update, e.g. 'TeamMemberships (required) + - @member {object} data - [{_ref: objectRef}], where the objectRefs are to be removed from the collection (required) + - @member {string/string[]} fetch - the fields to include on the returned records + - @member {object} scope - the default scoping to use. if not specified server default will be used. + - @member {ref} scope.workspace - the workspace + - @member {object} requestOptions - Additional options to be applied to the request: https://github.com/mikeal/request (optional) + @param {function} callback - A callback to be called when the operation completes + - @param {string[]} errors - Any errors which occurred + - @param {object} result - the operation result + @return {promise} + */ +RestApi.prototype.remove = function(options, callback) { + return collectionPost.call(this, options, 'remove', callback); +}; + module.exports = RestApi; diff --git a/spec/restapi.spec.js b/spec/restapi.spec.js index 58b9e00..ba5283f 100644 --- a/spec/restapi.spec.js +++ b/spec/restapi.spec.js @@ -573,5 +573,205 @@ describe('RestApi', function() { }).done(); }); }); + + describe('add', function() { + it('translates request options', function() { + var restApi = new RestApi(); + restApi.add({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates', + scope: {workspace: '/workspace/1234'}, + fetch: ['FormattedID'], + requestOptions: { + qs: {foo: 'bar'} + } + }); + + this.post.callCount.should.eql(1); + var args = this.post.firstCall.args[0]; + args.qs.workspace.should.eql('/workspace/1234'); + args.qs.fetch.should.eql('FormattedID'); + args.qs.foo.should.eql('bar'); + }); + + it('generates correct post request', function() { + var restApi = new RestApi(); + var callback = sinon.stub(); + restApi.add({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }, callback); + + this.post.callCount.should.eql(1); + var args = this.post.firstCall.args; + args[0].url.should.eql('/defect/1234/Duplicates/add'); + args[0].json.should.eql({CollectionItems: [{_ref: '/defect/2345'}]}); + }); + + it('calls back with result', function(done) { + this.post.yieldsAsync(null, {Errors: [], Warnings: [], Results: [{_ref: '/defect/2345'}]}); + var restApi = new RestApi(); + restApi.add({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }, function(error, result) { + should.not.exist(error); + result.Errors.should.eql([]); + result.Warnings.should.eql([]); + result.Results.should.eql([{_ref: '/defect/2345'}]); + done(); + }); + }); + + it('resolves promise with result', function(done) { + this.post.yieldsAsync(null, {Errors: [], Warnings: [], Results: [{_ref: '/defect/2345'}]}); + var restApi = new RestApi(); + var onError = sinon.stub(); + restApi.add({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }).then(function(result) { + onError.callCount.should.eql(0); + result.Errors.should.eql([]); + result.Warnings.should.eql([]); + result.Results.should.eql([{_ref: '/defect/2345'}]); + done(); + }, onError).done(); + }); + + it('calls back with error', function(done) { + var error = 'Error!'; + this.post.yieldsAsync([error], null); + var restApi = new RestApi(); + restApi.add({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }, function(err, result) { + err.should.eql([error]); + should.not.exist(result); + done(); + }); + }); + + it('rejects promise with error', function(done) { + var error = 'Error!'; + this.post.yieldsAsync([error], null); + var restApi = new RestApi(); + var onSuccess = sinon.stub(); + restApi.add({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }).then(onSuccess, function(err) { + onSuccess.callCount.should.eql(0); + err.should.eql([error]); + done(); + }).done(); + }); + }); + + describe('remove', function() { + it('translates request options', function() { + var restApi = new RestApi(); + restApi.remove({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates', + scope: {workspace: '/workspace/1234'}, + fetch: ['FormattedID'], + requestOptions: { + qs: {foo: 'bar'} + } + }); + + this.post.callCount.should.eql(1); + var args = this.post.firstCall.args[0]; + args.qs.workspace.should.eql('/workspace/1234'); + args.qs.fetch.should.eql('FormattedID'); + args.qs.foo.should.eql('bar'); + }); + + it('generates correct post request', function() { + var restApi = new RestApi(); + var callback = sinon.stub(); + restApi.remove({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }, callback); + + this.post.callCount.should.eql(1); + var args = this.post.firstCall.args; + args[0].url.should.eql('/defect/1234/Duplicates/remove'); + args[0].json.should.eql({CollectionItems: [{_ref: '/defect/2345'}]}); + }); + + it('calls back with result', function(done) { + this.post.yieldsAsync(null, {Errors: [], Warnings: []}); + var restApi = new RestApi(); + restApi.remove({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }, function(error, result) { + should.not.exist(error); + result.Errors.should.eql([]); + result.Warnings.should.eql([]); + done(); + }); + }); + + it('resolves promise with result', function(done) { + this.post.yieldsAsync(null, {Errors: [], Warnings: []}); + var restApi = new RestApi(); + var onError = sinon.stub(); + restApi.remove({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }).then(function(result) { + onError.callCount.should.eql(0); + result.Errors.should.eql([]); + result.Warnings.should.eql([]); + done(); + }, onError).done(); + }); + + it('calls back with error', function(done) { + var error = 'Error!'; + this.post.yieldsAsync([error], null); + var restApi = new RestApi(); + restApi.remove({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }, function(err, result) { + err.should.eql([error]); + should.not.exist(result); + done(); + }); + }); + + it('rejects promise with error', function(done) { + var error = 'Error!'; + this.post.yieldsAsync([error], null); + var restApi = new RestApi(); + var onSuccess = sinon.stub(); + restApi.remove({ + ref: '/defect/1234', + data: [{_ref: '/defect/2345'}], + collection: 'Duplicates' + }).then(onSuccess, function(err) { + onSuccess.callCount.should.eql(0); + err.should.eql([error]); + done(); + }).done(); + }); + }); }); });