# Tests - cypress | # Tests - cypress | ||||
cypress/snapshots | cypress/snapshots | ||||
cypress/videos | cypress/videos | ||||
cypress/downloads |
$this->server->addPlugin(new FakeLockerPlugin()); | $this->server->addPlugin(new FakeLockerPlugin()); | ||||
} | } | ||||
// Allow view-only plugin for webdav requests | |||||
$this->server->addPlugin(new ViewOnlyPlugin( | |||||
\OC::$server->getUserFolder(), | |||||
)); | |||||
if (BrowserErrorPagePlugin::isBrowserRequest($request)) { | if (BrowserErrorPagePlugin::isBrowserRequest($request)) { | ||||
$this->server->addPlugin(new BrowserErrorPagePlugin()); | $this->server->addPlugin(new BrowserErrorPagePlugin()); | ||||
} | } | ||||
// wait with registering these until auth is handled and the filesystem is setup | // wait with registering these until auth is handled and the filesystem is setup | ||||
$this->server->on('beforeMethod:*', function () use ($root, $lazySearchBackend, $logger) { | $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 | // custom properties plugin must be the last one | ||||
$userSession = \OC::$server->getUserSession(); | $userSession = \OC::$server->getUserSession(); | ||||
$user = $userSession->getUser(); | $user = $userSession->getUser(); |
<!-- | |||||
- @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> |
@open-sharing-details="openShareDetailsForCustomSettings(share)" /> | @open-sharing-details="openShareDetailsForCustomSettings(share)" /> | ||||
</div> | </div> | ||||
<NcButton class="sharing-entry__action" | <NcButton class="sharing-entry__action" | ||||
data-cy-files-sharing-share-actions | |||||
:aria-label="t('files_sharing', 'Open Sharing Details')" | :aria-label="t('files_sharing', 'Open Sharing Details')" | ||||
type="tertiary" | type="tertiary" | ||||
@click="openSharingDetails(share)"> | @click="openSharingDetails(share)"> |
<div ref="quickPermissions" class="sharingTabDetailsView__quick-permissions"> | <div ref="quickPermissions" class="sharingTabDetailsView__quick-permissions"> | ||||
<div> | <div> | ||||
<NcCheckboxRadioSwitch :button-variant="true" | <NcCheckboxRadioSwitch :button-variant="true" | ||||
data-cy-files-sharing-share-permissions-bundle="read-only" | |||||
:checked.sync="sharingPermission" | :checked.sync="sharingPermission" | ||||
:value="bundledPermissions.READ_ONLY.toString()" | :value="bundledPermissions.READ_ONLY.toString()" | ||||
name="sharing_permission_radio" | name="sharing_permission_radio" | ||||
</template> | </template> | ||||
</NcCheckboxRadioSwitch> | </NcCheckboxRadioSwitch> | ||||
<NcCheckboxRadioSwitch :button-variant="true" | <NcCheckboxRadioSwitch :button-variant="true" | ||||
data-cy-files-sharing-share-permissions-bundle="upload-edit" | |||||
:checked.sync="sharingPermission" | :checked.sync="sharingPermission" | ||||
:value="bundledPermissions.ALL.toString()" | :value="bundledPermissions.ALL.toString()" | ||||
name="sharing_permission_radio" | name="sharing_permission_radio" | ||||
</template> | </template> | ||||
</NcCheckboxRadioSwitch> | </NcCheckboxRadioSwitch> | ||||
<NcCheckboxRadioSwitch v-if="allowsFileDrop" | <NcCheckboxRadioSwitch v-if="allowsFileDrop" | ||||
data-cy-files-sharing-share-permissions-bundle="file-drop" | |||||
:button-variant="true" | :button-variant="true" | ||||
:checked.sync="sharingPermission" | :checked.sync="sharingPermission" | ||||
:value="bundledPermissions.FILE_DROP.toString()" | :value="bundledPermissions.FILE_DROP.toString()" | ||||
</template> | </template> | ||||
</NcCheckboxRadioSwitch> | </NcCheckboxRadioSwitch> | ||||
<NcCheckboxRadioSwitch :button-variant="true" | <NcCheckboxRadioSwitch :button-variant="true" | ||||
data-cy-files-sharing-share-permissions-bundle="custom" | |||||
:checked.sync="sharingPermission" | :checked.sync="sharingPermission" | ||||
:value="'custom'" | :value="'custom'" | ||||
name="sharing_permission_radio" | name="sharing_permission_radio" | ||||
@update:checked="queueUpdate('hideDownload')"> | @update:checked="queueUpdate('hideDownload')"> | ||||
{{ t('files_sharing', 'Hide download') }} | {{ t('files_sharing', 'Hide download') }} | ||||
</NcCheckboxRadioSwitch> | </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') }} | {{ t('files_sharing', 'Allow download') }} | ||||
</NcCheckboxRadioSwitch> | </NcCheckboxRadioSwitch> | ||||
<NcCheckboxRadioSwitch :checked.sync="writeNoteToRecipientIsChecked"> | <NcCheckboxRadioSwitch :checked.sync="writeNoteToRecipientIsChecked"> | ||||
</NcCheckboxRadioSwitch> | </NcCheckboxRadioSwitch> | ||||
<section v-if="setCustomPermissions" class="custom-permissions-group"> | <section v-if="setCustomPermissions" class="custom-permissions-group"> | ||||
<NcCheckboxRadioSwitch :disabled="!allowsFileDrop && share.type === SHARE_TYPES.SHARE_TYPE_LINK" | <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') }} | {{ t('files_sharing', 'Read') }} | ||||
</NcCheckboxRadioSwitch> | </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') }} | {{ t('files_sharing', 'Create') }} | ||||
</NcCheckboxRadioSwitch> | </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') }} | {{ t('files_sharing', 'Edit') }} | ||||
</NcCheckboxRadioSwitch> | </NcCheckboxRadioSwitch> | ||||
<NcCheckboxRadioSwitch v-if="config.isResharingAllowed && share.type !== SHARE_TYPES.SHARE_TYPE_LINK" | <NcCheckboxRadioSwitch v-if="config.isResharingAllowed && share.type !== SHARE_TYPES.SHARE_TYPE_LINK" | ||||
:disabled="!canSetReshare" | :disabled="!canSetReshare" | ||||
:checked.sync="canReshare"> | |||||
:checked.sync="canReshare" | |||||
data-cy-files-sharing-share-permissions-checkbox="share"> | |||||
{{ t('files_sharing', 'Share') }} | {{ t('files_sharing', 'Share') }} | ||||
</NcCheckboxRadioSwitch> | </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') }} | {{ t('files_sharing', 'Delete') }} | ||||
</NcCheckboxRadioSwitch> | </NcCheckboxRadioSwitch> | ||||
</section> | </section> | ||||
<div class="sharingTabDetailsView__footer"> | <div class="sharingTabDetailsView__footer"> | ||||
<div class="button-group"> | <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') }} | {{ t('files_sharing', 'Cancel') }} | ||||
</NcButton> | </NcButton> | ||||
<NcButton type="primary" @click="saveShare"> | |||||
<NcButton type="primary" | |||||
data-cy-files-sharing-share-editor-action="save" | |||||
@click="saveShare"> | |||||
{{ shareButtonText }} | {{ shareButtonText }} | ||||
<template v-if="creating" #icon> | <template v-if="creating" #icon> | ||||
<NcLoadingIcon /> | <NcLoadingIcon /> |
<NcListItem class="version" | <NcListItem class="version" | ||||
:name="versionLabel" | :name="versionLabel" | ||||
:force-display-actions="true" | :force-display-actions="true" | ||||
data-files-versions-version | |||||
:data-files-versions-version="version.fileVersion" | |||||
@click="click"> | @click="click"> | ||||
<!-- Icon --> | <!-- Icon --> | ||||
<template #icon> | <template #icon> | ||||
<!-- Actions --> | <!-- Actions --> | ||||
<template #actions> | <template #actions> | ||||
<NcActionButton v-if="enableLabeling && hasUpdatePermissions" | <NcActionButton v-if="enableLabeling && hasUpdatePermissions" | ||||
data-cy-files-versions-version-action="label" | |||||
:close-after-click="true" | :close-after-click="true" | ||||
@click="labelUpdate"> | @click="labelUpdate"> | ||||
<template #icon> | <template #icon> | ||||
{{ version.label === '' ? t('files_versions', 'Name this version') : t('files_versions', 'Edit version name') }} | {{ version.label === '' ? t('files_versions', 'Name this version') : t('files_versions', 'Edit version name') }} | ||||
</NcActionButton> | </NcActionButton> | ||||
<NcActionButton v-if="!isCurrent && canView && canCompare" | <NcActionButton v-if="!isCurrent && canView && canCompare" | ||||
data-cy-files-versions-version-action="compare" | |||||
:close-after-click="true" | :close-after-click="true" | ||||
@click="compareVersion"> | @click="compareVersion"> | ||||
<template #icon> | <template #icon> | ||||
{{ t('files_versions', 'Compare to current version') }} | {{ t('files_versions', 'Compare to current version') }} | ||||
</NcActionButton> | </NcActionButton> | ||||
<NcActionButton v-if="!isCurrent && hasUpdatePermissions" | <NcActionButton v-if="!isCurrent && hasUpdatePermissions" | ||||
data-cy-files-versions-version-action="restore" | |||||
:close-after-click="true" | :close-after-click="true" | ||||
@click="restoreVersion"> | @click="restoreVersion"> | ||||
<template #icon> | <template #icon> | ||||
{{ t('files_versions', 'Restore version') }} | {{ t('files_versions', 'Restore version') }} | ||||
</NcActionButton> | </NcActionButton> | ||||
<NcActionLink v-if="isDownloadable" | <NcActionLink v-if="isDownloadable" | ||||
data-cy-files-versions-version-action="download" | |||||
:href="downloadURL" | :href="downloadURL" | ||||
:close-after-click="true" | :close-after-click="true" | ||||
:download="downloadURL"> | :download="downloadURL"> | ||||
{{ t('files_versions', 'Download version') }} | {{ t('files_versions', 'Download version') }} | ||||
</NcActionLink> | </NcActionLink> | ||||
<NcActionButton v-if="!isCurrent && enableDeletion && hasDeletePermissions" | <NcActionButton v-if="!isCurrent && enableDeletion && hasDeletePermissions" | ||||
data-cy-files-versions-version-action="delete" | |||||
:close-after-click="true" | :close-after-click="true" | ||||
@click="deleteVersion"> | @click="deleteVersion"> | ||||
<template #icon> | <template #icon> | ||||
this.$emit('restore', this.version) | 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) | this.$emit('delete', this.version) | ||||
}, | }, | ||||
getActionButtonForFile(filename).click() | getActionButtonForFile(filename).click() | ||||
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).should('exist').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() | |||||
} |
* | * | ||||
*/ | */ | ||||
import { getRowForFile, triggerActionForFile } from './FilesUtils.ts' | |||||
import { getRowForFile, moveFile, copyFile, navigateToFolder } from './FilesUtils.ts' | |||||
describe('Files: Move or copy files', { testIsolation: true }, () => { | describe('Files: Move or copy files', { testIsolation: true }, () => { | ||||
let currentUser | let currentUser | ||||
cy.login(currentUser) | cy.login(currentUser) | ||||
cy.visit('/apps/files') | 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') | cy.url().should('contain', 'dir=/new-folder') | ||||
getRowForFile('original.txt').should('be.visible') | getRowForFile('original.txt').should('be.visible') | ||||
cy.login(currentUser) | cy.login(currentUser) | ||||
cy.visit('/apps/files') | 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 | // wait until visible again | ||||
getRowForFile('new-folder').should('be.visible') | getRowForFile('new-folder').should('be.visible') | ||||
// original should be moved -> not exist anymore | // original should be moved -> not exist anymore | ||||
getRowForFile('original.txt').should('not.exist') | 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') | cy.url().should('contain', 'dir=/new-folder') | ||||
getRowForFile('original.txt').should('be.visible') | getRowForFile('original.txt').should('be.visible') | ||||
cy.login(currentUser) | cy.login(currentUser) | ||||
cy.visit('/apps/files') | 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 | // wait until visible again | ||||
getRowForFile('original folder').should('be.visible') | getRowForFile('original folder').should('be.visible') | ||||
// original should be moved -> not exist anymore | // original should be moved -> not exist anymore | ||||
getRowForFile('original').should('not.exist') | 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') | cy.url().should('contain', 'dir=/original%20folder') | ||||
getRowForFile('original').should('be.visible') | getRowForFile('original').should('be.visible') | ||||
cy.login(currentUser) | cy.login(currentUser) | ||||
cy.visit('/apps/files') | 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') | 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 | // wait until visible again | ||||
cy.get('main').contains('No files in here').should('be.visible') | cy.get('main').contains('No files in here').should('be.visible') | ||||
cy.login(currentUser) | cy.login(currentUser) | ||||
cy.visit('/apps/files') | 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.txt').should('be.visible') | ||||
getRowForFile('original (copy).txt').should('be.visible') | getRowForFile('original (copy).txt').should('be.visible') | ||||
}) | }) | ||||
cy.login(currentUser) | cy.login(currentUser) | ||||
cy.visit('/apps/files') | 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.txt').should('be.visible') | ||||
getRowForFile('original (copy 2).txt').should('be.visible') | getRowForFile('original (copy 2).txt').should('be.visible') | ||||
}) | }) |
/* 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() | |||||
} |
/* eslint-disable jsdoc/require-jsdoc */ | |||||
/** | /** | ||||
* @copyright Copyright (c) 2022 Louis Chemineau <louis@chmn.me> | * @copyright Copyright (c) 2022 Louis Chemineau <louis@chmn.me> | ||||
* | * | ||||
import type { User } from '@nextcloud/cypress' | import type { User } from '@nextcloud/cypress' | ||||
import path from 'path' | import path from 'path' | ||||
import { createShare, type ShareSetting } from '../files_sharing/filesSharingUtils' | |||||
export const uploadThreeVersions = (user: User, fileName: string) => { | export const uploadThreeVersions = (user: User, fileName: string) => { | ||||
// A new version will not be created if the changes occur | // A new version will not be created if the changes occur | ||||
cy.login(user) | cy.login(user) | ||||
} | } | ||||
export const openVersionsPanel = (fileName: string) =>{ | |||||
export function openVersionsPanel(fileName: string) { | |||||
// Detect the versions list fetch | // Detect the versions list fetch | ||||
cy.intercept('PROPFIND', '**/dav/versions/*/versions/**').as('getVersions') | cy.intercept('PROPFIND', '**/dav/versions/*/versions/**').as('getVersions') | ||||
cy.get('#tab-version_vue').should('be.visible', { timeout: 10000 }) | 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.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') | const downloadsFolder = Cypress.config('downloadsFolder') | ||||
openVersionMenu(index) | |||||
clickPopperAction('Download version') | |||||
triggerVersionAction(index, 'download') | |||||
return cy.readFile(path.join(downloadsFolder, filename)) | return cy.readFile(path.join(downloadsFolder, filename)) | ||||
.then((versionContent) => expect(versionContent).to.equal(expectedContent)) | .then((versionContent) => expect(versionContent).to.equal(expectedContent)) | ||||
.then(() => cy.exec(`rm ${downloadsFolder}/${filename}`)) | .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) | |||||
}) | |||||
} |
/** | |||||
* @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) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) |
* | * | ||||
*/ | */ | ||||
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', () => { | describe('Versions download', () => { | ||||
let randomFileName = '' | let randomFileName = '' | ||||
let user: User | |||||
before(() => { | before(() => { | ||||
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | ||||
cy.createRandomUser() | cy.createRandomUser() | ||||
.then((user) => { | |||||
.then((_user) => { | |||||
user = _user | |||||
uploadThreeVersions(user, randomFileName) | uploadThreeVersions(user, randomFileName) | ||||
cy.login(user) | cy.login(user) | ||||
cy.visit('/apps/files') | cy.visit('/apps/files') | ||||
}) | }) | ||||
}) | }) | ||||
it('Download versions and assert there content', () => { | |||||
it('Download versions and assert their content', () => { | |||||
assertVersionContent(randomFileName, 0, 'v3') | assertVersionContent(randomFileName, 0, 'v3') | ||||
assertVersionContent(randomFileName, 1, 'v2') | assertVersionContent(randomFileName, 1, 'v2') | ||||
assertVersionContent(randomFileName, 2, 'v1') | 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) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | }) |
* | * | ||||
*/ | */ | ||||
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', () => { | describe('Versions naming', () => { | ||||
let randomFileName = '' | let randomFileName = '' | ||||
let user: User | |||||
before(() => { | before(() => { | ||||
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | ||||
cy.createRandomUser() | cy.createRandomUser() | ||||
.then((user) => { | |||||
.then((_user) => { | |||||
user = _user | |||||
uploadThreeVersions(user, randomFileName) | uploadThreeVersions(user, randomFileName) | ||||
cy.login(user) | cy.login(user) | ||||
cy.visit('/apps/files') | cy.visit('/apps/files') | ||||
}) | }) | ||||
}) | }) | ||||
it('Names the initial version as v1', () => { | |||||
it('Names the versions', () => { | |||||
nameVersion(2, 'v1') | nameVersion(2, 'v1') | ||||
cy.get('#tab-version_vue').within(() => { | 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('v1') | ||||
cy.get('[data-files-versions-version]').eq(2).contains('Initial version').should('not.exist') | cy.get('[data-files-versions-version]').eq(2).contains('Initial version').should('not.exist') | ||||
}) | }) | ||||
}) | |||||
it('Names the second version as v2', () => { | |||||
nameVersion(1, 'v2') | nameVersion(1, 'v2') | ||||
cy.get('#tab-version_vue').within(() => { | cy.get('#tab-version_vue').within(() => { | ||||
cy.get('[data-files-versions-version]').eq(1).contains('v2') | cy.get('[data-files-versions-version]').eq(1).contains('v2') | ||||
}) | }) | ||||
}) | |||||
it('Names the current version as v3', () => { | |||||
nameVersion(0, 'v3') | nameVersion(0, 'v3') | ||||
cy.get('#tab-version_vue').within(() => { | cy.get('#tab-version_vue').within(() => { | ||||
cy.get('[data-files-versions-version]').eq(0).contains('v3 (Current version)') | 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) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | }) |
* | * | ||||
*/ | */ | ||||
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', () => { | describe('Versions restoration', () => { | ||||
let randomFileName = '' | let randomFileName = '' | ||||
let user: User | |||||
before(() => { | before(() => { | ||||
randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' | ||||
cy.createRandomUser() | cy.createRandomUser() | ||||
.then((user) => { | |||||
.then((_user) => { | |||||
user = _user | |||||
uploadThreeVersions(user, randomFileName) | uploadThreeVersions(user, randomFileName) | ||||
cy.login(user) | cy.login(user) | ||||
cy.visit('/apps/files') | cy.visit('/apps/files') | ||||
}) | }) | ||||
}) | }) | ||||
it('Current version does not have restore action', () => { | |||||
doesNotHaveAction(0, 'restore') | |||||
}) | |||||
it('Restores initial version', () => { | it('Restores initial version', () => { | ||||
restoreVersion(2) | restoreVersion(2) | ||||
cy.get('#tab-version_vue').within(() => { | cy.get('#tab-version_vue').within(() => { | ||||
cy.get('[data-files-versions-version]').should('have.length', 3) | 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(0).contains('Current version') | ||||
assertVersionContent(randomFileName, 1, 'v3') | assertVersionContent(randomFileName, 1, 'v3') | ||||
assertVersionContent(randomFileName, 2, 'v2') | 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) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | |||||
}) | }) |