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))
+ }
+ })
}