From 7549d4fd3741b0de0c44eb21748a68c2b7fbce7c Mon Sep 17 00:00:00 2001 From: Matt Willer Date: Wed, 27 Mar 2019 12:31:30 -0700 Subject: [PATCH] Add common item methods for Web Links (#364) --- docs/collections.md | 24 ++ docs/web-links.md | 127 ++++++ lib/managers/web-links.js | 109 ++++- tests/endpoint-test.js | 143 +++++++ ...et_web_links_id_collections_empty_200.json | 6 + ...get_web_links_id_collections_full_200.json | 13 + .../web-links/post_web_links_id_copy_201.json | 52 +++ .../put_web_links_id_collections_200.json | 13 + .../put_web_links_id_parent_200.json | 52 +++ tests/lib/managers/web-links-test.js | 380 ++++++++++++++++++ 10 files changed, 918 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/endpoints/web-links/get_web_links_id_collections_empty_200.json create mode 100644 tests/fixtures/endpoints/web-links/get_web_links_id_collections_full_200.json create mode 100644 tests/fixtures/endpoints/web-links/post_web_links_id_copy_201.json create mode 100644 tests/fixtures/endpoints/web-links/put_web_links_id_collections_200.json create mode 100644 tests/fixtures/endpoints/web-links/put_web_links_id_parent_200.json diff --git a/docs/collections.md b/docs/collections.md index 0c058570..f631b63b 100644 --- a/docs/collections.md +++ b/docs/collections.md @@ -14,6 +14,8 @@ all be in the same folder. - [Remove File from a Collection](#remove-file-from-a-collection) - [Add Folder to a Collection](#add-folder-to-a-collection) - [Remove Folder from a Collection](#remove-folder-from-a-collection) +- [Add Web Link to a Collection](#add-web-link-to-a-collection) +- [Remove Web Link from a Collection](#remove-web-link-from-a-collection) @@ -109,3 +111,25 @@ method with the IDs of the folder and collection. ```js client.folders.removeFromCollection('87263', '235747', callback); ``` + +Add Web Link to a Collection +---------------------------- + +To add a web link to a collection, call the +[`weblinks.addToCollection(webLinkID, collectionID, callback)`](http://opensource.box.com/box-node-sdk/jsdoc/WebLinks.html#addToCollection) +method with the IDs of the web link and collection. + +```js +client.weblinks.addToCollection('87263', '235747', callback); +``` + +Remove Web Link from a Collection +--------------------------------- + +To remove a web link from a collection, call the +[`weblinks.removeFromCollection(webLinkID, collectionID, callback)`](http://opensource.box.com/box-node-sdk/jsdoc/WebLinks.html#removeFromCollection) +method with the IDs of the web link and collection. + +```js +client.weblinks.removeFromCollection('87263', '235747', callback); +``` diff --git a/docs/web-links.md b/docs/web-links.md index a1bafa53..9cb14938 100644 --- a/docs/web-links.md +++ b/docs/web-links.md @@ -13,6 +13,8 @@ and restore. - [Get a Web Link's information](#get-a-web-links-information) - [Update a Web Link](#update-a-web-link) - [Delete a Web Link](#delete-a-web-link) +- [Copy a Web Link](#copy-a-web-link) +- [Move a Web Link](#move-a-web-link) @@ -240,3 +242,128 @@ client.weblinks.delete('11111') // deletion succeeded — no value returned }); ``` + +Copy a Web Link +--------------- + +Call the +[`weblinks.copy(webLinkID, destinationFolderID, options, callback)`](http://opensource.box.com/box-node-sdk/jsdoc/WebLinks.html#copy) +method to copy a web link into another folder. + +```js +client.weblinks.copy('11111', '0') + .then(webLinkCopy => { + /* webLinkCopy -> { + type: 'web_link', + id: '11112', + sequence_id: '0', + etag: '0', + name: 'Renamed web link copy', + url: 'http://example.com', + created_by: + { type: 'user', + id: '22222', + name: 'Example User', + login: 'user@example.com' }, + created_at: '2019-03-26T12:49:06-07:00', + modified_at: '2019-03-26T12:49:06-07:00', + parent: + { type: 'folder', + id: '0', + sequence_id: null, + etag: null, + name: 'All Files' }, + description: '', + item_status: 'active', + trashed_at: null, + purged_at: null, + shared_link: null, + path_collection: + { total_count: 1, + entries: + [ { type: 'folder', + id: '0', + sequence_id: null, + etag: null, + name: 'All Files' } ] }, + modified_by: + { type: 'user', + id: '22222', + name: 'Example User', + login: 'user@example.com' }, + owned_by: + { type: 'user', + id: '22222', + name: 'Example User', + login: 'user@example.com' } } + */ + }); +``` + +An optional `name` parameter can also be passed to rename the folder on copy. This can be +used to avoid a name conflict when there is already an item with the same name in the +target folder. + +```js +client.weblinks.copy('12345', '0', {name: 'Renamed web link'}) + .then(webLinkCopy => { + // ... + }); +``` + +Move a Web Link +--------------- + +Call the [`weblinks.move(webLinkID, destinationFolderID, callback)`](http://opensource.box.com/box-node-sdk/jsdoc/WebLinks.html#move) method with the destination you want the folder moved to. + +```js +var webLinkID = '88888'; +var destinationFolderID = '0'; +client.weblinks.move(webLinkID, destinationFolderID) + .then(webLink => { + /* webLink -> { + type: 'web_link', + id: '88888', + sequence_id: '0', + etag: '0', + name: 'Example Web Link', + url: 'http://example.com', + created_by: + { type: 'user', + id: '22222', + name: 'Example User', + login: 'user@example.com' }, + created_at: '2019-03-26T12:49:06-07:00', + modified_at: '2019-03-26T12:49:06-07:00', + parent: + { type: 'folder', + id: '0', + sequence_id: null, + etag: null, + name: 'All Files' }, + description: '', + item_status: 'active', + trashed_at: null, + purged_at: null, + shared_link: null, + path_collection: + { total_count: 1, + entries: + [ { type: 'folder', + id: '0', + sequence_id: null, + etag: null, + name: 'All Files' } ] }, + modified_by: + { type: 'user', + id: '22222', + name: 'Example User', + login: 'user@example.com' }, + owned_by: + { type: 'user', + id: '22222', + name: 'Example User', + login: 'user@example.com' } } + */ + }); +``` diff --git a/lib/managers/web-links.js b/lib/managers/web-links.js index e75ba23d..5922d47f 100644 --- a/lib/managers/web-links.js +++ b/lib/managers/web-links.js @@ -89,7 +89,7 @@ WebLinks.prototype.get = function(weblinkID, options, callback) { * @param {Object} updates - Fields of the weblink to update * @param {string} [updates.name] - Name for the web link. Will default to the URL if empty. * @param {string} [updates.description] - Description of the web link. Will provide more context to users about the web link. - * @param {Function} [callback] - Passed the updated web-link information if it was acquired successfully, error otherwise + * @param {Function} [callback] - Passed the updated web link information if it was acquired successfully, error otherwise * @returns {Promise} A promise resolving to the updated web link object */ WebLinks.prototype.update = function(weblinkID, updates, callback) { @@ -117,4 +117,111 @@ WebLinks.prototype.delete = function(weblinkID, callback) { return this.client.wrapWithDefaultHandler(this.client.del)(apiPath, null, callback); }; +/** + * Move a web link into a new parent folder. + * + * API Endpoint: '/web_links/:webLinkID' + * Method: PUT + * + * @param {string} webLinkID - The Box ID of the web link being requested + * @param {string} newParentID - The Box ID for the new parent folder. '0' to move to All Files. + * @param {Function} [callback] - Passed the updated web link information if it was acquired successfully + * @returns {Promise} A promise resolving to the updated web link object + */ +WebLinks.prototype.move = function(webLinkID, newParentID, callback) { + var params = { + body: { + parent: { + id: newParentID + } + } + }; + var apiPath = urlPath(BASE_PATH, webLinkID); + return this.client.wrapWithDefaultHandler(this.client.put)(apiPath, params, callback); +}; + +/** + * Copy a web link into a new, different folder + * + * API Endpoint: '/web_links/:webLinkID/copy + * Method: POST + * + * @param {string} webLinkID - The Box ID of the web link being requested + * @param {string} newParentID - The Box ID for the new parent folder. '0' to copy to All Files. + * @param {Object} [options] - Optional parameters for the copy operation, can be left null in most cases + * @param {string} [options.name] - A new name to use if there is an identically-named item in the new parent folder + * @param {Function} [callback] - passed the new web link info if call was successful + * @returns {Promise} A promise resolving to the new web link object + */ +WebLinks.prototype.copy = function(webLinkID, newParentID, options, callback) { + + options = options || {}; + + options.parent = { + id: newParentID + }; + + var params = { + body: options + }; + var apiPath = urlPath(BASE_PATH, webLinkID, '/copy'); + return this.client.wrapWithDefaultHandler(this.client.post)(apiPath, params, callback); +}; + +/** + * Add a web link to a given collection + * + * API Endpoint: '/web_links/:webLinkID' + * Method: PUT + * + * @param {string} webLinkID - The web link to add to the collection + * @param {string} collectionID - The collection to add the web link to + * @param {Function} [callback] - Passed the updated web link if successful, error otherwise + * @returns {Promise} A promise resolving to the updated web link object + */ +WebLinks.prototype.addToCollection = function(webLinkID, collectionID, callback) { + + return this.get(webLinkID, {fields: 'collections'}) + .then(data => { + + var collections = data.collections || []; + + // Convert to correct format + collections = collections.map(c => ({id: c.id})); + + if (!collections.find(c => c.id === collectionID)) { + + collections.push({id: collectionID}); + } + + return this.update(webLinkID, {collections}); + }) + .asCallback(callback); +}; + +/** + * Remove a web link from a given collection + * + * API Endpoint: '/web_links/:webLinkID' + * Method: PUT + * + * @param {string} webLinkID - The web link to remove from the collection + * @param {string} collectionID - The collection to remove the web link from + * @param {Function} [callback] - Passed the updated web link if successful, error otherwise + * @returns {Promise} A promise resolving to the updated web link object + */ +WebLinks.prototype.removeFromCollection = function(webLinkID, collectionID, callback) { + + return this.get(webLinkID, {fields: 'collections'}) + .then(data => { + + var collections = data.collections || []; + // Convert to correct object format and remove the specified collection + collections = collections.map(c => ({id: c.id})).filter(c => c.id !== collectionID); + + return this.update(webLinkID, {collections}); + }) + .asCallback(callback); +}; + module.exports = WebLinks; diff --git a/tests/endpoint-test.js b/tests/endpoint-test.js index 0b7a1c7b..4a3c212b 100644 --- a/tests/endpoint-test.js +++ b/tests/endpoint-test.js @@ -4543,6 +4543,149 @@ describe('Endpoint', function() { .then(result => assert.isUndefined(result)); }); }); + + describe('copy()', function() { + it('should make POST call to create web link copy', function() { + + var webLinkID = '88888', + parentFolderID = '0', + name = 'New Bookmark (1)', + fixture = getFixture('web-links/post_web_links_id_copy_201'); + + var expectedBody = { + name, + parent: { + id: parentFolderID + } + }; + + apiMock.post(`/2.0/web_links/${webLinkID}/copy`, expectedBody) + .matchHeader('Authorization', function(authHeader) { + assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`); + return true; + }) + .matchHeader('User-Agent', function(uaHeader) { + assert.include(uaHeader, 'Box Node.js SDK v'); + return true; + }) + .reply(201, fixture); + + return basicClient.weblinks.copy(webLinkID, parentFolderID, { name }) + .then(webLink => assert.deepEqual(webLink, JSON.parse(fixture))); + }); + }); + + describe('addToCollection()', function() { + it('should make PUT call to update web link collections', function() { + + var webLinkID = '88888', + collectionID = '12345', + getCollectionsFixture = getFixture('web-links/get_web_links_id_collections_empty_200'), + putFixture = getFixture('web-links/put_web_links_id_collections_200'); + + var expectedBody = { + collections: [{ id: collectionID }] + }; + + apiMock.get(`/2.0/web_links/${webLinkID}`) + .query({ + fields: 'collections' + }) + .matchHeader('Authorization', function(authHeader) { + assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`); + return true; + }) + .matchHeader('User-Agent', function(uaHeader) { + assert.include(uaHeader, 'Box Node.js SDK v'); + return true; + }) + .reply(200, getCollectionsFixture) + .put(`/2.0/web_links/${webLinkID}`, expectedBody) + .matchHeader('Authorization', function(authHeader) { + assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`); + return true; + }) + .matchHeader('User-Agent', function(uaHeader) { + assert.include(uaHeader, 'Box Node.js SDK v'); + return true; + }) + .reply(200, putFixture); + + return basicClient.weblinks.addToCollection(webLinkID, collectionID) + .then(webLink => assert.deepEqual(webLink, JSON.parse(putFixture))); + }); + }); + + describe('removeFromCollection()', function() { + it('should make PUT call to update web link collections', function() { + + var webLinkID = '88888', + collectionID = '12345', + getCollectionsFixture = getFixture('web-links/get_web_links_id_collections_full_200'), + putFixture = getFixture('web-links/put_web_links_id_collections_200'); + + var expectedBody = { + collections: [] + }; + + apiMock.get(`/2.0/web_links/${webLinkID}`) + .query({ + fields: 'collections' + }) + .matchHeader('Authorization', function(authHeader) { + assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`); + return true; + }) + .matchHeader('User-Agent', function(uaHeader) { + assert.include(uaHeader, 'Box Node.js SDK v'); + return true; + }) + .reply(200, getCollectionsFixture) + .put(`/2.0/web_links/${webLinkID}`, expectedBody) + .matchHeader('Authorization', function(authHeader) { + assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`); + return true; + }) + .matchHeader('User-Agent', function(uaHeader) { + assert.include(uaHeader, 'Box Node.js SDK v'); + return true; + }) + .reply(200, putFixture); + + return basicClient.weblinks.removeFromCollection(webLinkID, collectionID) + .then(webLink => assert.deepEqual(webLink, JSON.parse(putFixture))); + }); + }); + + describe('move()', function() { + it('should make PUT call to change web link parent', function() { + + var webLinkID = '88888', + newParentID = '0', + fixture = getFixture('web-links/put_web_links_id_parent_200'); + + var expectedBody = { + parent: { + id: newParentID + } + }; + + apiMock.put(`/2.0/web_links/${webLinkID}`, expectedBody) + .matchHeader('Authorization', function(authHeader) { + assert.equal(authHeader, `Bearer ${TEST_ACCESS_TOKEN}`); + return true; + }) + .matchHeader('User-Agent', function(uaHeader) { + assert.include(uaHeader, 'Box Node.js SDK v'); + return true; + }) + .reply(200, fixture); + + return basicClient.weblinks.move(webLinkID, newParentID) + .then(webLink => assert.deepEqual(webLink, JSON.parse(fixture))); + }); + }); + }); describe('Storage Policies', function() { diff --git a/tests/fixtures/endpoints/web-links/get_web_links_id_collections_empty_200.json b/tests/fixtures/endpoints/web-links/get_web_links_id_collections_empty_200.json new file mode 100644 index 00000000..42706469 --- /dev/null +++ b/tests/fixtures/endpoints/web-links/get_web_links_id_collections_empty_200.json @@ -0,0 +1,6 @@ +{ + "type": "web_link", + "id": "88888", + "etag": "0", + "collections": [] +} \ No newline at end of file diff --git a/tests/fixtures/endpoints/web-links/get_web_links_id_collections_full_200.json b/tests/fixtures/endpoints/web-links/get_web_links_id_collections_full_200.json new file mode 100644 index 00000000..13778bf3 --- /dev/null +++ b/tests/fixtures/endpoints/web-links/get_web_links_id_collections_full_200.json @@ -0,0 +1,13 @@ +{ + "type": "web_link", + "id": "88888", + "etag": "0", + "collections": [ + { + "type": "collection", + "id": "12345", + "name": "Favorites", + "collection_type": "favorites" + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/endpoints/web-links/post_web_links_id_copy_201.json b/tests/fixtures/endpoints/web-links/post_web_links_id_copy_201.json new file mode 100644 index 00000000..e2d1d85f --- /dev/null +++ b/tests/fixtures/endpoints/web-links/post_web_links_id_copy_201.json @@ -0,0 +1,52 @@ +{ + "type": "web_link", + "id": "88888", + "sequence_id": "0", + "etag": "0", + "name": "Renamed web link copy", + "url": "http://example.com", + "created_by": { + "type": "user", + "id": "22222", + "name": "Example User", + "login": "user@example.com" + }, + "created_at": "2019-03-26T12:49:06-07:00", + "modified_at": "2019-03-26T12:49:06-07:00", + "parent": { + "type": "folder", + "id": "0", + "sequence_id": null, + "etag": null, + "name": "All Files" + }, + "description": "", + "item_status": "active", + "trashed_at": null, + "purged_at": null, + "shared_link": null, + "path_collection": { + "total_count": 1, + "entries": [ + { + "type": "folder", + "id": "0", + "sequence_id": null, + "etag": null, + "name": "All Files" + } + ] + }, + "modified_by": { + "type": "user", + "id": "22222", + "name": "Example User", + "login": "user@example.com" + }, + "owned_by": { + "type": "user", + "id": "22222", + "name": "Example User", + "login": "user@example.com" + } +} diff --git a/tests/fixtures/endpoints/web-links/put_web_links_id_collections_200.json b/tests/fixtures/endpoints/web-links/put_web_links_id_collections_200.json new file mode 100644 index 00000000..13778bf3 --- /dev/null +++ b/tests/fixtures/endpoints/web-links/put_web_links_id_collections_200.json @@ -0,0 +1,13 @@ +{ + "type": "web_link", + "id": "88888", + "etag": "0", + "collections": [ + { + "type": "collection", + "id": "12345", + "name": "Favorites", + "collection_type": "favorites" + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/endpoints/web-links/put_web_links_id_parent_200.json b/tests/fixtures/endpoints/web-links/put_web_links_id_parent_200.json new file mode 100644 index 00000000..e2d1d85f --- /dev/null +++ b/tests/fixtures/endpoints/web-links/put_web_links_id_parent_200.json @@ -0,0 +1,52 @@ +{ + "type": "web_link", + "id": "88888", + "sequence_id": "0", + "etag": "0", + "name": "Renamed web link copy", + "url": "http://example.com", + "created_by": { + "type": "user", + "id": "22222", + "name": "Example User", + "login": "user@example.com" + }, + "created_at": "2019-03-26T12:49:06-07:00", + "modified_at": "2019-03-26T12:49:06-07:00", + "parent": { + "type": "folder", + "id": "0", + "sequence_id": null, + "etag": null, + "name": "All Files" + }, + "description": "", + "item_status": "active", + "trashed_at": null, + "purged_at": null, + "shared_link": null, + "path_collection": { + "total_count": 1, + "entries": [ + { + "type": "folder", + "id": "0", + "sequence_id": null, + "etag": null, + "name": "All Files" + } + ] + }, + "modified_by": { + "type": "user", + "id": "22222", + "name": "Example User", + "login": "user@example.com" + }, + "owned_by": { + "type": "user", + "id": "22222", + "name": "Example User", + "login": "user@example.com" + } +} diff --git a/tests/lib/managers/web-links-test.js b/tests/lib/managers/web-links-test.js index 1277f462..6f0d7750 100644 --- a/tests/lib/managers/web-links-test.js +++ b/tests/lib/managers/web-links-test.js @@ -304,4 +304,384 @@ describe('WebLinks', function() { }); }); + describe('addToCollection()', function() { + + var COLLECTION_ID = '9873473596'; + + it('should get current collections and add new collection when item has no collections', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [] + }; + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, {collections: [{id: COLLECTION_ID}]}) + .returns(Promise.resolve(webLink)); + + weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID, done); + }); + + it('should get current collections and add new collection when item has other collections', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [{id: '111'}] + }; + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, {collections: [ + {id: '111'}, + {id: COLLECTION_ID} + ]}) + .returns(Promise.resolve(webLink)); + + weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID, done); + }); + + it('should get current collections and pass same collections when item is already in the collection', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: COLLECTION_ID}, + {id: '111'} + ] + }; + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, {collections: [ + {id: COLLECTION_ID}, + {id: '111'} + ]}) + .returns(Promise.resolve(webLink)); + + weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID, done); + }); + + it('should call callback with updated folder when API calls succeed', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: COLLECTION_ID}, + {id: '111'} + ] + }; + + sandbox.stub(weblinks, 'get').returns(Promise.resolve(webLink)); + sandbox.stub(weblinks, 'update').returns(Promise.resolve(webLink)); + + weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID, function(err, data) { + + assert.ifError(err); + assert.equal(data, webLink); + done(); + }); + }); + + it('should return promise resolving to the updated folder when API calls succeed', function() { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: COLLECTION_ID}, + {id: '111'} + ] + }; + + sandbox.stub(weblinks, 'get').returns(Promise.resolve(webLink)); + sandbox.stub(weblinks, 'update').returns(Promise.resolve(webLink)); + + return weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID) + .then(data => { + + assert.equal(data, webLink); + }); + }); + + it('should call callback with error when getting current collections fails', function(done) { + + var error = new Error('Failed get'); + + var weblinksMock = sandbox.mock(weblinks); + + // Using Promise.reject() causes an unhandled rejection error, so make the promise reject asynchronously + var p = Promise.delay(1).then(() => { + throw error; + }); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(p); + + weblinksMock.expects('update').never(); + + weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID, function(err) { + + assert.equal(err, error); + done(); + }); + }); + + it('should return promise that rejects when getting current collections fails', function() { + + var error = new Error('Failed get'); + + var weblinksMock = sandbox.mock(weblinks); + + // Using Promise.reject() causes an unhandled rejection error, so make the promise reject asynchronously + var p = Promise.delay(1).then(() => { + throw error; + }); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(p); + + weblinksMock.expects('update').never(); + + return weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID) + .catch(err => { + assert.equal(err, error); + }); + }); + + it('should call callback with error when adding the collection fails', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: COLLECTION_ID}, + {id: '111'} + ] + }; + + var expectedBody = { + collections: [ + { id: COLLECTION_ID }, + { id: '111' } + ] + }; + + var error = new Error('Failed update'); + + // Using Promise.reject() causes an unhandled rejection error, so make the promise reject asynchronously + var p = Promise.delay(1).then(() => { + throw error; + }); + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, expectedBody) + .returns(p); + + weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID, function(err) { + + assert.equal(err, error); + done(); + }); + }); + + it('should return promise that rejects when adding the collection fails', function() { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: COLLECTION_ID}, + {id: '111'} + ] + }; + + var expectedBody = { + collections: [ + { id: COLLECTION_ID }, + { id: '111' } + ] + }; + + var error = new Error('Failed update'); + // Using Promise.reject() causes an unhandled rejection error, so make the promise reject asynchronously + var p = Promise.delay(1).then(() => { + throw error; + }); + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, expectedBody) + .returns(p); + + return weblinks.addToCollection(WEB_LINK_ID, COLLECTION_ID) + .catch(err => { + assert.equal(err, error); + }); + }); + }); + + describe('removeFromCollection()', function() { + + var COLLECTION_ID = '98763'; + + it('should get current collections and pass empty array when item is not in any collections', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [] + }; + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, {collections: []}) + .returns(Promise.resolve(webLink)); + + weblinks.removeFromCollection(WEB_LINK_ID, COLLECTION_ID, done); + }); + + it('should get current collections and pass empty array when item is in the collection to be removed', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [{id: COLLECTION_ID}] + }; + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, {collections: []}) + .returns(Promise.resolve(webLink)); + + weblinks.removeFromCollection(WEB_LINK_ID, COLLECTION_ID, done); + }); + + it('should get current collections and pass filtered array when item is in multiple collections', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: COLLECTION_ID}, + {id: '111'} + ] + }; + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, {collections: [{id: '111'}]}) + .returns(Promise.resolve(webLink)); + + weblinks.removeFromCollection(WEB_LINK_ID, COLLECTION_ID, done); + }); + + it('should get current collections and pass same array when item is in only other collections', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: '111'}, + {id: '222'} + ] + }; + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, {collections: [ + {id: '111'}, + {id: '222'} + ]}) + .returns(Promise.resolve(webLink)); + + weblinks.removeFromCollection(WEB_LINK_ID, COLLECTION_ID, done); + }); + + it('should call callback with the updated folder when API calls succeed', function(done) { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: '111'}, + {id: '222'} + ] + }; + + sandbox.stub(weblinks, 'get').returns(Promise.resolve(webLink)); + sandbox.stub(weblinks, 'update').returns(Promise.resolve(webLink)); + + weblinks.removeFromCollection(WEB_LINK_ID, COLLECTION_ID, function(err, data) { + + assert.ifError(err); + assert.equal(data, webLink); + done(); + }); + }); + + it('should return promise resolving to the updated folder when API calls succeed', function() { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: '111'}, + {id: '222'} + ] + }; + + sandbox.stub(weblinks, 'get').returns(Promise.resolve(webLink)); + sandbox.stub(weblinks, 'update').returns(Promise.resolve(webLink)); + + return weblinks.removeFromCollection(WEB_LINK_ID, COLLECTION_ID) + .then(data => { + assert.equal(data, webLink); + }); + }); + + it('should call callback with error when getting current collections fails', function(done) { + + var error = new Error('Failed get'); + + var weblinksMock = sandbox.mock(weblinks); + + // Using Promise.reject() causes an unhandled rejection error, so make the promise reject asynchronously + var p = Promise.delay(1).then(() => { + throw error; + }); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(p); + weblinksMock.expects('update').never(); + + weblinks.removeFromCollection(WEB_LINK_ID, COLLECTION_ID, function(err) { + + assert.equal(err, error); + done(); + }); + }); + + it('should return promise that rejects when adding the collection fails', function() { + + var webLink = { + id: WEB_LINK_ID, + collections: [ + {id: COLLECTION_ID}, + {id: '111'} + ] + }; + + var error = new Error('Failed update'); + + var weblinksMock = sandbox.mock(weblinks); + weblinksMock.expects('get').withArgs(WEB_LINK_ID, {fields: 'collections'}) + .returns(Promise.resolve(webLink)); + weblinksMock.expects('update').withArgs(WEB_LINK_ID, {collections: [{id: '111'}]}) + .returns(Promise.resolve(error)); + + return weblinks.removeFromCollection(WEB_LINK_ID, COLLECTION_ID) + .catch(err => { + assert.equal(err, error); + }); + }); + }); + });