diff --git a/cypress/dockerNode.ts b/cypress/dockerNode.ts index e73ee2ffb..8f000daca 100644 --- a/cypress/dockerNode.ts +++ b/cypress/dockerNode.ts @@ -134,6 +134,7 @@ export const configureNextcloud = async function(branch = 'master') { // Enable the app and give status await runExec(container, ['php', 'occ', 'app:enable', '--force', 'viewer'], true) await runExec(container, ['php', 'occ', 'app:enable', 'groupfolders', '--force'], true) + await runExec(container, ['php', 'occ', 'app:enable', 'files_trashbin', '--force'], true) // await runExec(container, ['php', 'occ', 'app:list'], true) console.log('└─ Nextcloud is now ready to use 🎉') diff --git a/cypress/e2e/groupfolders.cy.ts b/cypress/e2e/groupfolders.cy.ts index 92064e04b..1fed11450 100644 --- a/cypress/e2e/groupfolders.cy.ts +++ b/cypress/e2e/groupfolders.cy.ts @@ -20,10 +20,22 @@ * */ import { + addACLManagerUser, addUserToGroup, createGroup, createGroupFolder, deleteGroupFolder, + deleteFile, + enableACLPermissions, + enterFolder, + enterFolderInTrashbin, + fileOrFolderExists, + fileOrFolderDoesNotExist, + fileOrFolderExistsInTrashbin, + fileOrFolderDoesNotExistInTrashbin, + restoreFile, + setACLPermissions, + PERMISSION_DELETE, PERMISSION_READ, PERMISSION_WRITE, } from './groupfoldersUtils' @@ -32,40 +44,220 @@ import { randHash } from '../utils' import type { User } from '@nextcloud/cypress' -describe('Manage groupfolders', () => { - let user: User +describe('Groupfolders ACLs and trashbin behavior', () => { + let user1: User + let user2: User + let managerUser: User let groupFolderId: string let groupName: string let groupFolderName: string - before(() => { + beforeEach(() => { + if (groupFolderId) { + deleteGroupFolder(groupFolderId) + } groupName = `test_group_${randHash()}` groupFolderName = `test_group_folder_${randHash()}` cy.createRandomUser() .then(_user => { - user = _user - cy.login(user) + user1 = _user + }) + cy.createRandomUser() + .then(_user => { + user2 = _user + }) + cy.createRandomUser() + .then(_user => { + managerUser = _user createGroup(groupName) .then(() => { - addUserToGroup(groupName, user.userId) - createGroupFolder(groupFolderName, groupName, [PERMISSION_READ, PERMISSION_WRITE]) + addUserToGroup(groupName, user1.userId) + addUserToGroup(groupName, user2.userId) + addUserToGroup(groupName, managerUser.userId) + createGroupFolder(groupFolderName, groupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE]) .then(_groupFolderId => { groupFolderId = _groupFolderId - cy.visit('/apps/files') + enableACLPermissions(groupFolderId) + addACLManagerUser(groupFolderId,managerUser.userId) }) }) }) }) - after(() => { - if (groupFolderId) { - deleteGroupFolder(groupFolderId) - } + it('ACL, delete and restore', () => { + // Create two subfolders and two files as manager + cy.login(managerUser) + cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`) + cy.mkdir(managerUser, `/${groupFolderName}/subfolder1/subfolder2`) + cy.uploadContent(managerUser, new Blob(['Content of the file']), 'text/plain', `/${groupFolderName}/subfolder1/file1.txt`) + cy.uploadContent(managerUser, new Blob(['Content of the file']), 'text/plain', `/${groupFolderName}/subfolder1/subfolder2/file2.txt`) + + // Set ACL permissions + setACLPermissions(groupFolderId, '/subfolder1', [`+${PERMISSION_READ}`,`-${PERMISSION_WRITE}`], undefined, user1.userId) + setACLPermissions(groupFolderId, '/subfolder1', [`-${PERMISSION_READ}`], undefined, user2.userId) + + // User1 has access + cy.login(user1) + cy.visit('/apps/files') + enterFolder(groupFolderName) + enterFolder('subfolder1') + fileOrFolderExists('file1.txt') + enterFolder('subfolder2') + fileOrFolderExists('file2.txt') + + // User2 has no access + cy.login(user2) + cy.visit('/apps/files') + enterFolder(groupFolderName) + fileOrFolderDoesNotExist('subfolder1') + + // Delete files + cy.login(managerUser) + cy.visit('/apps/files') + enterFolder(groupFolderName) + enterFolder('subfolder1') + deleteFile('file1.txt') + deleteFile('subfolder2') + + // User1 sees it in trash + cy.login(user1) + cy.visit('/apps/files/trashbin') + fileOrFolderExistsInTrashbin('file1.txt') + enterFolderInTrashbin('subfolder2') + fileOrFolderExists('file2.txt') + + // User2 does not + cy.login(user2) + cy.visit('/apps/files/trashbin') + fileOrFolderDoesNotExistInTrashbin('file1.txt') + fileOrFolderDoesNotExistInTrashbin('subfolder2') + + // Restore files + cy.login(managerUser) + cy.visit('/apps/files/trashbin') + fileOrFolderExistsInTrashbin('file1.txt') + fileOrFolderExistsInTrashbin('subfolder2') + restoreFile('file1.txt') + restoreFile('subfolder2') + + // User1 has access + cy.login(user1) + cy.visit('/apps/files') + enterFolder(groupFolderName) + fileOrFolderExists('subfolder1') + enterFolder('subfolder1') + fileOrFolderExists('file1.txt') + enterFolder('subfolder2') + fileOrFolderExists('file2.txt') + + // User2 has no access + cy.login(user2) + cy.visit('/apps/files') + enterFolder(groupFolderName) + fileOrFolderDoesNotExist('subfolder1') + }) + + it.skip('ACL directly on deleted folder', () => { + // Create a subfolders and a file as manager + cy.login(managerUser) + cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`) + cy.uploadContent(managerUser, new Blob(['Content of the file']), 'text/plain', `/${groupFolderName}/subfolder1/file1.txt`) + + // Set ACL permissions on subfolder + setACLPermissions(groupFolderId, '/subfolder1', [`+${PERMISSION_READ}`,`-${PERMISSION_WRITE}`], undefined, user1.userId) + setACLPermissions(groupFolderId, '/subfolder1', [`-${PERMISSION_READ}`], undefined, user2.userId) + + // Delete subfolder + cy.login(managerUser) + cy.visit('/apps/files') + enterFolder(groupFolderName) + deleteFile('subfolder1') + + // User1 sees it in trash + cy.login(user1) + cy.visit('/apps/files/trashbin') + fileOrFolderExistsInTrashbin('subfolder1') + enterFolderInTrashbin('subfolder1') + fileOrFolderExists('file1.txt') + + // User2 does not + cy.login(user2) + cy.visit('/apps/files/trashbin') + fileOrFolderDoesNotExistInTrashbin('subfolder1') }) - it('Visite the group folder', () => { - return true + it.skip('Delete, rename parent and restore', () => { + // Create a subfolders and a file as manager + cy.login(managerUser) + cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`) + cy.uploadContent(managerUser, new Blob(['Content of the file']), 'text/plain', `/${groupFolderName}/subfolder1/file1.txt`) + + // Delete file + cy.login(managerUser) + cy.visit('/apps/files') + enterFolder(groupFolderName) + enterFolder('subfolder1') + deleteFile('file1.txt') + + // Rename subfolder1 + cy.visit('/apps/files') + enterFolder(groupFolderName) + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="subfolder1"] [data-cy-files-list-row-actions]`).click() + cy.get(`[data-cy-files-list] [data-cy-files-list-row-action="rename"]`).scrollIntoView() + cy.get(`[data-cy-files-list] [data-cy-files-list-row-action="rename"]`).click() + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="subfolder1"] [class="files-list__row-rename"] [class="input-field__input"]`).type('subfolder1renamed{enter}') + fileOrFolderExists('subfolder1renamed') + + // Restore from trash + cy.visit('/apps/files/trashbin') + restoreFile('file1.txt') + + // File should be restored in renamed folder + cy.login(managerUser) + cy.visit('/apps/files') + enterFolder(groupFolderName) + fileOrFolderExists('subfolder1renamed') + fileOrFolderDoesNotExist('file1.txt') + enterFolder('subfolder1renamed') + fileOrFolderExists('file1.txt') + }) + + it.skip('Delete, rename parent and check ACL', () => { + // Create a subfolders and a file as manager + cy.login(managerUser) + cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`) + cy.uploadContent(managerUser, new Blob(['Content of the file']), 'text/plain', `/${groupFolderName}/subfolder1/file1.txt`) + + // Set ACL permissions + setACLPermissions(groupFolderId, '/subfolder1', [`+${PERMISSION_READ}`,`-${PERMISSION_WRITE}`], undefined, user1.userId) + setACLPermissions(groupFolderId, '/subfolder1', [`-${PERMISSION_READ}`], undefined, user2.userId) + + // Delete file + cy.login(managerUser) + cy.visit('/apps/files') + enterFolder(groupFolderName) + enterFolder('subfolder1') + deleteFile('file1.txt') + + // Rename subfolder1 + cy.visit('/apps/files') + enterFolder(groupFolderName) + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="subfolder1"] [data-cy-files-list-row-actions]`).click() + cy.get(`[data-cy-files-list] [data-cy-files-list-row-action="rename"]`).scrollIntoView() + cy.get(`[data-cy-files-list] [data-cy-files-list-row-action="rename"]`).click() + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="subfolder1"] [class="files-list__row-rename"] [class="input-field__input"]`).type('subfolder1renamed{enter}') + fileOrFolderExists('subfolder1renamed') + + // User1 sees it in trash + cy.login(user1) + cy.visit('/apps/files/trashbin') + fileOrFolderExistsInTrashbin('file1.txt') + + // User2 does not + cy.login(user2) + cy.visit('/apps/files/trashbin') + fileOrFolderDoesNotExistInTrashbin('file1.txt') }) }) diff --git a/cypress/e2e/groupfoldersUtils.ts b/cypress/e2e/groupfoldersUtils.ts index bc83a9c91..42cf61295 100644 --- a/cypress/e2e/groupfoldersUtils.ts +++ b/cypress/e2e/groupfoldersUtils.ts @@ -51,8 +51,8 @@ export function disableACLPermissions(groupFolderId: string) { return cy.runOccCommand(`groupfolders:permissions ${groupFolderId} --disable`) } -export function addACLManager(groupFolderId: string, groupOrUserName: string) { - return cy.runOccCommand(`groupfolders:permissions ${groupFolderId} --manage-add ${groupOrUserName}`) +export function addACLManagerUser(groupFolderId: string, userName: string) { + return cy.runOccCommand(`groupfolders:permissions ${groupFolderId} --manage-add --user ${userName}`) } export function removeACLManager(groupFolderId: string, groupOrUserName: string) { @@ -67,9 +67,47 @@ export function setACLPermissions( userId?: string, ) { const target = groupId !== undefined ? `--group ${groupId}` : `--user ${userId}` - return cy.runOccCommand(`groupfolders:permissions ${groupFolderId} ${path} ${aclPermissions} ${target}`) + return cy.runOccCommand(`groupfolders:permissions ${groupFolderId} ${path} ${target} -- ${aclPermissions.join(' ')}`) } export function deleteGroupFolder(groupFolderId: string) { return cy.runOccCommand(`groupfolders:delete ${groupFolderId}`) } + +export function fileOrFolderExists(name: string) { + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${name}"]`).should('be.visible') +} + +export function fileOrFolderDoesNotExist(name: string) { + // Make sure file list is loaded first + cy.get(`[data-cy-files-list-tfoot],[data-cy-files-content-empty]`).should('be.visible') + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${name}"]`).should('not.exist') +} + +export function fileOrFolderExistsInTrashbin(name: string) { + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name^="${name}.d"]`).should('be.visible') +} + +export function fileOrFolderDoesNotExistInTrashbin(name: string) { + // Make sure file list is loaded first + cy.get(`[data-cy-files-list-tfoot],[data-cy-files-content-empty]`).should('be.visible') + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name^="${name}.d"]`).should('not.exist') +} + +export function enterFolder(name: string) { + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${name}"]`).click() +} + +export function enterFolderInTrashbin(name: string) { + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name^="${name}.d"]`).click() +} + +export function deleteFile(name: string) { + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name="${name}"] [data-cy-files-list-row-actions]`).click() + cy.get(`[data-cy-files-list] [data-cy-files-list-row-action="delete"]`).scrollIntoView() + cy.get(`[data-cy-files-list] [data-cy-files-list-row-action="delete"]`).click() +} + +export function restoreFile(name: string) { + cy.get(`[data-cy-files-list] [data-cy-files-list-row-name^="${name}.d"] [data-cy-files-list-row-action="restore"]`).click() +} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 187def7bb..d17922be3 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -45,6 +45,12 @@ declare global { */ uploadContent(user: User, content: Blob, mimeType: string, target: string): Cypress.Chainable, + /** + * Create a new directory + * **Warning**: Using this function will reset the previous session + */ + mkdir(user: User, target: string): Cypress.Chainable, + /** * Run an occ command in the docker container. */ @@ -56,6 +62,30 @@ declare global { const url = (Cypress.config('baseUrl') || '').replace(/\/index.php\/?$/g, '') Cypress.env('baseUrl', url) + +Cypress.Commands.add('mkdir', (user: User, target: string) => { + // eslint-disable-next-line cypress/unsafe-to-chain-command + cy.clearCookies() + .then({timeout:8000}, async () => { + try { + const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}` + const filePath = target.split('/').map(encodeURIComponent).join('/') + const response = await axios({ + url: `${rootPath}${filePath}`, + method: 'MKCOL', + auth: { + username: user.userId, + password: user.password, + }, + }) + cy.log(`Created directory ${target}`, response) + } catch (error) { + cy.log('error', error) + throw new Error('Unable to process fixture') + } + }) +}) + /** * cy.uploadedFile - uploads a file from the fixtures folder * TODO: standardise in @nextcloud/cypress @@ -85,7 +115,7 @@ Cypress.Commands.add('uploadFile', (user, fixture = 'image.jpg', mimeType = 'ima */ Cypress.Commands.add('uploadContent', (user, blob, mimeType, target) => { cy.clearCookies() - .then(async () => { + .then({timeout:8000}, async () => { const fileName = basename(target) // Process paths