Skip to content

Commit 4699500

Browse files
authored
Merge pull request #53772 from nextcloud/fix/PublicShareUtils
2 parents 1411f09 + a55b0cc commit 4699500

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
import type { User } from '@nextcloud/cypress'
6+
import type { ShareOptions } from '../ShareOptionsType.ts'
7+
import { openSharingPanel } from '../FilesSharingUtils.ts'
8+
9+
export interface ShareContext {
10+
user: User
11+
url?: string
12+
}
13+
14+
const defaultShareContext: ShareContext = {
15+
user: {} as User,
16+
url: undefined,
17+
}
18+
19+
/**
20+
* Retrieves the URL of the share.
21+
* Throws an error if the share context is not initialized properly.
22+
*
23+
* @param context The current share context (defaults to `defaultShareContext` if not provided).
24+
* @return The share URL.
25+
* @throws Error if the share context has no URL.
26+
*/
27+
export function getShareUrl(context: ShareContext = defaultShareContext): string {
28+
if (!context.url) {
29+
throw new Error('You need to setup the share first!')
30+
}
31+
return context.url
32+
}
33+
34+
/**
35+
* Setup the available data
36+
* @param user The current share context
37+
* @param shareName The name of the shared folder
38+
*/
39+
export function setupData(user: User, shareName: string): void {
40+
cy.mkdir(user, `/${shareName}`)
41+
cy.mkdir(user, `/${shareName}/subfolder`)
42+
cy.uploadContent(user, new Blob(['<content>foo</content>']), 'text/plain', `/${shareName}/foo.txt`)
43+
cy.uploadContent(user, new Blob(['<content>bar</content>']), 'text/plain', `/${shareName}/subfolder/bar.txt`)
44+
}
45+
46+
/**
47+
* Check the password state based on enforcement and default presence.
48+
*
49+
* @param enforced Whether the password is enforced.
50+
* @param alwaysAskForPassword Wether the password should always be asked for.
51+
*/
52+
function checkPasswordState(enforced: boolean, alwaysAskForPassword: boolean) {
53+
if (enforced) {
54+
cy.contains('Password protection (enforced)').should('exist')
55+
} else if (alwaysAskForPassword) {
56+
cy.contains('Password protection').should('exist')
57+
}
58+
cy.contains('Enter a password')
59+
.should('exist')
60+
.and('not.be.disabled')
61+
}
62+
63+
/**
64+
* Check the expiration date state based on enforcement and default presence.
65+
*
66+
* @param enforced Whether the expiration date is enforced.
67+
* @param hasDefault Whether a default expiration date is set.
68+
*/
69+
function checkExpirationDateState(enforced: boolean, hasDefault: boolean) {
70+
if (enforced) {
71+
cy.contains('Enable link expiration (enforced)').should('exist')
72+
} else if (hasDefault) {
73+
cy.contains('Enable link expiration').should('exist')
74+
}
75+
cy.contains('Enter expiration date')
76+
.should('exist')
77+
.and('not.be.disabled')
78+
cy.get('input[data-cy-files-sharing-expiration-date-input]').should('exist')
79+
cy.get('input[data-cy-files-sharing-expiration-date-input]')
80+
.invoke('val')
81+
.then((val) => {
82+
// eslint-disable-next-line no-unused-expressions
83+
expect(val).to.not.be.undefined
84+
85+
const inputDate = new Date(typeof val === 'number' ? val : String(val))
86+
const expectedDate = new Date()
87+
expectedDate.setDate(expectedDate.getDate() + 2)
88+
expect(inputDate.toDateString()).to.eq(expectedDate.toDateString())
89+
})
90+
91+
}
92+
93+
/**
94+
* Create a public link share
95+
* @param context The current share context
96+
* @param shareName The name of the shared folder
97+
* @param options The share options
98+
*/
99+
export function createLinkShare(context: ShareContext, shareName: string, options: ShareOptions | null = null): Cypress.Chainable<string> {
100+
cy.login(context.user)
101+
cy.visit('/apps/files')
102+
openSharingPanel(shareName)
103+
104+
cy.intercept('POST', '**/ocs/v2.php/apps/files_sharing/api/v1/shares').as('createLinkShare')
105+
cy.findByRole('button', { name: 'Create a new share link' }).click()
106+
// Conduct optional checks based on the provided options
107+
if (options) {
108+
cy.get('.sharing-entry__actions').should('be.visible') // Wait for the dialog to open
109+
checkPasswordState(options.enforcePassword ?? false, options.alwaysAskForPassword ?? false)
110+
checkExpirationDateState(options.enforceExpirationDate ?? false, options.defaultExpirationDateSet ?? false)
111+
cy.findByRole('button', { name: 'Create share' }).click()
112+
}
113+
114+
return cy.wait('@createLinkShare')
115+
.should(({ response }) => {
116+
expect(response?.statusCode).to.eq(200)
117+
const url = response?.body?.ocs?.data?.url
118+
expect(url).to.match(/^https?:\/\//)
119+
context.url = url
120+
})
121+
.then(() => cy.wrap(context.url as string))
122+
}
123+
124+
/**
125+
* open link share details for specific index
126+
*
127+
* @param index
128+
*/
129+
export function openLinkShareDetails(index: number) {
130+
cy.findByRole('list', { name: 'Link shares' })
131+
.findAllByRole('listitem')
132+
.eq(index)
133+
.findByRole('button', { name: /Actions/i })
134+
.click()
135+
cy.findByRole('menuitem', { name: /Customize link/i }).click()
136+
}
137+
138+
/**
139+
* Adjust share permissions to be editable
140+
*/
141+
function adjustSharePermission(): void {
142+
openLinkShareDetails(0)
143+
144+
cy.get('[data-cy-files-sharing-share-permissions-bundle]').should('be.visible')
145+
cy.get('[data-cy-files-sharing-share-permissions-bundle="upload-edit"]').click()
146+
147+
cy.intercept('PUT', '**/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('updateShare')
148+
cy.findByRole('button', { name: 'Update share' }).click()
149+
cy.wait('@updateShare').its('response.statusCode').should('eq', 200)
150+
}
151+
152+
/**
153+
* Setup a public share and backup the state.
154+
* If the setup was already done in another run, the state will be restored.
155+
*
156+
* @param shareName The name of the shared folder
157+
* @return The URL of the share
158+
*/
159+
export function setupPublicShare(shareName = 'shared'): Cypress.Chainable<string> {
160+
161+
return cy.task('getVariable', { key: 'public-share-data' })
162+
.then((data) => {
163+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
164+
const { dataSnapshot, shareUrl } = data as any || {}
165+
if (dataSnapshot) {
166+
cy.restoreState(dataSnapshot)
167+
defaultShareContext.url = shareUrl
168+
return cy.wrap(shareUrl as string)
169+
} else {
170+
const shareData: Record<string, unknown> = {}
171+
return cy.createRandomUser()
172+
.then((user) => {
173+
defaultShareContext.user = user
174+
})
175+
.then(() => setupData(defaultShareContext.user, shareName))
176+
.then(() => createLinkShare(defaultShareContext, shareName))
177+
.then((url) => {
178+
shareData.shareUrl = url
179+
})
180+
.then(() => adjustSharePermission())
181+
.then(() =>
182+
cy.saveState().then((snapshot) => {
183+
shareData.dataSnapshot = snapshot
184+
}),
185+
)
186+
.then(() => cy.task('setVariable', { key: 'public-share-data', value: shareData }))
187+
.then(() => cy.log(`Public share setup, URL: ${shareData.shareUrl}`))
188+
.then(() => cy.wrap(defaultShareContext.url))
189+
}
190+
})
191+
}

0 commit comments

Comments
 (0)