diff --git a/cypress/e2e/files_sharing/ShareOptionsType.ts b/cypress/e2e/files_sharing/ShareOptionsType.ts new file mode 100644 index 0000000000000..a6ce69222995d --- /dev/null +++ b/cypress/e2e/files_sharing/ShareOptionsType.ts @@ -0,0 +1,18 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +export type ShareOptions = { + enforcePassword?: boolean + enforceExpirationDate?: boolean + alwaysAskForPassword?: boolean + defaultExpirationDateSet?: boolean +} + +export const defaultShareOptions: ShareOptions = { + enforcePassword: false, + enforceExpirationDate: false, + alwaysAskForPassword: false, + defaultExpirationDateSet: false, +} diff --git a/cypress/e2e/files_sharing/public-share/required-before-create.cy.ts b/cypress/e2e/files_sharing/public-share/required-before-create.cy.ts new file mode 100644 index 0000000000000..d0222f087e1d4 --- /dev/null +++ b/cypress/e2e/files_sharing/public-share/required-before-create.cy.ts @@ -0,0 +1,187 @@ +/*! + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { ShareContext } from './setup-public-share.ts' +import type { ShareOptions } from '../ShareOptionsType.ts' +import { setupData, createShare } from './setup-public-share.ts' + +describe('files_sharing: Before create checks', () => { + + let shareContext: ShareContext + + before(() => { + // Setup data for the shared folder once before all tests + cy.createRandomUser().then((randomUser) => { + shareContext = { + user: randomUser, + } + }) + }) + + afterEach(() => { + cy.runOccCommand('config:app:delete core shareapi_enable_link_password_by_default') + cy.runOccCommand('config:app:delete core shareapi_enforce_links_password') + cy.runOccCommand('config:app:delete core shareapi_default_expire_date') + cy.runOccCommand('config:app:delete core shareapi_enforce_expire_date') + cy.runOccCommand('config:app:delete core shareapi_expire_after_n_days') + }) + + const applyShareOptions = (options: ShareOptions): void => { + cy.runOccCommand(`config:app:set --value ${options.alwaysAskForPassword ? 'yes' : 'no'} core shareapi_enable_link_password_by_default`) + cy.runOccCommand(`config:app:set --value ${options.enforcePassword ? 'yes' : 'no'} core shareapi_enforce_links_password`) + cy.runOccCommand(`config:app:set --value ${options.enforceExpirationDate ? 'yes' : 'no'} core shareapi_enforce_expire_date`) + cy.runOccCommand(`config:app:set --value ${options.defaultExpirationDateSet ? 'yes' : 'no'} core shareapi_default_expire_date`) + if (options.defaultExpirationDateSet) { + cy.runOccCommand('config:app:set --value 2 core shareapi_expire_after_n_days') + } + } + + it('Checks if user can create share when both password and expiration date are enforced', () => { + const shareOptions : ShareOptions = { + alwaysAskForPassword: true, + enforcePassword: true, + enforceExpirationDate: true, + defaultExpirationDateSet: true, + } + applyShareOptions(shareOptions) + const shareName = 'passwordAndExpireEnforced' + setupData(shareContext, shareName) + createShare(shareContext, shareName, shareOptions).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share when password is enforced and expiration date has a default set', () => { + const shareOptions : ShareOptions = { + alwaysAskForPassword: true, + enforcePassword: true, + defaultExpirationDateSet: true, + } + applyShareOptions(shareOptions) + const shareName = 'passwordEnforcedDefaultExpire' + setupData(shareContext, shareName) + createShare(shareContext, shareName).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share when password is optionally requested and expiration date is enforced', () => { + const shareOptions : ShareOptions = { + alwaysAskForPassword: true, + enforceExpirationDate: true, + } + applyShareOptions(shareOptions) + const shareName = 'defaultPasswordExpireEnforced' + setupData(shareContext, shareName) + createShare(shareContext, shareName, shareOptions).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share when password is optionally requested and expiration date have defaults set', () => { + const shareOptions : ShareOptions = { + alwaysAskForPassword: true, + defaultExpirationDateSet: true, + } + applyShareOptions(shareOptions) + const shareName = 'defaultPasswordAndExpire' + setupData(shareContext, shareName) + createShare(shareContext, shareName).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share with password enforced and expiration date set but not enforced', () => { + const shareOptions : ShareOptions = { + alwaysAskForPassword: true, + enforcePassword: true, + defaultExpirationDateSet: true, + enforceExpirationDate: false, + } + applyShareOptions(shareOptions) + const shareName = 'passwordEnforcedExpireSetNotEnforced' + setupData(shareContext, shareName) + createShare(shareContext, shareName, shareOptions).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share with both password and expiration date not enforced, but defaults set', () => { + const shareOptions : ShareOptions = { + enforcePassword: false, + enforceExpirationDate: false, + defaultExpirationDateSet: true, + alwaysAskForPassword: true, + } + const shareName = 'defaultPasswordExpireNotEnforced' + setupData(shareContext, shareName) + createShare(shareContext, shareName, shareOptions).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share with password not enforced but expiration date enforced', () => { + const shareOptions : ShareOptions = { + alwaysAskForPassword: true, + enforcePassword: false, + defaultExpirationDateSet: true, + enforceExpirationDate: true, + } + applyShareOptions(shareOptions) + const shareName = 'noPasswordExpireEnforced' + setupData(shareContext, shareName) + createShare(shareContext, shareName, shareOptions).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share with password not enforced and expiration date has a default set', () => { + const shareOptions : ShareOptions = { + enforcePassword: false, + defaultExpirationDateSet: true, + } + applyShareOptions(shareOptions) + const shareName = 'defaultExpireNoPasswordEnforced' + setupData(shareContext, shareName) + createShare(shareContext, shareName, shareOptions).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share with expiration date set and password not enforced', () => { + const shareOptions : ShareOptions = { + alwaysAskForPassword: true, + enforcePassword: false, + defaultExpirationDateSet: true, + } + applyShareOptions(shareOptions) + + const shareName = 'noPasswordExpireDefault' + setupData(shareContext, shareName) + createShare(shareContext, shareName, shareOptions).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + + it('Checks if user can create share with password not enforced, expiration date not enforced, and no defaults set', () => { + applyShareOptions() + const shareName = 'noPasswordNoExpireNoDefaults' + setupData(shareContext, shareName) + createShare(shareContext, shareName, null).then((shareUrl) => { + shareContext.url = shareUrl + cy.log(`Created share with URL: ${shareUrl}`) + }) + }) + +}) diff --git a/cypress/e2e/files_sharing/public-share/setup-public-share.ts b/cypress/e2e/files_sharing/public-share/setup-public-share.ts index d56c7dd188551..8f3bc303208e4 100644 --- a/cypress/e2e/files_sharing/public-share/setup-public-share.ts +++ b/cypress/e2e/files_sharing/public-share/setup-public-share.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import type { User } from '@nextcloud/cypress' +import type { ShareOptions } from '../ShareOptionsType.ts' import { openSharingPanel } from '../FilesSharingUtils.ts' export interface ShareContext { @@ -25,37 +26,81 @@ const defaultShareContext: ShareContext = { */ export function getShareUrl(context: ShareContext = defaultShareContext): string { if (!context.url) { - throw new Error('Share context is not properly initialized with a URL.') + throw new Error('You need to setup the share first!') } return context.url } /** * Setup the available data - * @param context The current share context + * @param user The current share context * @param shareName The name of the shared folder */ -export function setupData(context: ShareContext, shareName: string): void { - cy.mkdir(context.user, `/${shareName}`) - cy.mkdir(context.user, `/${shareName}/subfolder`) - cy.uploadContent(context.user, new Blob(['foo']), 'text/plain', `/${shareName}/foo.txt`) - cy.uploadContent(context.user, new Blob(['bar']), 'text/plain', `/${shareName}/subfolder/bar.txt`) +export function setupData(user: User, shareName: string): void { + cy.mkdir(user, `/${shareName}`) + cy.mkdir(user, `/${shareName}/subfolder`) + cy.uploadContent(user, new Blob(['foo']), 'text/plain', `/${shareName}/foo.txt`) + cy.uploadContent(user, new Blob(['bar']), 'text/plain', `/${shareName}/subfolder/bar.txt`) +} + +/** + * Check the password state based on enforcement and default presence. + * + * @param enforced Whether the password is enforced. + * @param alwaysAskForPassword Wether the password should always be asked for. + */ +function checkPasswordState(enforced: boolean, alwaysAskForPassword: boolean) { + if (enforced) { + cy.contains('Password protection (enforced)').should('exist') + cy.contains('Enter a password') + .should('exist') + .and('not.be.disabled') + } else if (alwaysAskForPassword) { + cy.contains('Password protection').should('exist') + cy.contains('Enter a password') + .should('exist') + .and('not.be.disabled') + } +} + +/** + * Check the expiration date state based on enforcement and default presence. + * + * @param enforced Whether the expiration date is enforced. + * @param hasDefault Whether a default expiration date is set. + */ +function checkExpirationDateState(enforced: boolean, hasDefault: boolean) { + if (enforced) { + cy.contains('Enable link expiration (enforced)').should('exist') + } else if (hasDefault) { + cy.contains('Enable link expiration').should('exist') + } + cy.contains('Enter expiration date') + .should('exist') + .and('not.be.disabled') } /** * Create a public link share * @param context The current share context * @param shareName The name of the shared folder + * @param options The share options */ -export function createShare(context: ShareContext, shareName: string) { +export function createShare(context: ShareContext, shareName: string, options: ShareOptions | null = null) { cy.login(context.user) - cy.visit('/apps/files') // Open the files app - openSharingPanel(shareName) // Open the sharing sidebar + cy.visit('/apps/files') + openSharingPanel(shareName) cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createShare') cy.findByRole('button', { name: 'Create a new share link' }).click() + // Conduct optional checks based on the provided options + if (options) { + cy.get('.sharing-entry__actions').should('be.visible') // Wait for the dialog to open + checkPasswordState(options.enforcePassword ?? false, options.alwaysAskForPassword ?? false) + checkExpirationDateState(options.enforceExpirationDate ?? false, options.defaultExpirationDateSet ?? false) + cy.findByRole('button', { name: 'Create share' }).click() + } - // Extract the share link return cy.wait('@createShare') .should(({ response }) => { expect(response?.statusCode).to.eq(200) @@ -94,34 +139,34 @@ function adjustSharePermission(): void { */ export function setupPublicShare(shareName = 'shared'): Cypress.Chainable { - return cy.task('getVariable', { key: 'public-share-data' }).then((data) => { - // Leave dataSnapshot part unchanged - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { dataSnapshot, shareUrl } = data as any || {} - if (dataSnapshot) { - cy.restoreState(dataSnapshot) - defaultShareContext.url = shareUrl - return cy.wrap(shareUrl as string) - } else { - const shareData: Record = {} - return cy.createRandomUser() - .then((user) => { - defaultShareContext.user = user - }) - .then(() => setupData(defaultShareContext, shareName)) - .then(() => createShare(defaultShareContext, shareName)) - .then((url) => { - shareData.shareUrl = url - }) - .then(() => adjustSharePermission()) - .then(() => - cy.saveState().then((snapshot) => { - shareData.dataSnapshot = snapshot - }), - ) - .then(() => cy.task('setVariable', { key: 'public-share-data', value: shareData })) - .then(() => cy.log(`Public share setup, URL: ${shareData.shareUrl}`)) - .then(() => cy.wrap(defaultShareContext.url)) - } - }) + return cy.task('getVariable', { key: 'public-share-data' }) + .then((data) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { dataSnapshot, shareUrl } = data as any || {} + if (dataSnapshot) { + cy.restoreState(dataSnapshot) + defaultShareContext.url = shareUrl + return cy.wrap(shareUrl as string) + } else { + const shareData: Record = {} + return cy.createRandomUser() + .then((user) => { + defaultShareContext.user = user + }) + .then(() => setupData(defaultShareContext.user, shareName)) + .then(() => createShare(defaultShareContext, shareName)) + .then((url) => { + shareData.shareUrl = url + }) + .then(() => adjustSharePermission()) + .then(() => + cy.saveState().then((snapshot) => { + shareData.dataSnapshot = snapshot + }), + ) + .then(() => cy.task('setVariable', { key: 'public-share-data', value: shareData })) + .then(() => cy.log(`Public share setup, URL: ${shareData.shareUrl}`)) + .then(() => cy.wrap(defaultShareContext.url)) + } + }) }