Skip to content

Commit 47b97a5

Browse files
committed
test: Add E2E test for searching with restricted ACL read permissions
Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
1 parent 71d3513 commit 47b97a5

3 files changed

Lines changed: 168 additions & 1 deletion

File tree

cypress/e2e/groupfolders.cy.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ import {
2828
PERMISSION_READ,
2929
PERMISSION_WRITE,
3030
} from './groupfoldersUtils.ts'
31+
import {
32+
openUnifiedSearch,
33+
searchCanLoadMoreResults,
34+
searchFor,
35+
searchHasResult,
36+
} from './unifiedSearchUtils.ts'
3137
import { randHash } from '../utils/index.js'
3238
import { triggerActionForFile } from './files/filesUtils.ts'
3339

@@ -323,3 +329,90 @@ describe('Groupfolders ACLs and trashbin behavior', () => {
323329
})
324330

325331
})
332+
333+
describe('Groupfolders ACLs and unified search behavior', () => {
334+
let user1: User
335+
let user2: User
336+
let managerUser: User
337+
let groupFolderId: string
338+
let groupName: string
339+
let groupFolderName: string
340+
341+
beforeEach(() => {
342+
if (groupFolderId) {
343+
deleteGroupFolder(groupFolderId)
344+
}
345+
groupName = `test_group_${randHash()}`
346+
groupFolderName = `test_group_folder_${randHash()}`
347+
348+
cy.createRandomUser()
349+
.then(_user => {
350+
user1 = _user
351+
})
352+
cy.createRandomUser()
353+
.then(_user => {
354+
user2 = _user
355+
})
356+
cy.createRandomUser()
357+
.then(_user => {
358+
managerUser = _user
359+
360+
createGroup(groupName)
361+
.then(() => {
362+
addUserToGroup(groupName, user1.userId)
363+
addUserToGroup(groupName, user2.userId)
364+
addUserToGroup(groupName, managerUser.userId)
365+
createGroupFolder(groupFolderName, groupName, [PERMISSION_READ, PERMISSION_WRITE, PERMISSION_DELETE])
366+
.then(_groupFolderId => {
367+
groupFolderId = _groupFolderId
368+
enableACLPermissions(groupFolderId)
369+
addACLManagerUser(groupFolderId, managerUser.userId)
370+
})
371+
})
372+
})
373+
})
374+
375+
it('Search for files in groupfolders with restricted read permissions', () => {
376+
// Create two subfolders and twelve files alterning between subfolders
377+
cy.login(managerUser)
378+
cy.mkdir(managerUser, `/${groupFolderName}/subfolder1`)
379+
cy.mkdir(managerUser, `/${groupFolderName}/subfolder2`)
380+
// Use incremental mtimes to have a specific order in the results
381+
const mtime = Date.now() / 1000
382+
for (let i = 0; i < 12; i = i + 2) {
383+
cy.uploadContent(managerUser, new Blob([i]), 'text/plain', `/${groupFolderName}/subfolder1/test${i}.txt`, mtime + i)
384+
cy.uploadContent(managerUser, new Blob([i + 1]), 'text/plain', `/${groupFolderName}/subfolder2/test${i + 1}.txt`, mtime + i + 1)
385+
}
386+
387+
// Set ACL permissions
388+
setACLPermissions(groupFolderId, '/subfolder1', [`+${PERMISSION_READ}`], undefined, user1.userId)
389+
setACLPermissions(groupFolderId, '/subfolder1', [`+${PERMISSION_READ}`], undefined, user2.userId)
390+
setACLPermissions(groupFolderId, '/subfolder2', [`+${PERMISSION_READ}`], undefined, user1.userId)
391+
setACLPermissions(groupFolderId, '/subfolder2', [`-${PERMISSION_READ}`], undefined, user2.userId)
392+
393+
// user1 can find files in both subfolders
394+
cy.login(user1)
395+
cy.visit('/apps/files')
396+
openUnifiedSearch()
397+
searchFor('test')
398+
searchHasResult('Files', `test11.txt in ${groupFolderName}/subfolder2`)
399+
searchHasResult('Files', `test10.txt in ${groupFolderName}/subfolder1`)
400+
searchHasResult('Files', `test9.txt in ${groupFolderName}/subfolder2`)
401+
searchHasResult('Files', `test8.txt in ${groupFolderName}/subfolder1`)
402+
searchHasResult('Files', `test7.txt in ${groupFolderName}/subfolder2`)
403+
searchCanLoadMoreResults('Files')
404+
405+
// user2 can find files only in subfolder1
406+
cy.login(user2)
407+
cy.visit('/apps/files')
408+
openUnifiedSearch()
409+
searchFor('test')
410+
searchHasResult('Files', `test10.txt in ${groupFolderName}/subfolder1`)
411+
searchHasResult('Files', `test8.txt in ${groupFolderName}/subfolder1`)
412+
searchHasResult('Files', `test6.txt in ${groupFolderName}/subfolder1`)
413+
searchHasResult('Files', `test4.txt in ${groupFolderName}/subfolder1`)
414+
searchHasResult('Files', `test2.txt in ${groupFolderName}/subfolder1`)
415+
searchCanLoadMoreResults('Files')
416+
})
417+
418+
})

cypress/e2e/unifiedSearchUtils.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
/**
7+
* Get the unified search modal (if open)
8+
*/
9+
export function getUnifiedSearchModal() {
10+
return cy.get('[role="dialog"][id="unified-search"]')
11+
}
12+
13+
/**
14+
* Open the unified search modal
15+
*/
16+
export function openUnifiedSearch() {
17+
cy.get('[class="unified-search-input__button"]').click({ force: true })
18+
// wait for it to be open
19+
getUnifiedSearchModal().should('be.visible')
20+
}
21+
22+
/**
23+
* Searchs for the given string in the unified search modal
24+
*
25+
* @param string term the term to search for
26+
*/
27+
export function searchFor(term: string) {
28+
getUnifiedSearchModal().find('[data-cy-unified-search-input]').type(term)
29+
}
30+
31+
/**
32+
* Get search results main element
33+
*/
34+
export function getUnifiedSearchResults() {
35+
return getUnifiedSearchModal().find('[class="unified-search-modal__results"]')
36+
}
37+
38+
/**
39+
* Get search results list for a specific section
40+
*
41+
* @param string section the section
42+
*/
43+
export function getUnifiedSearchResultsForSection(section: string) {
44+
return getUnifiedSearchResults().contains('[class="result-title"]', section).next('ul')
45+
}
46+
47+
/**
48+
* Get search results footer for a specific section
49+
*
50+
* @param string section the section
51+
*/
52+
export function getUnifiedSearchResultsFooterForSection(section: string) {
53+
return getUnifiedSearchResults().contains('[class="result-title"]', section).siblings('[class="result-footer"]').first()
54+
}
55+
56+
/**
57+
* Checks that the given result is found in the given section
58+
*
59+
* @param string section the section
60+
* @param string result the result in the section
61+
*/
62+
export function searchHasResult(section: string, result: string) {
63+
getUnifiedSearchResultsForSection(section).contains(result).should('be.visible')
64+
}
65+
66+
/**
67+
* Checks that more results can be loaded for the given section
68+
*
69+
* @param string section the section
70+
*/
71+
export function searchCanLoadMoreResults(section: string) {
72+
getUnifiedSearchResultsFooterForSection(section).contains('Load more results').should('be.visible')
73+
}

cypress/support/commands.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Cypress.Commands.add('uploadFile', (user, fixture = 'image.jpg', mimeType = 'ima
9292
* @param {string} mimeType e.g. image/png
9393
* @param {string} target the target of the file relative to the user root
9494
*/
95-
Cypress.Commands.add('uploadContent', (user, blob, mimeType, target) => {
95+
Cypress.Commands.add('uploadContent', (user, blob, mimeType, target, mtime?) => {
9696
cy.clearCookies()
9797
.then({ timeout: 8000 }, async () => {
9898
const fileName = basename(target)
@@ -108,6 +108,7 @@ Cypress.Commands.add('uploadContent', (user, blob, mimeType, target) => {
108108
data: file,
109109
headers: {
110110
'Content-Type': mimeType,
111+
'X-OC-MTime': mtime ? `${mtime}` : undefined,
111112
},
112113
auth: {
113114
username: user.userId,

0 commit comments

Comments
 (0)