From b063ddb05b71988d92fe140ca003ad58301cfcc8 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 22 Jan 2016 17:30:18 +0100 Subject: [PATCH] Share dialog use OCS API --- core/js/share.js | 7 - core/js/sharedialogexpirationview.js | 10 +- core/js/sharedialoglinkshareview.js | 22 +- core/js/sharedialogshareelistview.js | 60 +-- core/js/shareitemmodel.js | 333 ++++++------- .../tests/specs/sharedialogshareelistview.js | 10 +- core/js/tests/specs/sharedialogviewSpec.js | 238 +++------ core/js/tests/specs/shareitemmodelSpec.js | 468 +++++++++++++----- 8 files changed, 611 insertions(+), 537 deletions(-) diff --git a/core/js/share.js b/core/js/share.js index e14e19a2543..9baa34d9bb7 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -387,13 +387,6 @@ OC.Share = _.extend(OC.Share || {}, { } }); }, - setPermissions:function(itemType, itemSource, shareType, shareWith, permissions) { - $.post(OC.filePath('core', 'ajax', 'share.php'), { action: 'setPermissions', itemType: itemType, itemSource: itemSource, shareType: shareType, shareWith: shareWith, permissions: permissions }, function(result) { - if (!result || result.status !== 'success') { - OC.dialogs.alert(t('core', 'Error while changing permissions'), t('core', 'Error')); - } - }); - }, showDropDown:function(itemType, itemSource, appendTo, link, possiblePermissions, filename) { var configModel = new OC.Share.ShareConfigModel(); var attributes = {itemType: itemType, itemSource: itemSource, possiblePermissions: possiblePermissions}; diff --git a/core/js/sharedialogexpirationview.js b/core/js/sharedialogexpirationview.js index eef440c9af5..fa5c0c00986 100644 --- a/core/js/sharedialogexpirationview.js +++ b/core/js/sharedialogexpirationview.js @@ -93,8 +93,9 @@ this.$el.find('.expirationDateContainer').toggleClass('hidden', !state); if (!state) { // discard expiration date - this.model.setExpirationDate(''); - this.model.saveLinkShare(); + this.model.saveLinkShare({ + expireDate: '' + }); } }, @@ -103,8 +104,9 @@ $target.tooltip('hide'); $target.removeClass('error'); - this.model.setExpirationDate($target.val()); - this.model.saveLinkShare(null, { + this.model.saveLinkShare({ + expiration: moment($target.val(), 'DD-MM-YYYY').format('YYYY-MM-DD') + }, { error: function(model, message) { if (!message) { $target.attr('title', t('core', 'Error setting expiration date')); diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index 452599fb7f2..efae618ba99 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -157,8 +157,9 @@ onShowPasswordClick: function() { this.$el.find('.linkPass').slideToggle(OC.menuSpeed); if(!this.$el.find('.showPasswordCheckbox').is(':checked')) { - this.model.setPassword(''); - this.model.saveLinkShare(); + this.model.saveLinkShare({ + password: '' + }); } else { this.$el.find('.linkPassText').focus(); } @@ -171,7 +172,6 @@ }, onPasswordEntered: function() { - var self = this; var $loading = this.$el.find('.linkPass .icon-loading-small'); if (!$loading.hasClass('hidden')) { // still in process @@ -189,8 +189,9 @@ .removeClass('hidden') .addClass('inlineblock'); - this.model.setPassword(password); - this.model.saveLinkShare({}, { + this.model.saveLinkShare({ + password: password + }, { error: function(model, msg) { $loading.removeClass('inlineblock').addClass('hidden'); $input.addClass('error'); @@ -204,8 +205,15 @@ onAllowPublicUploadChange: function() { var $checkbox = this.$('.publicUploadCheckbox'); $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); - this.model.setPublicUpload($checkbox.is(':checked')); - this.model.saveLinkShare(); + + var permissions = OC.PERMISSION_READ; + if($checkbox.is(':checked')) { + permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ; + } + + this.model.saveLinkShare({ + permissions: permissions + }); }, _onEmailPrivateLink: function(event) { diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js index 5728fbfd210..e26421dcaed 100644 --- a/core/js/sharedialogshareelistview.js +++ b/core/js/sharedialogshareelistview.js @@ -16,11 +16,7 @@ var TEMPLATE = '' ; @@ -81,12 +76,6 @@ /** @type {Function} **/ _template: undefined, - /** @type {boolean} **/ - showLink: true, - - /** @type {object} **/ - _collections: {}, - events: { 'click .unshare': 'onUnshare', 'click .permissions': 'onPermissionChange', @@ -107,23 +96,6 @@ }); }, - processCollectionShare: function(shareIndex) { - var type = this.model.getCollectionType(shareIndex); - var id = this.model.getCollectionPath(shareIndex); - if(type !== 'file' && type !== 'folder') { - id = this.model.getCollectionSource(shareIndex); - } - var displayName = this.model.getShareWithDisplayName(shareIndex); - if(!_.isUndefined(this._collections[id])) { - this._collections[id].text = this._collections[id].text + ", " + displayName; - } else { - this._collections[id] = {}; - this._collections[id].text = t('core', 'Shared in {item} with {user}', {'item': id, user: displayName}); - this._collections[id].id = id; - this._collections[id].isCollection = true; - } - }, - /** * * @param {OC.Share.Types.ShareInfo} shareInfo @@ -156,6 +128,7 @@ shareWith: shareWith, shareWithDisplayName: shareWithDisplayName, shareType: shareType, + shareId: this.model.get('shares')[shareIndex].id, modSeed: shareType !== OC.Share.SHARE_TYPE_USER, isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE }); @@ -187,8 +160,6 @@ deletePermission: OC.PERMISSION_DELETE }; - this._collections = {}; - if(!this.model.hasUserShares()) { return []; } @@ -196,15 +167,10 @@ var shares = this.model.get('shares'); var list = []; for(var index = 0; index < shares.length; index++) { - if(this.model.isCollection(index)) { - this.processCollectionShare(index); - } else { - // first empty {} is necessary, otherwise we get in trouble - // with references - list.push(_.extend({}, universal, this.getShareeObject(index))); - } + // first empty {} is necessary, otherwise we get in trouble + // with references + list.push(_.extend({}, universal, this.getShareeObject(index))); } - list = _.union(_.values(this._collections), list); return list; }, @@ -244,6 +210,7 @@ }, onUnshare: function(event) { + var self = this; var $element = $(event.target); if (!$element.is('a')) { $element = $element.closest('a'); @@ -257,17 +224,24 @@ $loading.removeClass('hidden'); var $li = $element.closest('li'); - var shareType = $li.data('share-type'); - var shareWith = $li.attr('data-share-with'); - this.model.removeShare(shareType, shareWith); + var shareId = $li.data('share-id'); + self.model.removeShare(shareId) + .done(function() { + $li.remove(); + }) + .fail(function() { + $loading.addClass('hidden'); + OC.Notification.showTemporary(t('core', 'Could not unshare')); + }); return false; }, onPermissionChange: function(event) { var $element = $(event.target); var $li = $element.closest('li'); + var shareId = $li.data('share-id'); var shareType = $li.data('share-type'); var shareWith = $li.attr('data-share-with'); @@ -289,7 +263,7 @@ permissions |= $(checkbox).data('permissions'); }); - this.model.setPermissions(shareType, shareWith, permissions); + this.model.updateShare(shareId, {permissions: permissions}); }, onCrudsToggle: function(event) { diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index ae3cb0ce2e3..c9fc85c91b2 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -25,13 +25,6 @@ * @property {number} stime share time */ - /** - * @typedef {object} OC.Share.Types.Collection - * @property {string} item_type - * @property {string} path - * @property {string} item_source TODO: verify - */ - /** * @typedef {object} OC.Share.Types.Reshare * @property {string} uid_owner @@ -51,7 +44,6 @@ * @property {string} share_with * @property {string} share_with_displayname * @property {string} mail_send - * @property {OC.Share.Types.Collection|undefined} collection * @property {Date} expiration optional? * @property {number} stime optional? */ @@ -84,6 +76,11 @@ * where the link share is one of them */ var ShareItemModel = OC.Backbone.Model.extend({ + /** + * @type share id of the link share, if applicable + */ + _linkShareId: null, + initialize: function(attributes, options) { if(!_.isUndefined(options.configModel)) { this.configModel = options.configModel; @@ -110,118 +107,48 @@ * TODO: this should be a separate model */ saveLinkShare: function(attributes, options) { - var model = this; - var itemType = this.get('itemType'); - var itemSource = this.get('itemSource'); - - // TODO: use backbone's default value mechanism once this is a separate model - var requiredAttributes = [ - { name: 'password', defaultValue: '' }, - { name: 'passwordChanged', defaultValue: false }, - { name: 'permissions', defaultValue: OC.PERMISSION_READ }, - { name: 'expiration', defaultValue: this.configModel.getDefaultExpirationDateString() } - ]; - - attributes = attributes || {}; - - // get attributes from the model and fill in with default values - _.each(requiredAttributes, function(attribute) { - // a provided options overrides a present value of the link - // share. If neither is given, the default value is used. - if(_.isUndefined(attribute[attribute.name])) { - attributes[attribute.name] = attribute.defaultValue; - var currentValue = model.get('linkShare')[attribute.name]; - if(!_.isUndefined(currentValue)) { - attributes[attribute.name] = currentValue; - } - } - }); + options = options || {}; + attributes = _.extend({}, attributes); - var password = { - password: attributes.password, - passwordChanged: attributes.passwordChanged - }; + var shareId = null; + var call; - OC.Share.share( - itemType, - itemSource, - OC.Share.SHARE_TYPE_LINK, - password, - attributes.permissions, - this.fileInfoModel.get('name'), - attributes.expiration, - function(result) { - if (!result || result.status !== 'success') { - model.fetch({ - success: function() { - if (options && _.isFunction(options.success)) { - options.success(model); - } - } - }); - } else { - if (options && _.isFunction(options.error)) { - options.error(model); - } - } - }, - function(result) { - var msg = t('core', 'Error'); - if (result.data && result.data.message) { - msg = result.data.message; - } + // oh yeah... + if (attributes.expiration) { + attributes.expireDate = attributes.expiration; + delete attributes.expiration; + } - if (options && _.isFunction(options.error)) { - options.error(model, msg); - } else { - OC.dialogs.alert(msg, t('core', 'Error while sharing')); - } - } - ); - }, + if (this.get('linkShare') && this.get('linkShare').isLinkShare) { + shareId = this.get('linkShare').id; - removeLinkShare: function() { - this.removeShare(OC.Share.SHARE_TYPE_LINK, ''); - }, + // note: update can only update a single value at a time + call = this.updateShare(shareId, attributes); + } else { + attributes = _.defaults(attributes, { + password: '', + passwordChanged: false, + permissions: OC.PERMISSION_READ, + expireDate: this.configModel.getDefaultExpirationDateString(), + shareType: OC.Share.SHARE_TYPE_LINK + }); - /** - * Sets the public upload flag - * - * @param {bool} allow whether public upload is allowed - */ - setPublicUpload: function(allow) { - var permissions = OC.PERMISSION_READ; - if(allow) { - permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ; + call = this.addShare(attributes); } - this.get('linkShare').permissions = permissions; + return call; }, - /** - * Sets the expiration date of the public link - * - * @param {string} expiration expiration date - */ - setExpirationDate: function(expiration) { - this.get('linkShare').expiration = expiration; - }, - - /** - * Set password of the public link share - * - * @param {string} password - */ - setPassword: function(password) { - this.get('linkShare').password = password; - this.get('linkShare').passwordChanged = true; + removeLinkShare: function() { + if (this.get('linkShare')) { + return this.removeShare(this.get('linkShare').id); + } }, addShare: function(attributes, options) { var shareType = attributes.shareType; - var shareWith = attributes.shareWith; - var fileName = this.fileInfoModel.get('name'); options = options || {}; + attributes = _.extend({}, attributes); // Default permissions are Edit (CRUD) and Share // Check if these permissions are possible @@ -243,29 +170,64 @@ } } - var model = this; - var itemType = this.get('itemType'); - var itemSource = this.get('itemSource'); - OC.Share.share(itemType, itemSource, shareType, shareWith, permissions, fileName, options.expiration, function() { - model.fetch(); - }); - }, + attributes.permissions = permissions; + if (_.isUndefined(attributes.path)) { + attributes.path = this.fileInfoModel.getFullPath(); + } - setPermissions: function(shareType, shareWith, permissions) { - var itemType = this.get('itemType'); - var itemSource = this.get('itemSource'); + var self = this; + return $.ajax({ + type: 'POST', + url: this._getUrl('shares'), + data: attributes, + dataType: 'json' + }).done(function() { + self.fetch({ + success: function() { + if (_.isFunction(options.success)) { + options.success(self); + } + } + }); + }).fail(function(result) { + var msg = t('core', 'Error'); + if (result.ocs && result.ocs.meta) { + msg = result.ocs.meta.message; + } - // TODO: in the future, only set the permissions on the model but don't save directly - OC.Share.setPermissions(itemType, itemSource, shareType, shareWith, permissions); + if (_.isFunction(options.error)) { + options.error(self, msg); + } else { + OC.dialogs.alert(msg, t('core', 'Error while sharing')); + } + }); }, - removeShare: function(shareType, shareWith) { - var model = this; - var itemType = this.get('itemType'); - var itemSource = this.get('itemSource'); + updateShare: function(shareId, attrs) { + var self = this; + return $.ajax({ + type: 'PUT', + url: this._getUrl('shares/' + encodeURIComponent(shareId)), + data: attrs, + dataType: 'json' + }).done(function() { + self.fetch(); + }); + }, - OC.Share.unshare(itemType, itemSource, shareType, shareWith, function() { - model.fetch(); + /** + * Deletes the share with the given id + * + * @param {int} shareId share id + * @return {jQuery} + */ + removeShare: function(shareId) { + var self = this; + return $.ajax({ + type: 'DELETE', + url: this._getUrl('shares/' + encodeURIComponent(shareId)), + }).done(function() { + self.fetch(); }); }, @@ -320,71 +282,6 @@ return false; }, - /** - * @param {number} shareIndex - * @returns {string} - */ - getCollectionType: function(shareIndex) { - /** @type OC.Share.Types.ShareInfo **/ - var share = this.get('shares')[shareIndex]; - if(!_.isObject(share)) { - throw "Unknown Share"; - } else if(_.isUndefined(share.collection)) { - throw "Share is not a collection"; - } - - return share.collection.item_type; - }, - - /** - * @param {number} shareIndex - * @returns {string} - */ - getCollectionPath: function(shareIndex) { - /** @type OC.Share.Types.ShareInfo **/ - var share = this.get('shares')[shareIndex]; - if(!_.isObject(share)) { - throw "Unknown Share"; - } else if(_.isUndefined(share.collection)) { - throw "Share is not a collection"; - } - - return share.collection.path; - }, - - /** - * @param {number} shareIndex - * @returns {string} - */ - getCollectionSource: function(shareIndex) { - /** @type OC.Share.Types.ShareInfo **/ - var share = this.get('shares')[shareIndex]; - if(!_.isObject(share)) { - throw "Unknown Share"; - } else if(_.isUndefined(share.collection)) { - throw "Share is not a collection"; - } - - return share.collection.item_source; - }, - - /** - * @param {number} shareIndex - * @returns {boolean} - */ - isCollection: function(shareIndex) { - /** @type OC.Share.Types.ShareInfo **/ - var share = this.get('shares')[shareIndex]; - if(!_.isObject(share)) { - throw "Unknown Share"; - } - if(_.isUndefined(share.collection)) { - return false; - } - return true; - }, - - /** * @returns {string} */ @@ -635,13 +532,64 @@ || this.hasDeletePermission(shareIndex); }, + _getUrl: function(base, params) { + params = _.extend({format: 'json'}, params || {}); + return OC.linkToOCS('apps/files_sharing/api/v1', 2) + base + '?' + OC.buildQueryString(params); + }, + + _fetchShares: function() { + var path = this.fileInfoModel.getFullPath(); + return $.ajax({ + type: 'GET', + url: this._getUrl('shares', {path: path, reshares: true}) + }); + }, + + _fetchReshare: function() { + // only fetch original share once + if (!this._reshareFetched) { + var path = this.fileInfoModel.getFullPath(); + this._reshareFetched = true; + return $.ajax({ + type: 'GET', + url: this._getUrl('shares', {path: path, shared_with_me: true}) + }); + } else { + return $.Deferred().resolve([{ + ocs: { + data: [this.get('reshare')] + } + }]); + } + }, + fetch: function() { var model = this; this.trigger('request', this); - OC.Share.loadItem(this.get('itemType'), this.get('itemSource'), function(data) { + + var deferred = $.when( + this._fetchShares(), + this._fetchReshare() + ); + deferred.done(function(data1, data2) { model.trigger('sync', 'GET', this); - model.set(model.parse(data)); + var sharesMap = {}; + _.each(data1[0].ocs.data, function(shareItem) { + sharesMap[shareItem.id] = shareItem; + }); + + var reshare = false; + if (data2[0].ocs.data.length) { + reshare = data2[0].ocs.data[0]; + } + + model.set(model.parse({ + shares: sharesMap, + reshare: reshare + })); }); + + return deferred; }, /** @@ -690,7 +638,7 @@ parse: function(data) { if(data === false) { console.warn('no data was returned'); - trigger('fetchError'); + this.trigger('fetchError'); return {}; } @@ -752,6 +700,7 @@ } linkShare = { isLinkShare: true, + id: share.id, token: share.token, password: share.share_with, link: link, diff --git a/core/js/tests/specs/sharedialogshareelistview.js b/core/js/tests/specs/sharedialogshareelistview.js index 44611d0d964..cef97469753 100644 --- a/core/js/tests/specs/sharedialogshareelistview.js +++ b/core/js/tests/specs/sharedialogshareelistview.js @@ -27,7 +27,7 @@ describe('OC.Share.ShareDialogShareeListView', function () { var configModel; var shareModel; var listView; - var setPermissionsStub; + var updateShareStub; beforeEach(function () { /* jshint camelcase:false */ @@ -81,7 +81,7 @@ describe('OC.Share.ShareDialogShareeListView', function () { oldCurrentUser = OC.currentUser; OC.currentUser = 'user0'; - setPermissionsStub = sinon.stub(listView.model, 'setPermissions'); + updateShareStub = sinon.stub(OC.Share.ShareItemModel.prototype, 'updateShare'); }); afterEach(function () { @@ -89,7 +89,7 @@ describe('OC.Share.ShareDialogShareeListView', function () { /* jshint camelcase:false */ oc_appconfig.core = oldAppConfig; listView.remove(); - setPermissionsStub.restore(); + updateShareStub.restore(); }); describe('Manages checkbox events correctly', function () { @@ -105,7 +105,7 @@ describe('OC.Share.ShareDialogShareeListView', function () { listView.render(); listView.$el.find("input[name='edit']").click(); expect(listView.$el.find("input[name='update']").is(':checked')).toEqual(true); - expect(setPermissionsStub.called).toEqual(true); + expect(updateShareStub.calledOnce).toEqual(true); }); it('Checks edit box when create/update/delete are checked', function () { @@ -120,7 +120,7 @@ describe('OC.Share.ShareDialogShareeListView', function () { listView.render(); listView.$el.find("input[name='update']").click(); expect(listView.$el.find("input[name='edit']").is(':checked')).toEqual(true); - expect(setPermissionsStub.called).toEqual(true); + expect(updateShareStub.calledOnce).toEqual(true); }); it('shows cruds checkboxes when toggled', function () { diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js index 63cfe5299ae..6899e625c45 100644 --- a/core/js/tests/specs/sharedialogviewSpec.js +++ b/core/js/tests/specs/sharedialogviewSpec.js @@ -28,8 +28,10 @@ describe('OC.Share.ShareDialogView', function() { var avatarStub; var placeholderStub; var oldCurrentUser; + var saveLinkShareStub; var fetchStub; + var notificationStub; var configModel; var shareModel; @@ -46,6 +48,7 @@ describe('OC.Share.ShareDialogView', function() { oc_appconfig.core.enforcePasswordForPublicLink = false; fetchStub = sinon.stub(OC.Share.ShareItemModel.prototype, 'fetch'); + saveLinkShareStub = sinon.stub(OC.Share.ShareItemModel.prototype, 'saveLinkShare'); fileInfoModel = new OCA.Files.FileInfoModel({ id: 123, @@ -116,6 +119,7 @@ describe('OC.Share.ShareDialogView', function() { dialog.remove(); fetchStub.restore(); + saveLinkShareStub.restore(); autocompleteStub.restore(); avatarStub.restore(); @@ -128,55 +132,32 @@ describe('OC.Share.ShareDialogView', function() { it('update password on focus out', function() { $('#allowShareWithLink').val('yes'); + dialog.model.set('linkShare', { + isLinkShare: true + }); dialog.render(); - // Toggle linkshare - dialog.$el.find('.linkCheckbox').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - // Enable password, enter password and focusout dialog.$el.find('[name=showPassword]').click(); dialog.$el.find('.linkPassText').focus(); dialog.$el.find('.linkPassText').val('foo'); dialog.$el.find('.linkPassText').focusout(); - expect(fakeServer.requests[1].method).toEqual('POST'); - var body = OC.parseQueryString(fakeServer.requests[1].requestBody); - expect(body['shareWith[password]']).toEqual('foo'); - expect(body['shareWith[passwordChanged]']).toEqual('true'); - - fetchStub.reset(); - - // Set password response - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - expect(fetchStub.calledOnce).toEqual(true); - // fetching the model will rerender the view - dialog.render(); - - expect(dialog.$el.find('.linkPassText').val()).toEqual(''); - expect(dialog.$el.find('.linkPassText').attr('placeholder')).toEqual('**********'); + expect(saveLinkShareStub.calledOnce).toEqual(true); + expect(saveLinkShareStub.firstCall.args[0]).toEqual({ + password: 'foo' + }); }); it('update password on enter', function() { $('#allowShareWithLink').val('yes'); + dialog.model.set('linkShare', { + isLinkShare: true + }); dialog.render(); // Toggle linkshare dialog.$el.find('.linkCheckbox').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); // Enable password and enter password dialog.$el.find('[name=showPassword]').click(); @@ -184,26 +165,10 @@ describe('OC.Share.ShareDialogView', function() { dialog.$el.find('.linkPassText').val('foo'); dialog.$el.find('.linkPassText').trigger(new $.Event('keyup', {keyCode: 13})); - expect(fakeServer.requests[1].method).toEqual('POST'); - var body = OC.parseQueryString(fakeServer.requests[1].requestBody); - expect(body['shareWith[password]']).toEqual('foo'); - expect(body['shareWith[passwordChanged]']).toEqual('true'); - - fetchStub.reset(); - - // Set password response - fakeServer.requests[1].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - - expect(fetchStub.calledOnce).toEqual(true); - // fetching the model will rerender the view - dialog.render(); - - expect(dialog.$el.find('.linkPassText').val()).toEqual(''); - expect(dialog.$el.find('.linkPassText').attr('placeholder')).toEqual('**********'); + expect(saveLinkShareStub.calledOnce).toEqual(true); + expect(saveLinkShareStub.firstCall.args[0]).toEqual({ + password: 'foo' + }); }); it('shows share with link checkbox when allowed', function() { $('#allowShareWithLink').val('yes'); @@ -241,16 +206,11 @@ describe('OC.Share.ShareDialogView', function() { it('autofocus link text when clicked', function() { $('#allowShareWithLink').val('yes'); + dialog.model.set('linkShare', { + isLinkShare: true + }); dialog.render(); - // Toggle linkshare - dialog.$el.find('.linkCheckbox').click(); - fakeServer.requests[0].respond( - 200, - { 'Content-Type': 'application/json' }, - JSON.stringify({data: {token: 'xyz'}, status: 'success'}) - ); - var focusStub = sinon.stub($.fn, 'focus'); var selectStub = sinon.stub($.fn, 'select'); dialog.$el.find('.linkText').click(); @@ -603,106 +563,6 @@ describe('OC.Share.ShareDialogView', function() { }); }); }); - describe('share permissions', function() { - beforeEach(function() { - oc_appconfig.core.resharingAllowed = true; - }); - - /** - * Tests sharing with the given possible permissions - * - * @param {int} possiblePermissions - * @return {int} permissions sent to the server - */ - function testWithPermissions(possiblePermissions) { - shareModel.set({ - permissions: possiblePermissions, - possiblePermissions: possiblePermissions - }); - dialog.render(); - var autocompleteOptions = autocompleteStub.getCall(0).args[0]; - // simulate autocomplete selection - autocompleteOptions.select(new $.Event('select'), { - item: { - label: 'User Two', - value: { - shareType: OC.Share.SHARE_TYPE_USER, - shareWith: 'user2' - } - } - }); - autocompleteStub.reset(); - var requestBody = OC.parseQueryString(_.last(fakeServer.requests).requestBody); - return parseInt(requestBody.permissions, 10); - } - - describe('regular sharing', function() { - it('shares with given permissions with default config', function() { - shareModel.set({ - reshare: {}, - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_SHARE); - }); - it('removes share permission when not allowed', function() { - configModel.set('isResharingAllowed', false); - shareModel.set({ - reshare: {}, - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE); - }); - it('automatically adds READ permission even when not specified', function() { - configModel.set('isResharingAllowed', false); - shareModel.set({ - reshare: {}, - shares: [] - }); - expect( - testWithPermissions(OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) - ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_UPDATE); - }); - it('does not show sharing options when sharing not allowed', function() { - shareModel.set({ - reshare: {}, - shares: [], - permissions: OC.PERMISSION_READ - }); - dialog.render(); - expect(dialog.$el.find('.shareWithField').prop('disabled')).toEqual(true); - }); - it('shows reshare owner', function() { - shareModel.set({ - reshare: { - uid_owner: 'user1' - }, - shares: [], - permissions: OC.PERMISSION_READ - }); - dialog.render(); - expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(1); - }); - it('does not show reshare owner if owner is current user', function() { - shareModel.set({ - reshare: { - uid_owner: OC.currentUser - }, - shares: [], - permissions: OC.PERMISSION_READ - }); - dialog.render(); - expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(0); - }); - }); - }); - describe('remote sharing', function() { it('shows remote share info when allowed', function() { configModel.set({ @@ -1093,5 +953,61 @@ describe('OC.Share.ShareDialogView', function() { expect(el.hasClass('user')).toEqual(true); }); }); + + it('calls addShare after selection', function() { + dialog.render(); + var addShareStub = sinon.stub(shareModel, 'addShare'); + var autocompleteOptions = autocompleteStub.getCall(0).args[0]; + autocompleteOptions.select(new $.Event('select'), { + item: { + label: 'User Two', + value: { + shareType: OC.Share.SHARE_TYPE_USER, + shareWith: 'user2' + } + } + }); + + expect(addShareStub.calledOnce).toEqual(true); + expect(addShareStub.firstCall.args[0]).toEqual({ + shareType: OC.Share.SHARE_TYPE_USER, + shareWith: 'user2' + }); + + addShareStub.restore(); + }); + }); + describe('reshare permissions', function() { + it('does not show sharing options when sharing not allowed', function() { + shareModel.set({ + reshare: {}, + shares: [], + permissions: OC.PERMISSION_READ + }); + dialog.render(); + expect(dialog.$el.find('.shareWithField').prop('disabled')).toEqual(true); + }); + it('shows reshare owner', function() { + shareModel.set({ + reshare: { + uid_owner: 'user1' + }, + shares: [], + permissions: OC.PERMISSION_READ + }); + dialog.render(); + expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(1); + }); + it('does not show reshare owner if owner is current user', function() { + shareModel.set({ + reshare: { + uid_owner: OC.currentUser + }, + shares: [], + permissions: OC.PERMISSION_READ + }); + dialog.render(); + expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(0); + }); }); }); diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index 28daf860393..d696b159e47 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -21,14 +21,20 @@ /* global oc_appconfig */ describe('OC.Share.ShareItemModel', function() { - var loadItemStub; + var fetchSharesStub, fetchReshareStub; + var fetchSharesDeferred, fetchReshareDeferred; var fileInfoModel, configModel, model; var oldCurrentUser; beforeEach(function() { oldCurrentUser = OC.currentUser; - loadItemStub = sinon.stub(OC.Share, 'loadItem'); + fetchSharesDeferred = new $.Deferred(); + fetchSharesStub = sinon.stub(OC.Share.ShareItemModel.prototype, '_fetchShares') + .returns(fetchSharesDeferred.promise()); + fetchReshareDeferred = new $.Deferred(); + fetchReshareStub = sinon.stub(OC.Share.ShareItemModel.prototype, '_fetchReshare') + .returns(fetchReshareDeferred.promise()); fileInfoModel = new OCA.Files.FileInfoModel({ id: 123, @@ -52,27 +58,70 @@ describe('OC.Share.ShareItemModel', function() { }); }); afterEach(function() { - loadItemStub.restore(); + if (fetchSharesStub) { + fetchSharesStub.restore(); + } + if (fetchReshareStub) { + fetchReshareStub.restore(); + } OC.currentUser = oldCurrentUser; }); + function makeOcsResponse(data) { + return [{ + ocs: { + data: data + } + }]; + } + describe('Fetching and parsing', function() { - it('fetching calls loadItem with the correct arguments', function() { + it('fetches both outgoing shares and the current incoming share', function() { model.fetch(); - expect(loadItemStub.calledOnce).toEqual(true); - expect(loadItemStub.calledWith('file', 123)).toEqual(true); + expect(fetchSharesStub.calledOnce).toEqual(true); + expect(fetchReshareStub.calledOnce).toEqual(true); + }); + it('fetches shares for the current path', function() { + fetchSharesStub.restore(); + + model._fetchShares(); + + expect(fakeServer.requests.length).toEqual(1); + expect(fakeServer.requests[0].method).toEqual('GET'); + expect(fakeServer.requests[0].url).toEqual( + OC.linkToOCS('apps/files_sharing/api/v1', 2) + + 'shares?format=json&path=%2Fsubdir%2Fshared_file_name.txt&reshares=true' + ); + + fetchSharesStub = null; + }); + it('fetches reshare for the current path', function() { + fetchReshareStub.restore(); + + model._fetchReshare(); + + expect(fakeServer.requests.length).toEqual(1); + expect(fakeServer.requests[0].method).toEqual('GET'); + expect(fakeServer.requests[0].url).toEqual( + OC.linkToOCS('apps/files_sharing/api/v1', 2) + + 'shares?format=json&path=%2Fsubdir%2Fshared_file_name.txt&shared_with_me=true' + ); + + fetchReshareStub = null; }); it('populates attributes with parsed response', function() { - loadItemStub.yields({ - /* jshint camelcase: false */ - reshare: { + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([ + { share_type: OC.Share.SHARE_TYPE_USER, uid_owner: 'owner', displayname_owner: 'Owner', permissions: 31 - }, - shares: [{ + } + ])); + fetchSharesDeferred.resolve(makeOcsResponse([ + { id: 100, item_source: 123, permissions: 31, @@ -112,8 +161,9 @@ describe('OC.Share.ShareItemModel', function() { storage: 1, token: 'tehtoken', uid_owner: 'root' - }] - }); + } + ])); + model.fetch(); var shares = model.get('shares'); @@ -130,10 +180,9 @@ describe('OC.Share.ShareItemModel', function() { // TODO: check more attributes }); it('does not parse link share when for a different file', function() { - loadItemStub.yields({ - reshare: [], - /* jshint camelcase: false */ - shares: [{ + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([{ displayname_owner: 'root', expiration: null, file_source: 456, @@ -152,7 +201,7 @@ describe('OC.Share.ShareItemModel', function() { token: 'tehtoken', uid_owner: 'root' }] - }); + )); model.fetch(); @@ -164,10 +213,9 @@ describe('OC.Share.ShareItemModel', function() { expect(linkShare.isLinkShare).toEqual(false); }); it('parses correct link share when a nested link share exists along with parent one', function() { - loadItemStub.yields({ - reshare: [], - /* jshint camelcase: false */ - shares: [{ + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([{ displayname_owner: 'root', expiration: '2015-10-12 00:00:00', file_source: 123, @@ -204,7 +252,7 @@ describe('OC.Share.ShareItemModel', function() { token: 'anothertoken', uid_owner: 'root' }] - }); + )); model.fetch(); @@ -219,26 +267,26 @@ describe('OC.Share.ShareItemModel', function() { // TODO: check child too }); it('reduces reshare permissions to the ones from the original share', function() { - loadItemStub.yields({ - reshare: { - permissions: OC.PERMISSION_READ, - uid_owner: 'user1' - }, - shares: [] - }); + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([{ + id: 123, + permissions: OC.PERMISSION_READ, + uid_owner: 'user1' + }])); + fetchSharesDeferred.resolve(makeOcsResponse([])); model.fetch(); // no resharing allowed expect(model.get('permissions')).toEqual(OC.PERMISSION_READ); }); it('reduces reshare permissions to possible permissions', function() { - loadItemStub.yields({ - reshare: { - permissions: OC.PERMISSION_ALL, - uid_owner: 'user1' - }, - shares: [] - }); + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([{ + id: 123, + permissions: OC.PERMISSION_ALL, + uid_owner: 'user1' + }])); + fetchSharesDeferred.resolve(makeOcsResponse([])); model.set('possiblePermissions', OC.PERMISSION_READ); model.fetch(); @@ -248,10 +296,8 @@ describe('OC.Share.ShareItemModel', function() { }); it('allows owner to share their own share when they are also the recipient', function() { OC.currentUser = 'user1'; - loadItemStub.yields({ - reshare: {}, - shares: [] - }); + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([])); model.fetch(); @@ -259,9 +305,9 @@ describe('OC.Share.ShareItemModel', function() { expect(model.get('permissions') & OC.PERMISSION_SHARE).toEqual(OC.PERMISSION_SHARE); }); it('properly parses integer values when the server is in the mood of returning ints as string', function() { - loadItemStub.yields({ - reshare: {}, - shares: [{ + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([{ displayname_owner: 'root', expiration: '2015-10-12 00:00:00', file_source: '123', @@ -280,7 +326,7 @@ describe('OC.Share.ShareItemModel', function() { token: 'tehtoken', uid_owner: 'root' }] - }); + )); model.fetch(); @@ -306,55 +352,50 @@ describe('OC.Share.ShareItemModel', function() { }); describe('hasUserShares', function() { it('returns false when no user shares exist', function() { - loadItemStub.yields({ - reshare: {}, - shares: [] - }); + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([])); model.fetch(); expect(model.hasUserShares()).toEqual(false); }); it('returns true when user shares exist on the current item', function() { - loadItemStub.yields({ - reshare: {}, - shares: [{ - id: 1, - share_type: OC.Share.SHARE_TYPE_USER, - share_with: 'user1', - item_source: '123' - }] - }); + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([{ + id: 1, + share_type: OC.Share.SHARE_TYPE_USER, + share_with: 'user1', + item_source: '123' + }])); model.fetch(); expect(model.hasUserShares()).toEqual(true); }); it('returns true when group shares exist on the current item', function() { - loadItemStub.yields({ - reshare: {}, - shares: [{ - id: 1, - share_type: OC.Share.SHARE_TYPE_GROUP, - share_with: 'group1', - item_source: '123' - }] - }); + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([{ + id: 1, + share_type: OC.Share.SHARE_TYPE_GROUP, + share_with: 'group1', + item_source: '123' + }])); model.fetch(); expect(model.hasUserShares()).toEqual(true); }); it('returns false when share exist on parent item', function() { - loadItemStub.yields({ - reshare: {}, - shares: [{ - id: 1, - share_type: OC.Share.SHARE_TYPE_GROUP, - share_with: 'group1', - item_source: '111' - }] - }); + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([{ + id: 1, + share_type: OC.Share.SHARE_TYPE_GROUP, + share_with: 'group1', + item_source: '111' + }])); model.fetch(); @@ -381,27 +422,28 @@ describe('OC.Share.ShareItemModel', function() { describe('sendEmailPrivateLink', function() { it('succeeds', function() { - loadItemStub.yields({ - shares: [{ - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }] - }); + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([{ + displayname_owner: 'root', + expiration: null, + file_source: 123, + file_target: '/folder', + id: 20, + item_source: '123', + item_type: 'folder', + mail_send: '0', + parent: null, + path: '/folder', + permissions: OC.PERMISSION_READ, + share_type: OC.Share.SHARE_TYPE_LINK, + share_with: null, + stime: 1403884258, + storage: 1, + token: 'tehtoken', + uid_owner: 'root' + }])); + model.fetch(); var res = model.sendEmailPrivateLink('foo@bar.com'); @@ -430,27 +472,28 @@ describe('OC.Share.ShareItemModel', function() { }); it('fails', function() { - loadItemStub.yields({ - shares: [{ - displayname_owner: 'root', - expiration: null, - file_source: 123, - file_target: '/folder', - id: 20, - item_source: '123', - item_type: 'folder', - mail_send: '0', - parent: null, - path: '/folder', - permissions: OC.PERMISSION_READ, - share_type: OC.Share.SHARE_TYPE_LINK, - share_with: null, - stime: 1403884258, - storage: 1, - token: 'tehtoken', - uid_owner: 'root' - }] - }); + /* jshint camelcase: false */ + fetchReshareDeferred.resolve(makeOcsResponse([])); + fetchSharesDeferred.resolve(makeOcsResponse([{ + displayname_owner: 'root', + expiration: null, + file_source: 123, + file_target: '/folder', + id: 20, + item_source: '123', + item_type: 'folder', + mail_send: '0', + parent: null, + path: '/folder', + permissions: OC.PERMISSION_READ, + share_type: OC.Share.SHARE_TYPE_LINK, + share_with: null, + stime: 1403884258, + storage: 1, + token: 'tehtoken', + uid_owner: 'root' + }])); + model.fetch(); var res = model.sendEmailPrivateLink('foo@bar.com'); @@ -478,5 +521,194 @@ describe('OC.Share.ShareItemModel', function() { expect(res.state()).toEqual('rejected'); }); }); + describe('share permissions', function() { + beforeEach(function() { + oc_appconfig.core.resharingAllowed = true; + }); + + /** + * Tests sharing with the given possible permissions + * + * @param {int} possiblePermissions + * @return {int} permissions sent to the server + */ + function testWithPermissions(possiblePermissions) { + model.set({ + permissions: possiblePermissions, + possiblePermissions: possiblePermissions + }); + model.addShare({ + shareType: OC.Share.SHARE_TYPE_USER, + shareWith: 'user2' + }); + + var requestBody = OC.parseQueryString(_.last(fakeServer.requests).requestBody); + return parseInt(requestBody.permissions, 10); + } + + describe('regular sharing', function() { + it('shares with given permissions with default config', function() { + configModel.set('isResharingAllowed', true); + model.set({ + reshare: {}, + shares: [] + }); + expect( + testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) + ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE); + expect( + testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_SHARE) + ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_SHARE); + }); + it('removes share permission when not allowed', function() { + configModel.set('isResharingAllowed', false); + model.set({ + reshare: {}, + shares: [] + }); + expect( + testWithPermissions(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) + ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE); + }); + it('automatically adds READ permission even when not specified', function() { + configModel.set('isResharingAllowed', false); + model.set({ + reshare: {}, + shares: [] + }); + expect( + testWithPermissions(OC.PERMISSION_UPDATE | OC.PERMISSION_SHARE) + ).toEqual(OC.PERMISSION_READ | OC.PERMISSION_UPDATE | OC.PERMISSION_UPDATE); + }); + }); + }); + + describe('saveLinkShare', function() { + var addShareStub; + var updateShareStub; + + beforeEach(function() { + addShareStub = sinon.stub(model, 'addShare'); + updateShareStub = sinon.stub(model, 'updateShare'); + }); + afterEach(function() { + addShareStub.restore(); + updateShareStub.restore(); + }); + + it('creates a new share if no link share exists', function() { + model.set({ + linkShare: { + isLinkShare: false + } + }); + + model.saveLinkShare(); + + expect(addShareStub.calledOnce).toEqual(true); + expect(addShareStub.firstCall.args[0]).toEqual({ + password: '', + passwordChanged: false, + permissions: OC.PERMISSION_READ, + expireDate: '', + shareType: OC.Share.SHARE_TYPE_LINK + }); + expect(updateShareStub.notCalled).toEqual(true); + }); + it('creates a new share with default expiration date', function() { + var clock = sinon.useFakeTimers(Date.UTC(2015, 6, 17, 1, 2, 0, 3)); + configModel.set({ + isDefaultExpireDateEnabled: true, + defaultExpireDate: 7 + }); + model.set({ + linkShare: { + isLinkShare: false + } + }); + + model.saveLinkShare(); + + expect(addShareStub.calledOnce).toEqual(true); + expect(addShareStub.firstCall.args[0]).toEqual({ + password: '', + passwordChanged: false, + permissions: OC.PERMISSION_READ, + expireDate: '2015-7-24 00:00:00', + shareType: OC.Share.SHARE_TYPE_LINK + }); + expect(updateShareStub.notCalled).toEqual(true); + clock.restore(); + }); + it('updates link share if it exists', function() { + model.set({ + linkShare: { + isLinkShare: true, + id: 123 + } + }); + + model.saveLinkShare({ + password: 'test' + }); + + expect(addShareStub.notCalled).toEqual(true); + expect(updateShareStub.calledOnce).toEqual(true); + expect(updateShareStub.firstCall.args[0]).toEqual(123); + expect(updateShareStub.firstCall.args[1]).toEqual({ + password: 'test' + }); + }); + }); + describe('creating shares', function() { + it('sends POST method to endpoint with passed values', function() { + model.addShare({ + shareType: OC.Share.SHARE_TYPE_GROUP, + shareWith: 'group1' + }); + + expect(fakeServer.requests.length).toEqual(1); + expect(fakeServer.requests[0].method).toEqual('POST'); + expect(fakeServer.requests[0].url).toEqual( + OC.linkToOCS('apps/files_sharing/api/v1', 2) + + 'shares?format=json' + ); + expect(OC.parseQueryString(fakeServer.requests[0].requestBody)).toEqual({ + path: '/subdir/shared_file_name.txt', + permissions: '' + OC.PERMISSION_READ, + shareType: '' + OC.Share.SHARE_TYPE_GROUP, + shareWith: 'group1' + }); + }); + }); + describe('updating shares', function() { + it('sends PUT method to endpoint with passed values', function() { + model.updateShare(123, { + permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE + }); + + expect(fakeServer.requests.length).toEqual(1); + expect(fakeServer.requests[0].method).toEqual('PUT'); + expect(fakeServer.requests[0].url).toEqual( + OC.linkToOCS('apps/files_sharing/api/v1', 2) + + 'shares/123?format=json' + ); + expect(OC.parseQueryString(fakeServer.requests[0].requestBody)).toEqual({ + permissions: '' + (OC.PERMISSION_READ | OC.PERMISSION_SHARE) + }); + }); + }); + describe('removing shares', function() { + it('sends DELETE method to endpoint with share id', function() { + model.removeShare(123); + + expect(fakeServer.requests.length).toEqual(1); + expect(fakeServer.requests[0].method).toEqual('DELETE'); + expect(fakeServer.requests[0].url).toEqual( + OC.linkToOCS('apps/files_sharing/api/v1', 2) + + 'shares/123?format=json' + ); + }); + }); }); -- 2.39.5