@@ -173,3 +173,4 @@ core/js/mimetypelist.js | |||
# Tests - cypress | |||
cypress/snapshots | |||
cypress/videos | |||
cypress/downloads |
@@ -241,11 +241,6 @@ class Server { | |||
$this->server->addPlugin(new FakeLockerPlugin()); | |||
} | |||
// Allow view-only plugin for webdav requests | |||
$this->server->addPlugin(new ViewOnlyPlugin( | |||
\OC::$server->getUserFolder(), | |||
)); | |||
if (BrowserErrorPagePlugin::isBrowserRequest($request)) { | |||
$this->server->addPlugin(new BrowserErrorPagePlugin()); | |||
} | |||
@@ -255,6 +250,11 @@ class Server { | |||
// wait with registering these until auth is handled and the filesystem is setup | |||
$this->server->on('beforeMethod:*', function () use ($root, $lazySearchBackend, $logger) { | |||
// Allow view-only plugin for webdav requests | |||
$this->server->addPlugin(new ViewOnlyPlugin( | |||
\OC::$server->getUserFolder(), | |||
)); | |||
// custom properties plugin must be the last one | |||
$userSession = \OC::$server->getUserSession(); | |||
$user = $userSession->getUser(); |
@@ -1,290 +0,0 @@ | |||
<!-- | |||
- @copyright Copyright (c) 2022 Louis Chmn <louis@chmn.me> | |||
- | |||
- @author Louis Chmn <louis@chmn.me> | |||
- | |||
- @license GNU AGPL version 3 or any later version | |||
- | |||
- This program is free software: you can redistribute it and/or modify | |||
- it under the terms of the GNU Affero General Public License as | |||
- published by the Free Software Foundation, either version 3 of the | |||
- License, or (at your option) any later version. | |||
- | |||
- This program is distributed in the hope that it will be useful, | |||
- but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
- GNU Affero General Public License for more details. | |||
- | |||
- You should have received a copy of the GNU Affero General Public License | |||
- along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
- | |||
--> | |||
<template> | |||
<li> | |||
<ul> | |||
<!-- file --> | |||
<NcActionCheckbox v-if="!isFolder" | |||
:checked="shareHasPermissions(atomicPermissions.UPDATE)" | |||
@update:checked="toggleSharePermissions(atomicPermissions.UPDATE)"> | |||
{{ t('files_sharing', 'Allow editing') }} | |||
</NcActionCheckbox> | |||
<!-- folder --> | |||
<template v-if="isFolder && fileHasCreatePermission && config.isPublicUploadEnabled"> | |||
<template v-if="!showCustomPermissionsForm"> | |||
<NcActionRadio :checked="sharePermissionEqual(bundledPermissions.READ_ONLY)" | |||
:value="bundledPermissions.READ_ONLY" | |||
:name="randomFormName" | |||
@change="setSharePermissions(bundledPermissions.READ_ONLY)"> | |||
{{ t('files_sharing', 'Read only') }} | |||
</NcActionRadio> | |||
<NcActionRadio :checked="sharePermissionEqual(bundledPermissions.UPLOAD_AND_UPDATE)" | |||
:value="bundledPermissions.UPLOAD_AND_UPDATE" | |||
:name="randomFormName" | |||
@change="setSharePermissions(bundledPermissions.UPLOAD_AND_UPDATE)"> | |||
{{ t('files_sharing', 'Allow upload and editing') }} | |||
</NcActionRadio> | |||
<NcActionRadio :checked="sharePermissionEqual(bundledPermissions.FILE_DROP)" | |||
:value="bundledPermissions.FILE_DROP" | |||
:name="randomFormName" | |||
class="sharing-entry__action--public-upload" | |||
@change="setSharePermissions(bundledPermissions.FILE_DROP)"> | |||
{{ t('files_sharing', 'File drop (upload only)') }} | |||
</NcActionRadio> | |||
<!-- custom permissions button --> | |||
<NcActionButton :title="t('files_sharing', 'Custom permissions')" | |||
@click="showCustomPermissionsForm = true"> | |||
<template #icon> | |||
<Tune /> | |||
</template> | |||
{{ sharePermissionsIsBundle ? "" : sharePermissionsSummary }} | |||
</NcActionButton> | |||
</template> | |||
<!-- custom permissions --> | |||
<span v-else :class="{error: !sharePermissionsSetIsValid}"> | |||
<NcActionCheckbox :checked="shareHasPermissions(atomicPermissions.READ)" | |||
:disabled="!canToggleSharePermissions(atomicPermissions.READ)" | |||
@update:checked="toggleSharePermissions(atomicPermissions.READ)"> | |||
{{ t('files_sharing', 'Read') }} | |||
</NcActionCheckbox> | |||
<NcActionCheckbox :checked="shareHasPermissions(atomicPermissions.CREATE)" | |||
:disabled="!canToggleSharePermissions(atomicPermissions.CREATE)" | |||
@update:checked="toggleSharePermissions(atomicPermissions.CREATE)"> | |||
{{ t('files_sharing', 'Upload') }} | |||
</NcActionCheckbox> | |||
<NcActionCheckbox :checked="shareHasPermissions(atomicPermissions.UPDATE)" | |||
:disabled="!canToggleSharePermissions(atomicPermissions.UPDATE)" | |||
@update:checked="toggleSharePermissions(atomicPermissions.UPDATE)"> | |||
{{ t('files_sharing', 'Edit') }} | |||
</NcActionCheckbox> | |||
<NcActionCheckbox :checked="shareHasPermissions(atomicPermissions.DELETE)" | |||
:disabled="!canToggleSharePermissions(atomicPermissions.DELETE)" | |||
@update:checked="toggleSharePermissions(atomicPermissions.DELETE)"> | |||
{{ t('files_sharing', 'Delete') }} | |||
</NcActionCheckbox> | |||
<NcActionButton @click="showCustomPermissionsForm = false"> | |||
<template #icon> | |||
<ChevronLeft /> | |||
</template> | |||
{{ t('files_sharing', 'Bundled permissions') }} | |||
</NcActionButton> | |||
</span> | |||
</template> | |||
</ul> | |||
</li> | |||
</template> | |||
<script> | |||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' | |||
import NcActionRadio from '@nextcloud/vue/dist/Components/NcActionRadio.js' | |||
import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js' | |||
import SharesMixin from '../mixins/SharesMixin.js' | |||
import { | |||
ATOMIC_PERMISSIONS, | |||
BUNDLED_PERMISSIONS, | |||
hasPermissions, | |||
permissionsSetIsValid, | |||
togglePermissions, | |||
canTogglePermissions, | |||
} from '../lib/SharePermissionsToolBox.js' | |||
import Tune from 'vue-material-design-icons/Tune.vue' | |||
import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue' | |||
export default { | |||
name: 'SharePermissionsEditor', | |||
components: { | |||
NcActionButton, | |||
NcActionCheckbox, | |||
NcActionRadio, | |||
Tune, | |||
ChevronLeft, | |||
}, | |||
mixins: [SharesMixin], | |||
data() { | |||
return { | |||
randomFormName: Math.random().toString(27).substring(2), | |||
showCustomPermissionsForm: false, | |||
atomicPermissions: ATOMIC_PERMISSIONS, | |||
bundledPermissions: BUNDLED_PERMISSIONS, | |||
} | |||
}, | |||
computed: { | |||
/** | |||
* Return the summary of custom checked permissions. | |||
* | |||
* @return {string} | |||
*/ | |||
sharePermissionsSummary() { | |||
return Object.values(this.atomicPermissions) | |||
.filter(permission => this.shareHasPermissions(permission)) | |||
.map(permission => { | |||
switch (permission) { | |||
case this.atomicPermissions.CREATE: | |||
return this.t('files_sharing', 'Upload') | |||
case this.atomicPermissions.READ: | |||
return this.t('files_sharing', 'Read') | |||
case this.atomicPermissions.UPDATE: | |||
return this.t('files_sharing', 'Edit') | |||
case this.atomicPermissions.DELETE: | |||
return this.t('files_sharing', 'Delete') | |||
default: | |||
return null | |||
} | |||
}) | |||
.filter(permissionLabel => permissionLabel !== null) | |||
.join(', ') | |||
}, | |||
/** | |||
* Return whether the share's permission is a bundle. | |||
* | |||
* @return {boolean} | |||
*/ | |||
sharePermissionsIsBundle() { | |||
return Object.values(BUNDLED_PERMISSIONS) | |||
.map(bundle => this.sharePermissionEqual(bundle)) | |||
.filter(isBundle => isBundle) | |||
.length > 0 | |||
}, | |||
/** | |||
* Return whether the share's permission is valid. | |||
* | |||
* @return {boolean} | |||
*/ | |||
sharePermissionsSetIsValid() { | |||
return permissionsSetIsValid(this.share.permissions) | |||
}, | |||
/** | |||
* Is the current share a folder ? | |||
* TODO: move to a proper FileInfo model? | |||
* | |||
* @return {boolean} | |||
*/ | |||
isFolder() { | |||
return this.fileInfo.type === 'dir' | |||
}, | |||
/** | |||
* Does the current file/folder have create permissions. | |||
* TODO: move to a proper FileInfo model? | |||
* | |||
* @return {boolean} | |||
*/ | |||
fileHasCreatePermission() { | |||
return !!(this.fileInfo.permissions & ATOMIC_PERMISSIONS.CREATE) | |||
}, | |||
}, | |||
mounted() { | |||
// Show the Custom Permissions view on open if the permissions set is not a bundle. | |||
this.showCustomPermissionsForm = !this.sharePermissionsIsBundle | |||
}, | |||
methods: { | |||
/** | |||
* Return whether the share has the exact given permissions. | |||
* | |||
* @param {number} permissions - the permissions to check. | |||
* | |||
* @return {boolean} | |||
*/ | |||
sharePermissionEqual(permissions) { | |||
// We use the share's permission without PERMISSION_SHARE as it is not relevant here. | |||
return (this.share.permissions & ~ATOMIC_PERMISSIONS.SHARE) === permissions | |||
}, | |||
/** | |||
* Return whether the share has the given permissions. | |||
* | |||
* @param {number} permissions - the permissions to check. | |||
* | |||
* @return {boolean} | |||
*/ | |||
shareHasPermissions(permissions) { | |||
return hasPermissions(this.share.permissions, permissions) | |||
}, | |||
/** | |||
* Set the share permissions to the given permissions. | |||
* | |||
* @param {number} permissions - the permissions to set. | |||
* | |||
* @return {void} | |||
*/ | |||
setSharePermissions(permissions) { | |||
this.share.permissions = permissions | |||
this.queueUpdate('permissions') | |||
}, | |||
/** | |||
* Return whether some given permissions can be toggled. | |||
* | |||
* @param {number} permissionsToToggle - the permissions to toggle. | |||
* | |||
* @return {boolean} | |||
*/ | |||
canToggleSharePermissions(permissionsToToggle) { | |||
return canTogglePermissions(this.share.permissions, permissionsToToggle) | |||
}, | |||
/** | |||
* Toggle a given permission. | |||
* | |||
* @param {number} permissions - the permissions to toggle. | |||
* | |||
* @return {void} | |||
*/ | |||
toggleSharePermissions(permissions) { | |||
this.share.permissions = togglePermissions(this.share.permissions, permissions) | |||
if (!permissionsSetIsValid(this.share.permissions)) { | |||
return | |||
} | |||
this.queueUpdate('permissions') | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.error { | |||
::v-deep .action-checkbox__label:before { | |||
border: 1px solid var(--color-error); | |||
} | |||
} | |||
</style> |
@@ -46,6 +46,7 @@ | |||
@open-sharing-details="openShareDetailsForCustomSettings(share)" /> | |||
</div> | |||
<NcButton class="sharing-entry__action" | |||
data-cy-files-sharing-share-actions | |||
:aria-label="t('files_sharing', 'Open Sharing Details')" | |||
type="tertiary" | |||
@click="openSharingDetails(share)"> |
@@ -19,6 +19,7 @@ | |||
<div ref="quickPermissions" class="sharingTabDetailsView__quick-permissions"> | |||
<div> | |||
<NcCheckboxRadioSwitch :button-variant="true" | |||
data-cy-files-sharing-share-permissions-bundle="read-only" | |||
:checked.sync="sharingPermission" | |||
:value="bundledPermissions.READ_ONLY.toString()" | |||
name="sharing_permission_radio" | |||
@@ -31,6 +32,7 @@ | |||
</template> | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch :button-variant="true" | |||
data-cy-files-sharing-share-permissions-bundle="upload-edit" | |||
:checked.sync="sharingPermission" | |||
:value="bundledPermissions.ALL.toString()" | |||
name="sharing_permission_radio" | |||
@@ -48,6 +50,7 @@ | |||
</template> | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch v-if="allowsFileDrop" | |||
data-cy-files-sharing-share-permissions-bundle="file-drop" | |||
:button-variant="true" | |||
:checked.sync="sharingPermission" | |||
:value="bundledPermissions.FILE_DROP.toString()" | |||
@@ -62,6 +65,7 @@ | |||
</template> | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch :button-variant="true" | |||
data-cy-files-sharing-share-permissions-bundle="custom" | |||
:checked.sync="sharingPermission" | |||
:value="'custom'" | |||
name="sharing_permission_radio" | |||
@@ -145,7 +149,10 @@ | |||
@update:checked="queueUpdate('hideDownload')"> | |||
{{ t('files_sharing', 'Hide download') }} | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch v-if="!isPublicShare" :disabled="!canSetDownload" :checked.sync="canDownload"> | |||
<NcCheckboxRadioSwitch v-if="!isPublicShare" | |||
:disabled="!canSetDownload" | |||
:checked.sync="canDownload" | |||
data-cy-files-sharing-share-permissions-checkbox="download"> | |||
{{ t('files_sharing', 'Allow download') }} | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch :checked.sync="writeNoteToRecipientIsChecked"> | |||
@@ -162,21 +169,30 @@ | |||
</NcCheckboxRadioSwitch> | |||
<section v-if="setCustomPermissions" class="custom-permissions-group"> | |||
<NcCheckboxRadioSwitch :disabled="!allowsFileDrop && share.type === SHARE_TYPES.SHARE_TYPE_LINK" | |||
:checked.sync="hasRead"> | |||
:checked.sync="hasRead" | |||
data-cy-files-sharing-share-permissions-checkbox="read"> | |||
{{ t('files_sharing', 'Read') }} | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch v-if="isFolder" :disabled="!canSetCreate" :checked.sync="canCreate"> | |||
<NcCheckboxRadioSwitch v-if="isFolder" | |||
:disabled="!canSetCreate" | |||
:checked.sync="canCreate" | |||
data-cy-files-sharing-share-permissions-checkbox="create"> | |||
{{ t('files_sharing', 'Create') }} | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch :disabled="!canSetEdit" :checked.sync="canEdit"> | |||
<NcCheckboxRadioSwitch :disabled="!canSetEdit" | |||
:checked.sync="canEdit" | |||
data-cy-files-sharing-share-permissions-checkbox="update"> | |||
{{ t('files_sharing', 'Edit') }} | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch v-if="config.isResharingAllowed && share.type !== SHARE_TYPES.SHARE_TYPE_LINK" | |||
:disabled="!canSetReshare" | |||
:checked.sync="canReshare"> | |||
:checked.sync="canReshare" | |||
data-cy-files-sharing-share-permissions-checkbox="share"> | |||
{{ t('files_sharing', 'Share') }} | |||
</NcCheckboxRadioSwitch> | |||
<NcCheckboxRadioSwitch :disabled="!canSetDelete" :checked.sync="canDelete"> | |||
<NcCheckboxRadioSwitch :disabled="!canSetDelete" | |||
:checked.sync="canDelete" | |||
data-cy-files-sharing-share-permissions-checkbox="delete"> | |||
{{ t('files_sharing', 'Delete') }} | |||
</NcCheckboxRadioSwitch> | |||
</section> | |||
@@ -199,10 +215,13 @@ | |||
<div class="sharingTabDetailsView__footer"> | |||
<div class="button-group"> | |||
<NcButton @click="$emit('close-sharing-details')"> | |||
<NcButton data-cy-files-sharing-share-editor-action="cancel" | |||
@click="$emit('close-sharing-details')"> | |||
{{ t('files_sharing', 'Cancel') }} | |||
</NcButton> | |||
<NcButton type="primary" @click="saveShare"> | |||
<NcButton type="primary" | |||
data-cy-files-sharing-share-editor-action="save" | |||
@click="saveShare"> | |||
{{ shareButtonText }} | |||
<template v-if="creating" #icon> | |||
<NcLoadingIcon /> |
@@ -19,7 +19,7 @@ | |||
<NcListItem class="version" | |||
:name="versionLabel" | |||
:force-display-actions="true" | |||
data-files-versions-version | |||
:data-files-versions-version="version.fileVersion" | |||
@click="click"> | |||
<!-- Icon --> | |||
<template #icon> | |||
@@ -52,6 +52,7 @@ | |||
<!-- Actions --> | |||
<template #actions> | |||
<NcActionButton v-if="enableLabeling && hasUpdatePermissions" | |||
data-cy-files-versions-version-action="label" | |||
:close-after-click="true" | |||
@click="labelUpdate"> | |||
<template #icon> | |||
@@ -60,6 +61,7 @@ | |||
{{ version.label === '' ? t('files_versions', 'Name this version') : t('files_versions', 'Edit version name') }} | |||
</NcActionButton> | |||
<NcActionButton v-if="!isCurrent && canView && canCompare" | |||
data-cy-files-versions-version-action="compare" | |||
:close-after-click="true" | |||
@click="compareVersion"> | |||
<template #icon> | |||
@@ -68,6 +70,7 @@ | |||
{{ t('files_versions', 'Compare to current version') }} | |||
</NcActionButton> | |||
<NcActionButton v-if="!isCurrent && hasUpdatePermissions" | |||
data-cy-files-versions-version-action="restore" | |||
:close-after-click="true" | |||
@click="restoreVersion"> | |||
<template #icon> | |||
@@ -76,6 +79,7 @@ | |||
{{ t('files_versions', 'Restore version') }} | |||
</NcActionButton> | |||
<NcActionLink v-if="isDownloadable" | |||
data-cy-files-versions-version-action="download" | |||
:href="downloadURL" | |||
:close-after-click="true" | |||
:download="downloadURL"> | |||
@@ -85,6 +89,7 @@ | |||
{{ t('files_versions', 'Download version') }} | |||
</NcActionLink> | |||
<NcActionButton v-if="!isCurrent && enableDeletion && hasDeletePermissions" | |||
data-cy-files-versions-version-action="delete" | |||
:close-after-click="true" | |||
@click="deleteVersion"> | |||
<template #icon> | |||
@@ -266,7 +271,11 @@ export default defineComponent({ | |||
this.$emit('restore', this.version) | |||
}, | |||
deleteVersion() { | |||
async deleteVersion() { | |||
// Let @nc-vue properly remove the popover before we delete the version. | |||
// This prevents @nc-vue from throwing a error. | |||
await this.$nextTick() | |||
await this.$nextTick() | |||
this.$emit('delete', this.version) | |||
}, | |||
@@ -30,3 +30,61 @@ export const triggerActionForFile = (filename: string, actionId: string) => { | |||
getActionButtonForFile(filename).click() | |||
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').click() | |||
} | |||
export const moveFile = (fileName: string, dirName: string) => { | |||
getRowForFile(fileName).should('be.visible') | |||
triggerActionForFile(fileName, 'move-copy') | |||
cy.get('.file-picker').within(() => { | |||
// intercept the copy so we can wait for it | |||
cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile') | |||
if (dirName === '/') { | |||
// select home folder | |||
cy.get('button[title="Home"]').should('be.visible').click() | |||
// click move | |||
cy.contains('button', 'Move').should('be.visible').click() | |||
} else if (dirName === '.') { | |||
// click move | |||
cy.contains('button', 'Copy').should('be.visible').click() | |||
} else { | |||
// select the folder | |||
cy.get(`[data-filename="${dirName}"]`).should('be.visible').click() | |||
// click move | |||
cy.contains('button', `Move to ${dirName}`).should('be.visible').click() | |||
} | |||
cy.wait('@moveFile') | |||
}) | |||
} | |||
export const copyFile = (fileName: string, dirName: string) => { | |||
getRowForFile(fileName).should('be.visible') | |||
triggerActionForFile(fileName, 'move-copy') | |||
cy.get('.file-picker').within(() => { | |||
// intercept the copy so we can wait for it | |||
cy.intercept('COPY', /\/remote.php\/dav\/files\//).as('copyFile') | |||
if (dirName === '/') { | |||
// select home folder | |||
cy.get('button[title="Home"]').should('be.visible').click() | |||
// click copy | |||
cy.contains('button', 'Copy').should('be.visible').click() | |||
} else if (dirName === '.') { | |||
// click copy | |||
cy.contains('button', 'Copy').should('be.visible').click() | |||
} else { | |||
// select folder | |||
cy.get(`[data-filename="${dirName}"]`).should('be.visible').click() | |||
// click copy | |||
cy.contains('button', `Copy to ${dirName}`).should('be.visible').click() | |||
} | |||
cy.wait('@copyFile') | |||
}) | |||
} | |||
export const navigateToFolder = (folderName: string) => { | |||
getRowForFile(folderName).should('be.visible').find('[data-cy-files-list-row-name-link]').click() | |||
} |
@@ -20,7 +20,7 @@ | |||
* | |||
*/ | |||
import { getRowForFile, triggerActionForFile } from './FilesUtils.ts' | |||
import { getRowForFile, moveFile, copyFile, navigateToFolder } from './FilesUtils.ts' | |||
describe('Files: Move or copy files', { testIsolation: true }, () => { | |||
let currentUser | |||
@@ -42,22 +42,9 @@ describe('Files: Move or copy files', { testIsolation: true }, () => { | |||
cy.login(currentUser) | |||
cy.visit('/apps/files') | |||
// intercept the copy so we can wait for it | |||
cy.intercept('COPY', /\/remote.php\/dav\/files\//).as('copyFile') | |||
copyFile('original.txt', 'new-folder') | |||
// Open actions and trigger copy-move action | |||
getRowForFile('original.txt').should('be.visible') | |||
triggerActionForFile('original.txt', 'move-copy') | |||
// select new folder | |||
cy.get('.file-picker [data-filename="new-folder"]').should('be.visible').click() | |||
// click copy | |||
cy.get('.file-picker').contains('button', 'Copy to new-folder').should('be.visible').click() | |||
// wait for copy to finish | |||
cy.wait('@copyFile') | |||
getRowForFile('new-folder').find('[data-cy-files-list-row-name-link]').click() | |||
navigateToFolder('new-folder') | |||
cy.url().should('contain', 'dir=/new-folder') | |||
getRowForFile('original.txt').should('be.visible') | |||
@@ -70,24 +57,14 @@ describe('Files: Move or copy files', { testIsolation: true }, () => { | |||
cy.login(currentUser) | |||
cy.visit('/apps/files') | |||
// intercept the copy so we can wait for it | |||
cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile') | |||
getRowForFile('original.txt').should('be.visible') | |||
triggerActionForFile('original.txt', 'move-copy') | |||
// select new folder | |||
cy.get('.file-picker [data-filename="new-folder"]').should('be.visible').click() | |||
// click copy | |||
cy.get('.file-picker').contains('button', 'Move to new-folder').should('be.visible').click() | |||
moveFile('original.txt', 'new-folder') | |||
cy.wait('@moveFile') | |||
// wait until visible again | |||
getRowForFile('new-folder').should('be.visible') | |||
// original should be moved -> not exist anymore | |||
getRowForFile('original.txt').should('not.exist') | |||
getRowForFile('new-folder').should('be.visible').find('[data-cy-files-list-row-name-link]').click() | |||
navigateToFolder('new-folder') | |||
cy.url().should('contain', 'dir=/new-folder') | |||
getRowForFile('original.txt').should('be.visible') | |||
@@ -101,24 +78,14 @@ describe('Files: Move or copy files', { testIsolation: true }, () => { | |||
cy.login(currentUser) | |||
cy.visit('/apps/files') | |||
// intercept the copy so we can wait for it | |||
cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile') | |||
getRowForFile('original').should('be.visible') | |||
triggerActionForFile('original', 'move-copy') | |||
// select new folder | |||
cy.get('.file-picker [data-filename="original folder"]').should('be.visible').click() | |||
// click copy | |||
cy.get('.file-picker').contains('button', 'Move to original folder').should('be.visible').click() | |||
moveFile('original', 'original folder') | |||
cy.wait('@moveFile') | |||
// wait until visible again | |||
getRowForFile('original folder').should('be.visible') | |||
// original should be moved -> not exist anymore | |||
getRowForFile('original').should('not.exist') | |||
getRowForFile('original folder').should('be.visible').find('[data-cy-files-list-row-name-link]').click() | |||
navigateToFolder('original folder') | |||
cy.url().should('contain', 'dir=/original%20folder') | |||
getRowForFile('original').should('be.visible') | |||
@@ -131,21 +98,11 @@ describe('Files: Move or copy files', { testIsolation: true }, () => { | |||
cy.login(currentUser) | |||
cy.visit('/apps/files') | |||
// intercept the copy so we can wait for it | |||
cy.intercept('MOVE', /\/remote.php\/dav\/files\//).as('moveFile') | |||
getRowForFile('new-folder').should('be.visible').find('[data-cy-files-list-row-name-link]').click() | |||
navigateToFolder('new-folder') | |||
cy.url().should('contain', 'dir=/new-folder') | |||
getRowForFile('original.txt').should('be.visible') | |||
triggerActionForFile('original.txt', 'move-copy') | |||
// select new folder | |||
cy.get('.file-picker button[title="Home"]').should('be.visible').click() | |||
// click move | |||
cy.get('.file-picker').contains('button', 'Move').should('be.visible').click() | |||
moveFile('original.txt', '/') | |||
cy.wait('@moveFile') | |||
// wait until visible again | |||
cy.get('main').contains('No files in here').should('be.visible') | |||
@@ -162,16 +119,8 @@ describe('Files: Move or copy files', { testIsolation: true }, () => { | |||
cy.login(currentUser) | |||
cy.visit('/apps/files') | |||
// intercept the copy so we can wait for it | |||
cy.intercept('COPY', /\/remote.php\/dav\/files\//).as('copyFile') | |||
copyFile('original.txt', '.') | |||
getRowForFile('original.txt').should('be.visible') | |||
triggerActionForFile('original.txt', 'move-copy') | |||
// click copy | |||
cy.get('.file-picker').contains('button', 'Copy').should('be.visible').click() | |||
cy.wait('@copyFile') | |||
getRowForFile('original.txt').should('be.visible') | |||
getRowForFile('original (copy).txt').should('be.visible') | |||
}) | |||
@@ -182,16 +131,8 @@ describe('Files: Move or copy files', { testIsolation: true }, () => { | |||
cy.login(currentUser) | |||
cy.visit('/apps/files') | |||
// intercept the copy so we can wait for it | |||
cy.intercept('COPY', /\/remote.php\/dav\/files\//).as('copyFile') | |||
getRowForFile('original.txt').should('be.visible') | |||
triggerActionForFile('original.txt', 'move-copy') | |||
// click copy | |||
cy.get('.file-picker').contains('button', 'Copy').should('be.visible').click() | |||
copyFile('original.txt', '.') | |||
cy.wait('@copyFile') | |||
getRowForFile('original.txt').should('be.visible') | |||
getRowForFile('original (copy 2).txt').should('be.visible') | |||
}) |
@@ -0,0 +1,104 @@ | |||
/* eslint-disable jsdoc/require-jsdoc */ | |||
/** | |||
* @copyright Copyright (c) 2024 Louis Chemineau <louis@chmn.me> | |||
* | |||
* @author Louis Chemineau <louis@chmn.me> | |||
* | |||
* @license AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
import { triggerActionForFile } from '../files/FilesUtils' | |||
export interface ShareSetting { | |||
read: boolean | |||
update: boolean | |||
delete: boolean | |||
share: boolean | |||
download: boolean | |||
} | |||
export function createShare(fileName: string, username: string, shareSettings: Partial<ShareSetting> = {}) { | |||
openSharingPanel(fileName) | |||
cy.get('#app-sidebar-vue').within(() => { | |||
cy.get('#sharing-search-input').clear() | |||
cy.intercept({ times: 1, method: 'GET', url: '**/apps/files_sharing/api/v1/sharees?*' }).as('userSearch') | |||
cy.get('#sharing-search-input').type(username) | |||
cy.wait('@userSearch') | |||
}) | |||
cy.get(`[user="${username}"]`).click() | |||
// HACK: Save the share and then update it, as permissions changes are currently not saved for new share. | |||
cy.get('[data-cy-files-sharing-share-editor-action="save"]').click({ scrollBehavior: 'nearest' }) | |||
updateShare(fileName, 0, shareSettings) | |||
} | |||
export function updateShare(fileName: string, index: number, shareSettings: Partial<ShareSetting> = {}) { | |||
openSharingPanel(fileName) | |||
cy.get('#app-sidebar-vue').within(() => { | |||
cy.get('[data-cy-files-sharing-share-actions]').eq(index).click() | |||
cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]').click() | |||
if (shareSettings.download !== undefined) { | |||
cy.get('[data-cy-files-sharing-share-permissions-checkbox="download"]').find('input').as('downloadCheckbox') | |||
if (shareSettings.download) { | |||
cy.get('@downloadCheckbox').check({ force: true, scrollBehavior: 'nearest' }) | |||
} else { | |||
cy.get('@downloadCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' }) | |||
} | |||
} | |||
if (shareSettings.read !== undefined) { | |||
cy.get('[data-cy-files-sharing-share-permissions-checkbox="read"]').find('input').as('readCheckbox') | |||
if (shareSettings.read) { | |||
cy.get('@readCheckbox').check({ force: true, scrollBehavior: 'nearest' }) | |||
} else { | |||
cy.get('@readCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' }) | |||
} | |||
} | |||
if (shareSettings.update !== undefined) { | |||
cy.get('[data-cy-files-sharing-share-permissions-checkbox="update"]').find('input').as('updateCheckbox') | |||
if (shareSettings.update) { | |||
cy.get('@updateCheckbox').check({ force: true, scrollBehavior: 'nearest' }) | |||
} else { | |||
cy.get('@updateCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' }) | |||
} | |||
} | |||
if (shareSettings.delete !== undefined) { | |||
cy.get('[data-cy-files-sharing-share-permissions-checkbox="delete"]').find('input').as('deleteCheckbox') | |||
if (shareSettings.delete) { | |||
cy.get('@deleteCheckbox').check({ force: true, scrollBehavior: 'nearest' }) | |||
} else { | |||
cy.get('@deleteCheckbox').uncheck({ force: true, scrollBehavior: 'nearest' }) | |||
} | |||
} | |||
cy.get('[data-cy-files-sharing-share-editor-action="save"]').click({ scrollBehavior: 'nearest' }) | |||
}) | |||
} | |||
export function openSharingPanel(fileName: string) { | |||
triggerActionForFile(fileName, 'details') | |||
cy.get('#app-sidebar-vue') | |||
.get('[aria-controls="tab-sharing"]') | |||
.click() | |||
} |
@@ -1,3 +1,4 @@ | |||
/* eslint-disable jsdoc/require-jsdoc */ | |||
/** | |||
* @copyright Copyright (c) 2022 Louis Chemineau <louis@chmn.me> | |||
* | |||
@@ -22,6 +23,7 @@ | |||
import type { User } from '@nextcloud/cypress' | |||
import path from 'path' | |||
import { createShare, type ShareSetting } from '../files_sharing/filesSharingUtils' | |||
export const uploadThreeVersions = (user: User, fileName: string) => { | |||
// A new version will not be created if the changes occur | |||
@@ -35,7 +37,7 @@ export const uploadThreeVersions = (user: User, fileName: string) => { | |||
cy.login(user) | |||
} | |||
export const openVersionsPanel = (fileName: string) =>{ | |||
export function openVersionsPanel(fileName: string) { | |||
// Detect the versions list fetch | |||
cy.intercept('PROPFIND', '**/dav/versions/*/versions/**').as('getVersions') | |||
@@ -50,35 +52,61 @@ export const openVersionsPanel = (fileName: string) =>{ | |||
cy.get('#tab-version_vue').should('be.visible', { timeout: 10000 }) | |||
} | |||
export const openVersionMenu = (index: number) => { | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]') | |||
.eq(index).within(() => { | |||
cy.get('.action-item__menutoggle').filter(':visible') | |||
.click() | |||
}) | |||
}) | |||
export function toggleVersionMenu(index: number) { | |||
cy.get('#tab-version_vue [data-files-versions-version]') | |||
.eq(index) | |||
.find('button') | |||
.click() | |||
} | |||
export const clickPopperAction = (actionName: string) => { | |||
cy.get('.v-popper__popper').filter(':visible') | |||
.contains(actionName) | |||
.click() | |||
export function triggerVersionAction(index: number, actionName: string) { | |||
toggleVersionMenu(index) | |||
cy.get(`[data-cy-files-versions-version-action="${actionName}"]`).filter(':visible').click() | |||
} | |||
export const nameVersion = (index: number, name: string) => { | |||
openVersionMenu(index) | |||
clickPopperAction('Name this version') | |||
export function nameVersion(index: number, name: string) { | |||
cy.intercept('PROPPATCH', '**/dav/versions/*/versions/**').as('labelVersion') | |||
triggerVersionAction(index, 'label') | |||
cy.get(':focused').type(`${name}{enter}`) | |||
cy.wait('@labelVersion') | |||
} | |||
export const assertVersionContent = (filename: string, index: number, expectedContent: string) => { | |||
export function restoreVersion(index: number) { | |||
cy.intercept('MOVE', '**/dav/versions/*/versions/**').as('restoreVersion') | |||
triggerVersionAction(index, 'restore') | |||
cy.wait('@restoreVersion') | |||
} | |||
export function deleteVersion(index: number) { | |||
cy.intercept('DELETE', '**/dav/versions/*/versions/**').as('deleteVersion') | |||
triggerVersionAction(index, 'delete') | |||
cy.wait('@deleteVersion') | |||
} | |||
export function doesNotHaveAction(index: number, actionName: string) { | |||
toggleVersionMenu(index) | |||
cy.get(`[data-cy-files-versions-version-action="${actionName}"]`).should('not.exist') | |||
toggleVersionMenu(index) | |||
} | |||
export function assertVersionContent(filename: string, index: number, expectedContent: string) { | |||
const downloadsFolder = Cypress.config('downloadsFolder') | |||
openVersionMenu(index) | |||
clickPopperAction('Download version') | |||
triggerVersionAction(index, 'download') | |||
return cy.readFile(path.join(downloadsFolder, filename)) | |||
.then((versionContent) => expect(versionContent).to.equal(expectedContent)) | |||
.then(() => cy.exec(`rm ${downloadsFolder}/${filename}`)) | |||
} | |||
export function setupTestSharedFileFromUser(owner: User, randomFileName: string, shareOptions: Partial<ShareSetting>) { | |||
return cy.createRandomUser() | |||
.then((recipient) => { | |||
cy.login(owner) | |||
cy.visit('/apps/files') | |||
createShare(randomFileName, recipient.userId, shareOptions) | |||
cy.login(recipient) | |||
cy.visit('/apps/files') | |||
return cy.wrap(recipient) | |||
}) | |||
} |
@@ -0,0 +1,110 @@ | |||
/** | |||
* @copyright Copyright (c) 2024 Louis Chmn <louis@chmn.me> | |||
* | |||
* @author Louis Chmn <louis@chmn.me> | |||
* | |||
* @license AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
import type { User } from '@nextcloud/cypress' | |||
import { doesNotHaveAction, openVersionsPanel, setupTestSharedFileFromUser, uploadThreeVersions, deleteVersion } from './filesVersionsUtils' | |||
import { navigateToFolder, getRowForFile } from '../files/FilesUtils' | |||
describe('Versions restoration', () => { | |||
const folderName = 'shared_folder' | |||
const randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | |||
const randomFilePath = `/${folderName}/${randomFileName}` | |||
let user: User | |||
let versionCount = 0 | |||
before(() => { | |||
cy.createRandomUser() | |||
.then((_user) => { | |||
user = _user | |||
cy.mkdir(user, `/${folderName}`) | |||
uploadThreeVersions(user, randomFilePath) | |||
uploadThreeVersions(user, randomFilePath) | |||
versionCount = 6 | |||
cy.login(user) | |||
cy.visit('/apps/files') | |||
navigateToFolder(folderName) | |||
openVersionsPanel(randomFilePath) | |||
}) | |||
}) | |||
it('Delete initial version', () => { | |||
cy.get('[data-files-versions-version]').should('have.length', versionCount) | |||
deleteVersion(2) | |||
versionCount-- | |||
cy.get('[data-files-versions-version]').should('have.length', versionCount) | |||
}) | |||
context('Delete versions of shared file', () => { | |||
it('Works with delete permission', () => { | |||
setupTestSharedFileFromUser(user, folderName, { delete: true }) | |||
navigateToFolder(folderName) | |||
openVersionsPanel(randomFilePath) | |||
cy.get('[data-files-versions-version]').should('have.length', versionCount) | |||
deleteVersion(2) | |||
versionCount-- | |||
cy.get('[data-files-versions-version]').should('have.length', versionCount) | |||
}) | |||
it('Does not work without delete permission', () => { | |||
setupTestSharedFileFromUser(user, folderName, { delete: false }) | |||
navigateToFolder(folderName) | |||
openVersionsPanel(randomFilePath) | |||
doesNotHaveAction(0, 'delete') | |||
doesNotHaveAction(1, 'delete') | |||
doesNotHaveAction(2, 'delete') | |||
}) | |||
it('Does not work without delete permission through direct API access', () => { | |||
let hostname: string | |||
let fileId: string|undefined | |||
let versionId: string|undefined | |||
setupTestSharedFileFromUser(user, folderName, { delete: false }) | |||
.then(recipient => { | |||
navigateToFolder(folderName) | |||
openVersionsPanel(randomFilePath) | |||
cy.url().then(url => { hostname = new URL(url).hostname }) | |||
getRowForFile(randomFileName).invoke('attr', 'data-cy-files-list-row-fileid').then(_fileId => { fileId = _fileId }) | |||
cy.get('[data-files-versions-version]').eq(1).invoke('attr', 'data-files-versions-version').then(_versionId => { versionId = _versionId }) | |||
cy.then(() => { | |||
cy.logout() | |||
cy.request({ | |||
method: 'DELETE', | |||
auth: { user: recipient.userId, pass: recipient.password }, | |||
headers: { | |||
cookie: '', | |||
}, | |||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`, | |||
failOnStatusCode: false, | |||
}) | |||
.then(({ status }) => { | |||
expect(status).to.equal(403) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) |
@@ -20,16 +20,20 @@ | |||
* | |||
*/ | |||
import { assertVersionContent, openVersionsPanel, uploadThreeVersions } from './filesVersionsUtils' | |||
import { assertVersionContent, doesNotHaveAction, openVersionsPanel, setupTestSharedFileFromUser, uploadThreeVersions } from './filesVersionsUtils' | |||
import type { User } from '@nextcloud/cypress' | |||
import { getRowForFile } from '../files/FilesUtils' | |||
describe('Versions download', () => { | |||
let randomFileName = '' | |||
let user: User | |||
before(() => { | |||
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | |||
cy.createRandomUser() | |||
.then((user) => { | |||
.then((_user) => { | |||
user = _user | |||
uploadThreeVersions(user, randomFileName) | |||
cy.login(user) | |||
cy.visit('/apps/files') | |||
@@ -37,9 +41,61 @@ describe('Versions download', () => { | |||
}) | |||
}) | |||
it('Download versions and assert there content', () => { | |||
it('Download versions and assert their content', () => { | |||
assertVersionContent(randomFileName, 0, 'v3') | |||
assertVersionContent(randomFileName, 1, 'v2') | |||
assertVersionContent(randomFileName, 2, 'v1') | |||
}) | |||
context('Download versions of shared file', () => { | |||
it('Works with download permission', () => { | |||
setupTestSharedFileFromUser(user, randomFileName, { download: true }) | |||
openVersionsPanel(randomFileName) | |||
assertVersionContent(randomFileName, 0, 'v3') | |||
assertVersionContent(randomFileName, 1, 'v2') | |||
assertVersionContent(randomFileName, 2, 'v1') | |||
}) | |||
it('Does not show action without download permission', () => { | |||
setupTestSharedFileFromUser(user, randomFileName, { download: false }) | |||
openVersionsPanel(randomFileName) | |||
cy.get('[data-files-versions-version]').eq(0).find('.action-item__menutoggle').should('not.exist') | |||
cy.get('[data-files-versions-version]').eq(0).get('[data-cy-version-action="download"]').should('not.exist') | |||
doesNotHaveAction(1, 'download') | |||
doesNotHaveAction(2, 'download') | |||
}) | |||
it('Does not work without download permission through direct API access', () => { | |||
let hostname: string | |||
let fileId: string|undefined | |||
let versionId: string|undefined | |||
setupTestSharedFileFromUser(user, randomFileName, { download: false }) | |||
.then(recipient => { | |||
openVersionsPanel(randomFileName) | |||
cy.url().then(url => { hostname = new URL(url).hostname }) | |||
getRowForFile(randomFileName).invoke('attr', 'data-cy-files-list-row-fileid').then(_fileId => { fileId = _fileId }) | |||
cy.get('[data-files-versions-version]').eq(1).invoke('attr', 'data-files-versions-version').then(_versionId => { versionId = _versionId }) | |||
cy.then(() => { | |||
cy.logout() | |||
cy.request({ | |||
auth: { user: recipient.userId, pass: recipient.password }, | |||
headers: { | |||
cookie: '', | |||
}, | |||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`, | |||
failOnStatusCode: false, | |||
}) | |||
.then(({ status }) => { | |||
expect(status).to.equal(403) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) |
@@ -20,16 +20,20 @@ | |||
* | |||
*/ | |||
import { nameVersion, openVersionsPanel, uploadThreeVersions } from './filesVersionsUtils' | |||
import type { User } from '@nextcloud/cypress' | |||
import { nameVersion, openVersionsPanel, uploadThreeVersions, doesNotHaveAction, setupTestSharedFileFromUser } from './filesVersionsUtils' | |||
import { getRowForFile } from '../files/FilesUtils' | |||
describe('Versions naming', () => { | |||
let randomFileName = '' | |||
let user: User | |||
before(() => { | |||
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | |||
cy.createRandomUser() | |||
.then((user) => { | |||
.then((_user) => { | |||
user = _user | |||
uploadThreeVersions(user, randomFileName) | |||
cy.login(user) | |||
cy.visit('/apps/files') | |||
@@ -37,25 +41,103 @@ describe('Versions naming', () => { | |||
}) | |||
}) | |||
it('Names the initial version as v1', () => { | |||
it('Names the versions', () => { | |||
nameVersion(2, 'v1') | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]').eq(2).contains('v1') | |||
cy.get('[data-files-versions-version]').eq(2).contains('Initial version').should('not.exist') | |||
}) | |||
}) | |||
it('Names the second version as v2', () => { | |||
nameVersion(1, 'v2') | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]').eq(1).contains('v2') | |||
}) | |||
}) | |||
it('Names the current version as v3', () => { | |||
nameVersion(0, 'v3') | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]').eq(0).contains('v3 (Current version)') | |||
}) | |||
}) | |||
context('Name versions of shared file', () => { | |||
context('with edit permission', () => { | |||
before(() => { | |||
setupTestSharedFileFromUser(user, randomFileName, { update: true }) | |||
openVersionsPanel(randomFileName) | |||
}) | |||
it('Names the versions', () => { | |||
nameVersion(2, 'v1 - shared') | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]').eq(2).contains('v1 - shared') | |||
cy.get('[data-files-versions-version]').eq(2).contains('Initial version').should('not.exist') | |||
}) | |||
nameVersion(1, 'v2 - shared') | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]').eq(1).contains('v2 - shared') | |||
}) | |||
nameVersion(0, 'v3 - shared') | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]').eq(0).contains('v3 - shared (Current version)') | |||
}) | |||
}) | |||
}) | |||
context('without edit permission', () => { | |||
it('Does not show action', () => { | |||
setupTestSharedFileFromUser(user, randomFileName, { update: false }) | |||
openVersionsPanel(randomFileName) | |||
cy.get('[data-files-versions-version]').eq(0).find('.action-item__menutoggle').should('not.exist') | |||
cy.get('[data-files-versions-version]').eq(0).get('[data-cy-version-action="label"]').should('not.exist') | |||
doesNotHaveAction(1, 'label') | |||
doesNotHaveAction(2, 'label') | |||
}) | |||
it('Does not work without update permission through direct API access', () => { | |||
let hostname: string | |||
let fileId: string|undefined | |||
let versionId: string|undefined | |||
setupTestSharedFileFromUser(user, randomFileName, { update: false }) | |||
.then(recipient => { | |||
openVersionsPanel(randomFileName) | |||
cy.url().then(url => { hostname = new URL(url).hostname }) | |||
getRowForFile(randomFileName).invoke('attr', 'data-cy-files-list-row-fileid').then(_fileId => { fileId = _fileId }) | |||
cy.get('[data-files-versions-version]').eq(1).invoke('attr', 'data-files-versions-version').then(_versionId => { versionId = _versionId }) | |||
cy.then(() => { | |||
cy.logout() | |||
cy.request({ | |||
method: 'PROPPATCH', | |||
auth: { user: recipient.userId, pass: recipient.password }, | |||
headers: { | |||
cookie: '', | |||
}, | |||
body: `<?xml version="1.0"?> | |||
<d:propertyupdate xmlns:d="DAV:" | |||
xmlns:oc="http://owncloud.org/ns" | |||
xmlns:nc="http://nextcloud.org/ns" | |||
xmlns:ocs="http://open-collaboration-services.org/ns"> | |||
<d:set> | |||
<d:prop> | |||
<nc:version-label>not authorized labeling</nc:version-label> | |||
</d:prop> | |||
</d:set> | |||
</d:propertyupdate>`, | |||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`, | |||
failOnStatusCode: false, | |||
}) | |||
.then(({ status }) => { | |||
expect(status).to.equal(403) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) |
@@ -20,23 +20,20 @@ | |||
* | |||
*/ | |||
import { assertVersionContent, clickPopperAction, openVersionMenu, openVersionsPanel, uploadThreeVersions } from './filesVersionsUtils' | |||
const restoreVersion = (index: number) => { | |||
cy.intercept('MOVE', '**/dav/versions/*/versions/**').as('restoreVersion') | |||
openVersionMenu(index) | |||
clickPopperAction('Restore version') | |||
cy.wait('@restoreVersion') | |||
} | |||
import type { User } from '@nextcloud/cypress' | |||
import { assertVersionContent, doesNotHaveAction, openVersionsPanel, setupTestSharedFileFromUser, restoreVersion, uploadThreeVersions } from './filesVersionsUtils' | |||
import { getRowForFile } from '../files/FilesUtils' | |||
describe('Versions restoration', () => { | |||
let randomFileName = '' | |||
let user: User | |||
before(() => { | |||
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | |||
cy.createRandomUser() | |||
.then((user) => { | |||
.then((_user) => { | |||
user = _user | |||
uploadThreeVersions(user, randomFileName) | |||
cy.login(user) | |||
cy.visit('/apps/files') | |||
@@ -44,8 +41,13 @@ describe('Versions restoration', () => { | |||
}) | |||
}) | |||
it('Current version does not have restore action', () => { | |||
doesNotHaveAction(0, 'restore') | |||
}) | |||
it('Restores initial version', () => { | |||
restoreVersion(2) | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]').should('have.length', 3) | |||
cy.get('[data-files-versions-version]').eq(0).contains('Current version') | |||
@@ -58,4 +60,69 @@ describe('Versions restoration', () => { | |||
assertVersionContent(randomFileName, 1, 'v3') | |||
assertVersionContent(randomFileName, 2, 'v2') | |||
}) | |||
context('Restore versions of shared file', () => { | |||
it('Works with update permission', () => { | |||
setupTestSharedFileFromUser(user, randomFileName, { update: true }) | |||
openVersionsPanel(randomFileName) | |||
it('Restores initial version', () => { | |||
restoreVersion(2) | |||
cy.get('#tab-version_vue').within(() => { | |||
cy.get('[data-files-versions-version]').should('have.length', 3) | |||
cy.get('[data-files-versions-version]').eq(0).contains('Current version') | |||
cy.get('[data-files-versions-version]').eq(2).contains('Initial version').should('not.exist') | |||
}) | |||
}) | |||
it('Downloads versions and assert there content', () => { | |||
assertVersionContent(randomFileName, 0, 'v1') | |||
assertVersionContent(randomFileName, 1, 'v3') | |||
assertVersionContent(randomFileName, 2, 'v2') | |||
}) | |||
}) | |||
it('Does not show action without delete permission', () => { | |||
setupTestSharedFileFromUser(user, randomFileName, { update: false }) | |||
openVersionsPanel(randomFileName) | |||
cy.get('[data-files-versions-version]').eq(0).find('.action-item__menutoggle').should('not.exist') | |||
cy.get('[data-files-versions-version]').eq(0).get('[data-cy-version-action="restore"]').should('not.exist') | |||
doesNotHaveAction(1, 'restore') | |||
doesNotHaveAction(2, 'restore') | |||
}) | |||
it('Does not work without update permission through direct API access', () => { | |||
let hostname: string | |||
let fileId: string|undefined | |||
let versionId: string|undefined | |||
setupTestSharedFileFromUser(user, randomFileName, { update: false }) | |||
.then(recipient => { | |||
openVersionsPanel(randomFileName) | |||
cy.url().then(url => { hostname = new URL(url).hostname }) | |||
getRowForFile(randomFileName).invoke('attr', 'data-cy-files-list-row-fileid').then(_fileId => { fileId = _fileId }) | |||
cy.get('[data-files-versions-version]').eq(1).invoke('attr', 'data-files-versions-version').then(_versionId => { versionId = _versionId }) | |||
cy.then(() => { | |||
cy.logout() | |||
cy.request({ | |||
method: 'MOVE', | |||
auth: { user: recipient.userId, pass: recipient.password }, | |||
headers: { | |||
cookie: '', | |||
Destination: 'https://nextcloud_server1.test/remote.php/dav/versions/admin/restore/target', | |||
}, | |||
url: `http://${hostname}/remote.php/dav/versions/${recipient.userId}/versions/${fileId}/${versionId}`, | |||
failOnStatusCode: false, | |||
}) | |||
.then(({ status }) => { | |||
expect(status).to.equal(403) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) | |||
}) |