diff --git a/cypress.config.ts b/cypress.config.ts index 69b4ba01b..0617017d4 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -45,6 +45,19 @@ export default defineConfig({ } }) + // This allows to store global data (e.g. the name of a snapshot) + // because Cypress.env() and other options are local to the current spec file. + const data = {} + on('task', { + setVariable({ key, value }) { + data[key] = value + return null + }, + getVariable({ key }) { + return data[key] ?? null + }, + }) + // Before the browser launches // starting Nextcloud testing container const ip = await startNextcloud(process.env.BRANCH) diff --git a/cypress/e2e/files_versions/filesVersionsUtils.ts b/cypress/e2e/files_versions/filesVersionsUtils.ts index c99d1f063..57fc9980f 100644 --- a/cypress/e2e/files_versions/filesVersionsUtils.ts +++ b/cypress/e2e/files_versions/filesVersionsUtils.ts @@ -22,7 +22,56 @@ */ import type { User } from '@nextcloud/cypress' -import path from 'path' +import { addUserToGroup, createGroup, createGroupFolder, PERMISSION_DELETE, PERMISSION_READ, PERMISSION_WRITE } from '../groupfoldersUtils' +import { navigateToFolder } from '../files/filesUtils' + +type SetupInfo = { + dataSnapshot: string + dbSnapshot: string + groupName: string + groupFolderName: string + fileName: string + filePath: string + user: User +} + +export function setupFilesVersions(): Cypress.Chainable { + return cy.task('getVariable', { key: 'files-versions-data' }) + .then((_setupInfo) => { + const setupInfo = _setupInfo as SetupInfo || {} + + if (setupInfo.dataSnapshot && setupInfo.dbSnapshot) { + cy.restoreDB(setupInfo.dbSnapshot) + cy.restoreData(setupInfo.dataSnapshot) + } else { + setupInfo.groupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + setupInfo.groupFolderName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + setupInfo.fileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' + setupInfo.filePath = `${setupInfo.groupFolderName}/${setupInfo.fileName}` + + cy.createRandomUser().then(_user => { setupInfo.user = _user }) + createGroup(setupInfo.groupName) + + cy.then(() => { + addUserToGroup(setupInfo.groupName, setupInfo.user.userId) + createGroupFolder(setupInfo.groupFolderName, setupInfo.groupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE]) + + uploadThreeVersions(setupInfo.user, setupInfo.filePath) + }) + .then(() => cy.backupDB().then((value) => { setupInfo.dbSnapshot = value })) + .then(() => cy.backupData([setupInfo.user.userId]).then((value) => { setupInfo.dataSnapshot = value })) + .then(() => cy.task('setVariable', { key: 'files-versions-data', value: setupInfo })) + } + + return cy.then(() => { + cy.login(setupInfo.user) + cy.visit('/apps/files') + navigateToFolder(setupInfo.groupFolderName) + openVersionsPanel(setupInfo.filePath) + return cy.wrap(setupInfo) + }) + }) +} export const uploadThreeVersions = (user: User, fileName: string) => { // A new version will not be created if the changes occur diff --git a/cypress/e2e/files_versions/version_creation.cy.ts b/cypress/e2e/files_versions/version_creation.cy.ts index 03282ae59..36b23326e 100644 --- a/cypress/e2e/files_versions/version_creation.cy.ts +++ b/cypress/e2e/files_versions/version_creation.cy.ts @@ -20,39 +20,11 @@ * */ -import type { User } from '@nextcloud/cypress' - -import { PERMISSION_DELETE, PERMISSION_READ, PERMISSION_WRITE, addUserToGroup, createGroup, createGroupFolder } from '../groupfoldersUtils' -import { openVersionsPanel, uploadThreeVersions } from './filesVersionsUtils' -import { navigateToFolder } from '../files/filesUtils' +import { setupFilesVersions } from './filesVersionsUtils' describe('Versions creation', () => { - let randomGroupName: string - let randomGroupFolderName: string - let randomFileName: string - let randomFilePath: string - let user1: User - - before(() => { - randomGroupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomGroupFolderName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' - randomFilePath = `${randomGroupFolderName}/${randomFileName}` - - cy.createRandomUser().then(_user => { user1 = _user }) - createGroup(randomGroupName) - - cy.then(() => { - addUserToGroup(randomGroupName, user1.userId) - createGroupFolder(randomGroupFolderName, randomGroupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE]) - - uploadThreeVersions(user1, randomFilePath) - cy.login(user1) - }) - - cy.visit('/apps/files') - navigateToFolder(randomGroupFolderName) - openVersionsPanel(randomFilePath) + beforeEach(() => { + setupFilesVersions() }) it('Opens the versions panel and sees the versions', () => { diff --git a/cypress/e2e/files_versions/version_deletion.cy.ts b/cypress/e2e/files_versions/version_deletion.cy.ts index 9c7804d9c..430e997cd 100644 --- a/cypress/e2e/files_versions/version_deletion.cy.ts +++ b/cypress/e2e/files_versions/version_deletion.cy.ts @@ -20,39 +20,11 @@ * */ -import type { User } from '@nextcloud/cypress' - -import { openVersionsPanel, uploadThreeVersions, deleteVersion } from './filesVersionsUtils' -import { navigateToFolder } from '../files/filesUtils' -import { PERMISSION_DELETE, PERMISSION_READ, PERMISSION_WRITE, addUserToGroup, createGroup, createGroupFolder } from '../groupfoldersUtils' +import { deleteVersion, setupFilesVersions } from './filesVersionsUtils' describe('Versions restoration', () => { - let randomGroupName: string - let randomGroupFolderName: string - let randomFileName: string - let randomFilePath: string - let user1: User - - before(() => { - randomGroupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomGroupFolderName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' - randomFilePath = `${randomGroupFolderName}/${randomFileName}` - - cy.createRandomUser().then(_user => { user1 = _user }) - createGroup(randomGroupName) - - cy.then(() => { - addUserToGroup(randomGroupName, user1.userId) - createGroupFolder(randomGroupFolderName, randomGroupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE]) - - uploadThreeVersions(user1, randomFilePath) - cy.login(user1) - }) - - cy.visit('/apps/files') - navigateToFolder(randomGroupFolderName) - openVersionsPanel(randomFilePath) + beforeEach(() => { + setupFilesVersions() }) it('Delete initial version', () => { diff --git a/cypress/e2e/files_versions/version_download.cy.ts b/cypress/e2e/files_versions/version_download.cy.ts index 7bc48f501..24af66815 100644 --- a/cypress/e2e/files_versions/version_download.cy.ts +++ b/cypress/e2e/files_versions/version_download.cy.ts @@ -20,39 +20,11 @@ * */ -import type { User } from '@nextcloud/cypress' - -import { assertVersionContent, openVersionsPanel, uploadThreeVersions } from './filesVersionsUtils' -import { PERMISSION_DELETE, PERMISSION_READ, PERMISSION_WRITE, addUserToGroup, createGroup, createGroupFolder } from '../groupfoldersUtils' -import { navigateToFolder } from '../files/filesUtils' +import { assertVersionContent, setupFilesVersions } from './filesVersionsUtils' describe('Versions download', () => { - let randomGroupName: string - let randomGroupFolderName: string - let randomFileName: string - let randomFilePath: string - let user1: User - - before(() => { - randomGroupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomGroupFolderName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' - randomFilePath = `${randomGroupFolderName}/${randomFileName}` - - cy.createRandomUser().then(_user => { user1 = _user }) - createGroup(randomGroupName) - - cy.then(() => { - addUserToGroup(randomGroupName, user1.userId) - createGroupFolder(randomGroupFolderName, randomGroupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE]) - - uploadThreeVersions(user1, randomFilePath) - cy.login(user1) - }) - - cy.visit('/apps/files') - navigateToFolder(randomGroupFolderName) - openVersionsPanel(randomFilePath) + beforeEach(() => { + setupFilesVersions() }) it('Download versions and assert their content', () => { diff --git a/cypress/e2e/files_versions/version_expiration.cy.ts b/cypress/e2e/files_versions/version_expiration.cy.ts new file mode 100644 index 000000000..261f2e2bd --- /dev/null +++ b/cypress/e2e/files_versions/version_expiration.cy.ts @@ -0,0 +1,52 @@ +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { closeSidebar } from '../files/filesUtils' +import { assertVersionContent, nameVersion, openVersionsPanel, setupFilesVersions } from './filesVersionsUtils' + +describe('Versions expiration', () => { + let randomFilePath: string + + beforeEach(() => { + setupFilesVersions() + .then(({ filePath }) => { + randomFilePath = filePath + }) + }) + + it('Expire all versions', () => { + cy.runOccCommand('config:system:set versions_retention_obligation --value "0, 0"') + cy.runOccCommand('groupfolders:expire') + cy.runOccCommand('config:system:set versions_retention_obligation --value auto') + closeSidebar() + openVersionsPanel(randomFilePath) + + cy.get('#tab-version_vue').within(() => { + cy.get('[data-files-versions-version]').should('have.length', 1) + cy.get('[data-files-versions-version]').eq(0).contains('Current version') + }) + + assertVersionContent(0, 'v3') + }) + + it('Expire versions v2', () => { + nameVersion(2, 'v1') + + cy.runOccCommand('config:system:set versions_retention_obligation --value "0, 0"') + cy.runOccCommand('groupfolders:expire') + cy.runOccCommand('config:system:set versions_retention_obligation --value auto') + closeSidebar() + openVersionsPanel(randomFilePath) + + cy.get('#tab-version_vue').within(() => { + cy.get('[data-files-versions-version]').should('have.length', 2) + cy.get('[data-files-versions-version]').eq(0).contains('Current version') + cy.get('[data-files-versions-version]').eq(1).contains('v1') + }) + + assertVersionContent(0, 'v3') + assertVersionContent(1, 'v1') + }) +}) diff --git a/cypress/e2e/files_versions/version_naming.cy.ts b/cypress/e2e/files_versions/version_naming.cy.ts index fec1a154a..328f1d434 100644 --- a/cypress/e2e/files_versions/version_naming.cy.ts +++ b/cypress/e2e/files_versions/version_naming.cy.ts @@ -20,39 +20,11 @@ * */ -import type { User } from '@nextcloud/cypress' - -import { nameVersion, openVersionsPanel, uploadThreeVersions } from './filesVersionsUtils' -import { PERMISSION_DELETE, PERMISSION_READ, PERMISSION_WRITE, addUserToGroup, createGroup, createGroupFolder } from '../groupfoldersUtils' -import { navigateToFolder } from '../files/filesUtils' +import { nameVersion, setupFilesVersions } from './filesVersionsUtils' describe('Versions naming', () => { - let randomGroupName: string - let randomGroupFolderName: string - let randomFileName: string - let randomFilePath: string - let user1: User - - before(() => { - randomGroupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomGroupFolderName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' - randomFilePath = `${randomGroupFolderName}/${randomFileName}` - - cy.createRandomUser().then(_user => { user1 = _user }) - createGroup(randomGroupName) - - cy.then(() => { - addUserToGroup(randomGroupName, user1.userId) - createGroupFolder(randomGroupFolderName, randomGroupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE]) - - uploadThreeVersions(user1, randomFilePath) - cy.login(user1) - }) - - cy.visit('/apps/files') - navigateToFolder(randomGroupFolderName) - openVersionsPanel(randomFilePath) + beforeEach(() => { + setupFilesVersions() }) it('Names the versions', () => { diff --git a/cypress/e2e/files_versions/version_restoration.cy.ts b/cypress/e2e/files_versions/version_restoration.cy.ts index 2ed806cab..8cf05496b 100644 --- a/cypress/e2e/files_versions/version_restoration.cy.ts +++ b/cypress/e2e/files_versions/version_restoration.cy.ts @@ -20,39 +20,11 @@ * */ -import type { User } from '@nextcloud/cypress' - -import { assertVersionContent, doesNotHaveAction, openVersionsPanel, restoreVersion, uploadThreeVersions } from './filesVersionsUtils' -import { PERMISSION_DELETE, PERMISSION_READ, PERMISSION_WRITE, addUserToGroup, createGroup, createGroupFolder } from '../groupfoldersUtils' -import { navigateToFolder } from '../files/filesUtils' +import { assertVersionContent, doesNotHaveAction, restoreVersion, setupFilesVersions } from './filesVersionsUtils' describe('Versions restoration', () => { - let randomGroupName: string - let randomGroupFolderName: string - let randomFileName: string - let randomFilePath: string - let user1: User - before(() => { - randomGroupName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomGroupFolderName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) - randomFileName = Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, 10) + '.txt' - randomFilePath = `${randomGroupFolderName}/${randomFileName}` - - cy.createRandomUser().then(_user => { user1 = _user }) - createGroup(randomGroupName) - - cy.then(() => { - addUserToGroup(randomGroupName, user1.userId) - createGroupFolder(randomGroupFolderName, randomGroupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE]) - - uploadThreeVersions(user1, randomFilePath) - cy.login(user1) - }) - - cy.visit('/apps/files') - navigateToFolder(randomGroupFolderName) - openVersionsPanel(randomFilePath) + setupFilesVersions() }) it('Current version does not have restore action', () => { @@ -69,7 +41,7 @@ describe('Versions restoration', () => { }) }) - it('Downloads versions and assert there content', () => { + it('Downloads versions and assert their content', () => { assertVersionContent(0, 'v1') assertVersionContent(1, 'v3') assertVersionContent(2, 'v2') diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 19c2f6840..6fd8a701b 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -38,6 +38,21 @@ declare global { * Run an occ command in the docker container. */ runOccCommand(command: string, options?: Partial): Cypress.Chainable, + + /** + * Create a snapshot of the current database + */ + backupDB(): Cypress.Chainable, + + /** + * Restore a snapshot of the database + * Default is the post-setup state + */ + restoreDB(snapshot?: string): Cypress.Chainable + + backupData(users?: string[]): Cypress.Chainable + + restoreData(snapshot?: string): Cypress.Chainable } } } @@ -45,11 +60,10 @@ 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 () => { + .then({ timeout: 8000 }, async () => { try { const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}` const filePath = target.split('/').map(encodeURIComponent).join('/') @@ -98,7 +112,7 @@ Cypress.Commands.add('uploadFile', (user, fixture = 'image.jpg', mimeType = 'ima */ Cypress.Commands.add('uploadContent', (user, blob, mimeType, target) => { cy.clearCookies() - .then({timeout:8000}, async () => { + .then({ timeout: 8000 }, async () => { const fileName = basename(target) // Process paths @@ -131,3 +145,29 @@ Cypress.Commands.add('runOccCommand', (command: string, options?: Partial `-e '${name}=${value}'`).join(' ') return cy.exec(`docker exec --user www-data ${env} nextcloud-cypress-tests_groupfolders php ./occ ${command}`, options) }) + +Cypress.Commands.add('backupDB', (): Cypress.Chainable => { + const randomString = Math.random().toString(36).substring(7) + cy.exec(`docker exec --user www-data nextcloud-cypress-tests_groupfolders cp /var/www/html/data/owncloud.db /var/www/html/data/owncloud.db-${randomString}`) + cy.log(`Created snapshot ${randomString}`) + return cy.wrap(randomString) +}) + +Cypress.Commands.add('restoreDB', (snapshot: string = 'init') => { + cy.exec(`docker exec --user www-data nextcloud-cypress-tests_groupfolders cp /var/www/html/data/owncloud.db-${snapshot} /var/www/html/data/owncloud.db`) + cy.log(`Restored snapshot ${snapshot}`) +}) + +Cypress.Commands.add('backupData', () => { + const snapshot = Math.random().toString(36).substring(7) + cy.exec(`docker exec --user www-data rm /var/www/html/data/data-${snapshot}.tar`, { failOnNonZeroExit: false }) + cy.exec(`docker exec --user www-data --workdir /var/www/html/data nextcloud-cypress-tests_groupfolders tar cf /var/www/html/data/data-${snapshot}.tar .`) + return cy.wrap(snapshot as string) +}) + +Cypress.Commands.add('restoreData', (snapshot?: string) => { + snapshot = snapshot ?? 'init' + snapshot.replaceAll('\\', '').replaceAll('"', '\\"') + cy.exec(`docker exec --user www-data --workdir /var/www/html/data nextcloud-cypress-tests_groupfolders rm -vfr $(tar --exclude='*/*' -tf '/var/www/html/data/data-${snapshot}.tar')`) + cy.exec(`docker exec --user www-data --workdir /var/www/html/data nextcloud-cypress-tests_groupfolders tar -xf '/var/www/html/data/data-${snapshot}.tar'`) +}) diff --git a/lib/Versions/ExpireManager.php b/lib/Versions/ExpireManager.php index e4dec1ce9..d6b8fe643 100644 --- a/lib/Versions/ExpireManager.php +++ b/lib/Versions/ExpireManager.php @@ -24,6 +24,7 @@ namespace OCA\GroupFolders\Versions; use OCA\Files_Versions\Expiration; +use OCA\Files_Versions\Versions\IMetadataVersion; use OCA\Files_Versions\Versions\IVersion; /** @@ -132,6 +133,16 @@ public function getExpiredVersion(array $versions, int $time, bool $quotaExceede }); $expired = array_filter($versionsLeft, function (IVersion $version) use ($quotaExceeded) { + // Do not expire current version. + if ($version->getTimestamp() === $version->getSourceFile()->getMtime()) { + return false; + } + + // Do not expire versions with a label. + if ($version instanceof IMetadataVersion && $version->getMetadataValue('label') !== null && $version->getMetadataValue('label') !== '') { + return false; + } + return $this->expiration->isExpired($version->getTimestamp(), $quotaExceeded); });