Skip to content

Commit eff3777

Browse files
committed
tests: Do not return folders twice in trashbin endpoint
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
1 parent d2b9849 commit eff3777

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

tests/FolderControllerTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
use OCA\Bookmarks\Service\TreeCacheManager;
2626
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
2727
use OCP\AppFramework\QueryException;
28+
use OCP\DB\QueryBuilder\IQueryBuilder;
2829
use OCP\ICache;
2930
use OCP\ICacheFactory;
31+
use OCP\IDBConnection;
3032
use OCP\IGroupManager;
3133
use OCP\IRequest;
3234
use OCP\IUserManager;
@@ -257,6 +259,51 @@ public function testRead(): void {
257259
$this->assertEquals($this->folder1->getTitle(), $data['item']['title']);
258260
}
259261

262+
/**
263+
* Regression test: trashed folders must not be listed more than once.
264+
*
265+
* The id columns of bookmarks, bookmarks_folders and bookmarks_shared_folders
266+
* are independent autoincrement sequences, so the same numeric id routinely
267+
* exists across all three types. getSoftDeletedRootItems() self-joins
268+
* bookmarks_tree to inspect each item's parent folder; when that join lacked
269+
* a type constraint, a bookmark or share sharing the parent folder's id
270+
* matched too, multiplying the result rows and showing every folder twice in
271+
* the trash bin.
272+
*
273+
* @throws AlreadyExistsError
274+
* @throws UrlParseError
275+
* @throws UserLimitExceededError
276+
* @throws MultipleObjectsReturnedException
277+
* @throws \OCP\AppFramework\Db\DoesNotExistException
278+
*/
279+
public function testGetSoftDeletedRootItemsDoesNotDuplicateOnIdCollision(): void {
280+
$this->setupBookmarks();
281+
$this->authorizer->setUserId($this->userId);
282+
283+
// folder2 lives inside folder1; trashing it individually makes it a root
284+
// item of the trash bin, since its parent (folder1) is not trashed.
285+
$this->treeMapper->softDeleteEntry(TreeMapper::TYPE_FOLDER, $this->folder2->getId());
286+
287+
// Reproduce a cross-type id collision: a non-folder tree entry whose id
288+
// equals the trashed folder's parent (folder1). This is the data shape
289+
// that used to duplicate rows via the type-less parent self-join.
290+
$db = \OCP\Server::get(IDBConnection::class);
291+
$qb = $db->getQueryBuilder();
292+
$qb->insert('bookmarks_tree')
293+
->values([
294+
'id' => $qb->createNamedParameter($this->folder1->getId(), IQueryBuilder::PARAM_INT),
295+
'type' => $qb->createNamedParameter(TreeMapper::TYPE_BOOKMARK),
296+
'parent_folder' => $qb->createNamedParameter($this->folderMapper->findRootFolder($this->userId)->getId(), IQueryBuilder::PARAM_INT),
297+
'index' => $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT),
298+
]);
299+
$qb->executeStatement();
300+
301+
$rootItems = $this->treeMapper->getSoftDeletedRootItems($this->userId, TreeMapper::TYPE_FOLDER);
302+
303+
$ids = array_map(static fn ($folder) => $folder->getId(), $rootItems);
304+
$this->assertEquals([$this->folder2->getId()], $ids, 'Trashed folder must appear exactly once');
305+
}
306+
260307
/**
261308
* @throws AlreadyExistsError
262309
* @throws UrlParseError

0 commit comments

Comments
 (0)