diff options
33 files changed, 791 insertions, 272 deletions
diff --git a/apps/dav/lib/Connector/Sabre/QuotaPlugin.php b/apps/dav/lib/Connector/Sabre/QuotaPlugin.php index 9790fc99837..bbb378edc9b 100644 --- a/apps/dav/lib/Connector/Sabre/QuotaPlugin.php +++ b/apps/dav/lib/Connector/Sabre/QuotaPlugin.php @@ -15,6 +15,8 @@ use OCP\Files\StorageNotAvailableException; use Sabre\DAV\Exception\InsufficientStorage; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\DAV\INode; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; /** * This plugin check user quota and deny creating files when they exceeds the quota. @@ -55,6 +57,7 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin { $server->on('beforeWriteContent', [$this, 'beforeWriteContent'], 10); $server->on('beforeCreateFile', [$this, 'beforeCreateFile'], 10); + $server->on('method:MKCOL', [$this, 'onCreateCollection'], 30); $server->on('beforeMove', [$this, 'beforeMove'], 10); $server->on('beforeCopy', [$this, 'beforeCopy'], 10); } @@ -89,6 +92,31 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin { } /** + * Check quota before creating directory + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + * @throws InsufficientStorage + * @throws \Sabre\DAV\Exception\Forbidden + */ + public function onCreateCollection(RequestInterface $request, ResponseInterface $response): bool { + try { + $destinationPath = $this->server->calculateUri($request->getUrl()); + $quotaPath = $this->getPathForDestination($destinationPath); + } catch (\Exception $e) { + return true; + } + if ($quotaPath) { + // MKCOL does not have a Content-Length header, so we can use + // a fixed value for the quota check. + return $this->checkQuota($quotaPath, 4096, true); + } + + return true; + } + + /** * Check quota before writing content * * @param string $uri target file URI @@ -174,7 +202,7 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin { * @throws InsufficientStorage * @return bool */ - public function checkQuota(string $path, $length = null) { + public function checkQuota(string $path, $length = null, $isDir = false) { if ($length === null) { $length = $this->getLength(); } @@ -191,6 +219,10 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin { $freeSpace = $this->getFreeSpace($path); if ($freeSpace >= 0 && $length > $freeSpace) { + if ($isDir) { + throw new InsufficientStorage("Insufficient space in $path. $freeSpace available. Cannot create directory"); + } + throw new InsufficientStorage("Insufficient space in $path, $length required, $freeSpace available"); } } diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 379cd793116..d993b35845c 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -812,6 +812,7 @@ class FederatedShareProvider implements IShareProvider { ->setPermissions((int)$data['permissions']) ->setTarget($data['file_target']) ->setMailSend((bool)$data['mail_send']) + ->setStatus((int)$data['accepted']) ->setToken($data['token']); $shareTime = new \DateTime(); diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml index cdcc79e47fb..95f6153e1d7 100644 --- a/apps/files/appinfo/info.xml +++ b/apps/files/appinfo/info.xml @@ -49,6 +49,9 @@ <command>OCA\Files\Command\Object\Delete</command> <command>OCA\Files\Command\Object\Get</command> <command>OCA\Files\Command\Object\Put</command> + <command>OCA\Files\Command\Object\Info</command> + <command>OCA\Files\Command\Object\ListObject</command> + <command>OCA\Files\Command\Object\Orphans</command> </commands> <settings> diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index 0d9e6aa2d77..a74df7ba3d2 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -35,7 +35,10 @@ return array( 'OCA\\Files\\Command\\Move' => $baseDir . '/../lib/Command/Move.php', 'OCA\\Files\\Command\\Object\\Delete' => $baseDir . '/../lib/Command/Object/Delete.php', 'OCA\\Files\\Command\\Object\\Get' => $baseDir . '/../lib/Command/Object/Get.php', + 'OCA\\Files\\Command\\Object\\Info' => $baseDir . '/../lib/Command/Object/Info.php', + 'OCA\\Files\\Command\\Object\\ListObject' => $baseDir . '/../lib/Command/Object/ListObject.php', 'OCA\\Files\\Command\\Object\\ObjectUtil' => $baseDir . '/../lib/Command/Object/ObjectUtil.php', + 'OCA\\Files\\Command\\Object\\Orphans' => $baseDir . '/../lib/Command/Object/Orphans.php', 'OCA\\Files\\Command\\Object\\Put' => $baseDir . '/../lib/Command/Object/Put.php', 'OCA\\Files\\Command\\Put' => $baseDir . '/../lib/Command/Put.php', 'OCA\\Files\\Command\\RepairTree' => $baseDir . '/../lib/Command/RepairTree.php', diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index 5ece9073178..1d79f38e35a 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -50,7 +50,10 @@ class ComposerStaticInitFiles 'OCA\\Files\\Command\\Move' => __DIR__ . '/..' . '/../lib/Command/Move.php', 'OCA\\Files\\Command\\Object\\Delete' => __DIR__ . '/..' . '/../lib/Command/Object/Delete.php', 'OCA\\Files\\Command\\Object\\Get' => __DIR__ . '/..' . '/../lib/Command/Object/Get.php', + 'OCA\\Files\\Command\\Object\\Info' => __DIR__ . '/..' . '/../lib/Command/Object/Info.php', + 'OCA\\Files\\Command\\Object\\ListObject' => __DIR__ . '/..' . '/../lib/Command/Object/ListObject.php', 'OCA\\Files\\Command\\Object\\ObjectUtil' => __DIR__ . '/..' . '/../lib/Command/Object/ObjectUtil.php', + 'OCA\\Files\\Command\\Object\\Orphans' => __DIR__ . '/..' . '/../lib/Command/Object/Orphans.php', 'OCA\\Files\\Command\\Object\\Put' => __DIR__ . '/..' . '/../lib/Command/Object/Put.php', 'OCA\\Files\\Command\\Put' => __DIR__ . '/..' . '/../lib/Command/Put.php', 'OCA\\Files\\Command\\RepairTree' => __DIR__ . '/..' . '/../lib/Command/RepairTree.php', diff --git a/apps/files/lib/Command/Object/Info.php b/apps/files/lib/Command/Object/Info.php new file mode 100644 index 00000000000..6748de37cfe --- /dev/null +++ b/apps/files/lib/Command/Object/Info.php @@ -0,0 +1,80 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files\Command\Object; + +use OC\Core\Command\Base; +use OCP\Files\IMimeTypeDetector; +use OCP\Files\ObjectStore\IObjectStoreMetaData; +use OCP\Util; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Info extends Base { + public function __construct( + private ObjectUtil $objectUtils, + private IMimeTypeDetector $mimeTypeDetector, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('files:object:info') + ->setDescription('Get the metadata of an object') + ->addArgument('object', InputArgument::REQUIRED, 'Object to get') + ->addOption('bucket', 'b', InputOption::VALUE_REQUIRED, "Bucket to get the object from, only required in cases where it can't be determined from the config"); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $object = $input->getArgument('object'); + $objectStore = $this->objectUtils->getObjectStore($input->getOption('bucket'), $output); + if (!$objectStore) { + return self::FAILURE; + } + + if (!$objectStore instanceof IObjectStoreMetaData) { + $output->writeln('<error>Configured object store does currently not support retrieve metadata</error>'); + return self::FAILURE; + } + + if (!$objectStore->objectExists($object)) { + $output->writeln("<error>Object $object does not exist</error>"); + return self::FAILURE; + } + + try { + $meta = $objectStore->getObjectMetaData($object); + } catch (\Exception $e) { + $msg = $e->getMessage(); + $output->writeln("<error>Failed to read $object from object store: $msg</error>"); + return self::FAILURE; + } + + if ($input->getOption('output') === 'plain' && isset($meta['size'])) { + $meta['size'] = Util::humanFileSize($meta['size']); + } + if (isset($meta['mtime'])) { + $meta['mtime'] = $meta['mtime']->format(\DateTimeImmutable::ATOM); + } + if (!isset($meta['mimetype'])) { + $handle = $objectStore->readObject($object); + $head = fread($handle, 8192); + fclose($handle); + $meta['mimetype'] = $this->mimeTypeDetector->detectString($head); + } + + $this->writeArrayInOutputFormat($input, $output, $meta); + + return self::SUCCESS; + } + +} diff --git a/apps/files/lib/Command/Object/ListObject.php b/apps/files/lib/Command/Object/ListObject.php new file mode 100644 index 00000000000..5d30232e09f --- /dev/null +++ b/apps/files/lib/Command/Object/ListObject.php @@ -0,0 +1,50 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files\Command\Object; + +use OC\Core\Command\Base; +use OCP\Files\ObjectStore\IObjectStoreMetaData; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ListObject extends Base { + private const CHUNK_SIZE = 100; + + public function __construct( + private readonly ObjectUtil $objectUtils, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('files:object:list') + ->setDescription('List all objects in the object store') + ->addOption('bucket', 'b', InputOption::VALUE_REQUIRED, "Bucket to list the objects from, only required in cases where it can't be determined from the config"); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $objectStore = $this->objectUtils->getObjectStore($input->getOption('bucket'), $output); + if (!$objectStore) { + return self::FAILURE; + } + + if (!$objectStore instanceof IObjectStoreMetaData) { + $output->writeln('<error>Configured object store does currently not support listing objects</error>'); + return self::FAILURE; + } + $objects = $objectStore->listObjects(); + $objects = $this->objectUtils->formatObjects($objects, $input->getOption('output') === self::OUTPUT_FORMAT_PLAIN); + $this->writeStreamingTableInOutputFormat($input, $output, $objects, self::CHUNK_SIZE); + + return self::SUCCESS; + } +} diff --git a/apps/files/lib/Command/Object/ObjectUtil.php b/apps/files/lib/Command/Object/ObjectUtil.php index c4ab59608fb..5f053c2c42f 100644 --- a/apps/files/lib/Command/Object/ObjectUtil.php +++ b/apps/files/lib/Command/Object/ObjectUtil.php @@ -12,6 +12,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\ObjectStore\IObjectStore; use OCP\IConfig; use OCP\IDBConnection; +use OCP\Util; use Symfony\Component\Console\Output\OutputInterface; class ObjectUtil { @@ -91,4 +92,24 @@ class ObjectUtil { return $fileId; } + + public function formatObjects(\Iterator $objects, bool $humanOutput): \Iterator { + foreach ($objects as $object) { + yield $this->formatObject($object, $humanOutput); + } + } + + public function formatObject(array $object, bool $humanOutput): array { + $row = array_merge([ + 'urn' => $object['urn'], + ], ($object['metadata'] ?? [])); + + if ($humanOutput && isset($row['size'])) { + $row['size'] = Util::humanFileSize($row['size']); + } + if (isset($row['mtime'])) { + $row['mtime'] = $row['mtime']->format(\DateTimeImmutable::ATOM); + } + return $row; + } } diff --git a/apps/files/lib/Command/Object/Orphans.php b/apps/files/lib/Command/Object/Orphans.php new file mode 100644 index 00000000000..f7132540fc8 --- /dev/null +++ b/apps/files/lib/Command/Object/Orphans.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files\Command\Object; + +use OC\Core\Command\Base; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\ObjectStore\IObjectStoreMetaData; +use OCP\IDBConnection; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Orphans extends Base { + private const CHUNK_SIZE = 100; + + private ?IQueryBuilder $query = null; + + public function __construct( + private readonly ObjectUtil $objectUtils, + private readonly IDBConnection $connection, + ) { + parent::__construct(); + } + + private function getQuery(): IQueryBuilder { + if (!$this->query) { + $this->query = $this->connection->getQueryBuilder(); + $this->query->select('fileid') + ->from('filecache') + ->where($this->query->expr()->eq('fileid', $this->query->createParameter('file_id'))); + } + return $this->query; + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('files:object:orphans') + ->setDescription('List all objects in the object store that don\'t have a matching entry in the database') + ->addOption('bucket', 'b', InputOption::VALUE_REQUIRED, "Bucket to list the objects from, only required in cases where it can't be determined from the config"); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $objectStore = $this->objectUtils->getObjectStore($input->getOption('bucket'), $output); + if (!$objectStore) { + return self::FAILURE; + } + + if (!$objectStore instanceof IObjectStoreMetaData) { + $output->writeln('<error>Configured object store does currently not support listing objects</error>'); + return self::FAILURE; + } + $prefixLength = strlen('urn:oid:'); + + $objects = $objectStore->listObjects('urn:oid:'); + $orphans = new \CallbackFilterIterator($objects, function (array $object) use ($prefixLength) { + $fileId = (int)substr($object['urn'], $prefixLength); + return !$this->fileIdInDb($fileId); + }); + + $orphans = $this->objectUtils->formatObjects($orphans, $input->getOption('output') === self::OUTPUT_FORMAT_PLAIN); + $this->writeStreamingTableInOutputFormat($input, $output, $orphans, self::CHUNK_SIZE); + + return self::SUCCESS; + } + + private function fileIdInDb(int $fileId): bool { + $query = $this->getQuery(); + $query->setParameter('file_id', $fileId, IQueryBuilder::PARAM_INT); + $result = $query->executeQuery(); + return $result->fetchOne() !== false; + } +} diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml index c92559635ea..03078b54040 100644 --- a/apps/files_sharing/appinfo/info.xml +++ b/apps/files_sharing/appinfo/info.xml @@ -50,6 +50,7 @@ Turning the feature off removes shared files and folders on the server for all s <command>OCA\Files_Sharing\Command\ExiprationNotification</command> <command>OCA\Files_Sharing\Command\DeleteOrphanShares</command> <command>OCA\Files_Sharing\Command\FixShareOwners</command> + <command>OCA\Files_Sharing\Command\ListShares</command> </commands> <settings> diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php index 400df9c9771..8682fd5f238 100644 --- a/apps/files_sharing/composer/composer/autoload_classmap.php +++ b/apps/files_sharing/composer/composer/autoload_classmap.php @@ -28,6 +28,7 @@ return array( 'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => $baseDir . '/../lib/Command/DeleteOrphanShares.php', 'OCA\\Files_Sharing\\Command\\ExiprationNotification' => $baseDir . '/../lib/Command/ExiprationNotification.php', 'OCA\\Files_Sharing\\Command\\FixShareOwners' => $baseDir . '/../lib/Command/FixShareOwners.php', + 'OCA\\Files_Sharing\\Command\\ListShares' => $baseDir . '/../lib/Command/ListShares.php', 'OCA\\Files_Sharing\\Controller\\AcceptController' => $baseDir . '/../lib/Controller/AcceptController.php', 'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => $baseDir . '/../lib/Controller/DeletedShareAPIController.php', 'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => $baseDir . '/../lib/Controller/ExternalSharesController.php', diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php index 02bb6d08bb5..3bf5bcdffda 100644 --- a/apps/files_sharing/composer/composer/autoload_static.php +++ b/apps/files_sharing/composer/composer/autoload_static.php @@ -43,6 +43,7 @@ class ComposerStaticInitFiles_Sharing 'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => __DIR__ . '/..' . '/../lib/Command/DeleteOrphanShares.php', 'OCA\\Files_Sharing\\Command\\ExiprationNotification' => __DIR__ . '/..' . '/../lib/Command/ExiprationNotification.php', 'OCA\\Files_Sharing\\Command\\FixShareOwners' => __DIR__ . '/..' . '/../lib/Command/FixShareOwners.php', + 'OCA\\Files_Sharing\\Command\\ListShares' => __DIR__ . '/..' . '/../lib/Command/ListShares.php', 'OCA\\Files_Sharing\\Controller\\AcceptController' => __DIR__ . '/..' . '/../lib/Controller/AcceptController.php', 'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/DeletedShareAPIController.php', 'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => __DIR__ . '/..' . '/../lib/Controller/ExternalSharesController.php', diff --git a/apps/files_sharing/lib/Command/ListShares.php b/apps/files_sharing/lib/Command/ListShares.php new file mode 100644 index 00000000000..2d5cdbf7812 --- /dev/null +++ b/apps/files_sharing/lib/Command/ListShares.php @@ -0,0 +1,161 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_Sharing\Command; + +use OC\Core\Command\Base; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Share\IManager; +use OCP\Share\IShare; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ListShares extends Base { + /** @var array<string, Node> */ + private array $fileCache = []; + + private const SHARE_TYPE_NAMES = [ + IShare::TYPE_USER => 'user', + IShare::TYPE_GROUP => 'group', + IShare::TYPE_LINK => 'link', + IShare::TYPE_EMAIL => 'email', + IShare::TYPE_REMOTE => 'remote', + IShare::TYPE_REMOTE_GROUP => 'group', + IShare::TYPE_ROOM => 'room', + IShare::TYPE_DECK => 'deck', + ]; + + public function __construct( + private readonly IManager $shareManager, + private readonly IRootFolder $rootFolder, + ) { + parent::__construct(); + } + + protected function configure() { + parent::configure(); + $this + ->setName('share:list') + ->setDescription('List available shares') + ->addOption('owner', null, InputOption::VALUE_REQUIRED, 'only show shares owned by a specific user') + ->addOption('recipient', null, InputOption::VALUE_REQUIRED, 'only show shares with a specific recipient') + ->addOption('by', null, InputOption::VALUE_REQUIRED, 'only show shares with by as specific user') + ->addOption('file', null, InputOption::VALUE_REQUIRED, 'only show shares of a specific file') + ->addOption('parent', null, InputOption::VALUE_REQUIRED, 'only show shares of files inside a specific folder') + ->addOption('recursive', null, InputOption::VALUE_NONE, 'also show shares nested deep inside the specified parent folder') + ->addOption('type', null, InputOption::VALUE_REQUIRED, 'only show shares of a specific type') + ->addOption('status', null, InputOption::VALUE_REQUIRED, 'only show shares with a specific status'); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + if ($input->getOption('recursive') && !$input->getOption('parent')) { + $output->writeln("<error>recursive option can't be used without parent option</error>"); + return 1; + } + + // todo: do some pre-filtering instead of first querying all shares + /** @var \Iterator<IShare> $allShares */ + $allShares = $this->shareManager->getAllShares(); + $shares = new \CallbackFilterIterator($allShares, function (IShare $share) use ($input) { + return $this->shouldShowShare($input, $share); + }); + $shares = iterator_to_array($shares); + $data = array_map(function (IShare $share) { + return [ + 'id' => $share->getId(), + 'file' => $share->getNodeId(), + 'target-path' => $share->getTarget(), + 'source-path' => $share->getNode()->getPath(), + 'owner' => $share->getShareOwner(), + 'recipient' => $share->getSharedWith(), + 'by' => $share->getSharedBy(), + 'type' => self::SHARE_TYPE_NAMES[$share->getShareType()] ?? 'unknown', + ]; + }, $shares); + + $this->writeTableInOutputFormat($input, $output, $data); + return 0; + } + + private function getFileId(string $file): int { + if (is_numeric($file)) { + return (int)$file; + } + return $this->getFile($file)->getId(); + } + + private function getFile(string $file): Node { + if (isset($this->fileCache[$file])) { + return $this->fileCache[$file]; + } + + if (is_numeric($file)) { + $node = $this->rootFolder->getFirstNodeById((int)$file); + if (!$node) { + throw new NotFoundException("File with id $file not found"); + } + } else { + $node = $this->rootFolder->get($file); + } + $this->fileCache[$file] = $node; + return $node; + } + + private function getShareType(string $type): int { + foreach (self::SHARE_TYPE_NAMES as $shareType => $shareTypeName) { + if ($shareTypeName === $type) { + return $shareType; + } + } + throw new \Exception("Unknown share type $type"); + } + + private function shouldShowShare(InputInterface $input, IShare $share): bool { + if ($input->getOption('owner') && $share->getShareOwner() !== $input->getOption('owner')) { + return false; + } + if ($input->getOption('recipient') && $share->getSharedWith() !== $input->getOption('recipient')) { + return false; + } + if ($input->getOption('by') && $share->getSharedBy() !== $input->getOption('by')) { + return false; + } + if ($input->getOption('file') && $share->getNodeId() !== $this->getFileId($input->getOption('file'))) { + return false; + } + if ($input->getOption('parent')) { + $parent = $this->getFile($input->getOption('parent')); + if (!$parent instanceof Folder) { + throw new \Exception("Parent {$parent->getPath()} is not a folder"); + } + $recursive = $input->getOption('recursive'); + if (!$recursive) { + $shareCacheEntry = $share->getNodeCacheEntry(); + if (!$shareCacheEntry) { + $shareCacheEntry = $share->getNode(); + } + if ($shareCacheEntry->getParentId() !== $parent->getId()) { + return false; + } + } else { + $shareNode = $share->getNode(); + if ($parent->getRelativePath($shareNode->getPath()) === null) { + return false; + } + } + } + if ($input->getOption('type') && $share->getShareType() !== $this->getShareType($input->getOption('type'))) { + return false; + } + return true; + } +} diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index c8566098707..611392c286e 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -19,6 +19,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Federation\ICloudFederationFactory; use OCP\Federation\ICloudFederationProviderManager; use OCP\Files\NotFoundException; +use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; use OCP\ICacheFactory; @@ -254,12 +255,18 @@ class ManagerTest extends TestCase { $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}'); $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); + $newClientCalls = []; + $this->clientService + ->method('newClient') + ->willReturnCallback(function () use (&$newClientCalls): IClient { + if (!empty($newClientCalls)) { + return array_shift($newClientCalls); + } + return $this->createMock(IClient::class); + }); if (!$isGroup) { - $client = $this->getMockBuilder('OCP\Http\Client\IClient') - ->disableOriginalConstructor()->getMock(); - $this->clientService->expects($this->at(0)) - ->method('newClient') - ->willReturn($client); + $client = $this->createMock(IClient::class); + $newClientCalls[] = $client; $response = $this->createMock(IResponse::class); $response->method('getBody') ->willReturn(json_encode([ @@ -311,11 +318,8 @@ class ManagerTest extends TestCase { $this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1'); if (!$isGroup) { - $client = $this->getMockBuilder('OCP\Http\Client\IClient') - ->disableOriginalConstructor()->getMock(); - $this->clientService->expects($this->at(0)) - ->method('newClient') - ->willReturn($client); + $client = $this->createMock(IClient::class); + $newClientCalls[] = $client; $response = $this->createMock(IResponse::class); $response->method('getBody') ->willReturn(json_encode([ @@ -367,16 +371,10 @@ class ManagerTest extends TestCase { // no http requests here $this->manager->removeGroupShares('group1'); } else { - $client1 = $this->getMockBuilder('OCP\Http\Client\IClient') - ->disableOriginalConstructor()->getMock(); - $client2 = $this->getMockBuilder('OCP\Http\Client\IClient') - ->disableOriginalConstructor()->getMock(); - $this->clientService->expects($this->exactly(2)) - ->method('newClient') - ->willReturnOnConsecutiveCalls( - $client1, - $client2, - ); + $client1 = $this->createMock(IClient::class); + $client2 = $this->createMock(IClient::class); + $newClientCalls[] = $client1; + $newClientCalls[] = $client2; $response = $this->createMock(IResponse::class); $response->method('getBody') ->willReturn(json_encode([ diff --git a/core/Command/Base.php b/core/Command/Base.php index b915ae2ae4a..c9b6337b64a 100644 --- a/core/Command/Base.php +++ b/core/Command/Base.php @@ -88,6 +88,58 @@ class Base extends Command implements CompletionAwareInterface { } } + protected function writeStreamingTableInOutputFormat(InputInterface $input, OutputInterface $output, \Iterator $items, int $tableGroupSize): void { + switch ($input->getOption('output')) { + case self::OUTPUT_FORMAT_JSON: + case self::OUTPUT_FORMAT_JSON_PRETTY: + $this->writeStreamingJsonArray($input, $output, $items); + break; + default: + foreach ($this->chunkIterator($items, $tableGroupSize) as $chunk) { + $this->writeTableInOutputFormat($input, $output, $chunk); + } + break; + } + } + + protected function writeStreamingJsonArray(InputInterface $input, OutputInterface $output, \Iterator $items): void { + $first = true; + $outputType = $input->getOption('output'); + + $output->writeln('['); + foreach ($items as $item) { + if (!$first) { + $output->writeln(','); + } + if ($outputType === self::OUTPUT_FORMAT_JSON_PRETTY) { + $output->write(json_encode($item, JSON_PRETTY_PRINT)); + } else { + $output->write(json_encode($item)); + } + $first = false; + } + $output->writeln("\n]"); + } + + public function chunkIterator(\Iterator $iterator, int $count): \Iterator { + $chunk = []; + + for ($i = 0; $iterator->valid(); $i++) { + $chunk[] = $iterator->current(); + $iterator->next(); + if (count($chunk) == $count) { + // Got a full chunk, yield and start a new one + yield $chunk; + $chunk = []; + } + } + + if (count($chunk)) { + // Yield the last chunk even if incomplete + yield $chunk; + } + } + /** * @param mixed $item diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 2d0b62d749f..52e3075e413 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -456,6 +456,7 @@ return array( 'OCP\\Files\\Notify\\INotifyHandler' => $baseDir . '/lib/public/Files/Notify/INotifyHandler.php', 'OCP\\Files\\Notify\\IRenameChange' => $baseDir . '/lib/public/Files/Notify/IRenameChange.php', 'OCP\\Files\\ObjectStore\\IObjectStore' => $baseDir . '/lib/public/Files/ObjectStore/IObjectStore.php', + 'OCP\\Files\\ObjectStore\\IObjectStoreMetaData' => $baseDir . '/lib/public/Files/ObjectStore/IObjectStoreMetaData.php', 'OCP\\Files\\ObjectStore\\IObjectStoreMultiPartUpload' => $baseDir . '/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php', 'OCP\\Files\\ReservedWordException' => $baseDir . '/lib/public/Files/ReservedWordException.php', 'OCP\\Files\\Search\\ISearchBinaryOperator' => $baseDir . '/lib/public/Files/Search/ISearchBinaryOperator.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 64210bd5843..e98bc3e1aaa 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -505,6 +505,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Files\\Notify\\INotifyHandler' => __DIR__ . '/../../..' . '/lib/public/Files/Notify/INotifyHandler.php', 'OCP\\Files\\Notify\\IRenameChange' => __DIR__ . '/../../..' . '/lib/public/Files/Notify/IRenameChange.php', 'OCP\\Files\\ObjectStore\\IObjectStore' => __DIR__ . '/../../..' . '/lib/public/Files/ObjectStore/IObjectStore.php', + 'OCP\\Files\\ObjectStore\\IObjectStoreMetaData' => __DIR__ . '/../../..' . '/lib/public/Files/ObjectStore/IObjectStoreMetaData.php', 'OCP\\Files\\ObjectStore\\IObjectStoreMultiPartUpload' => __DIR__ . '/../../..' . '/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php', 'OCP\\Files\\ReservedWordException' => __DIR__ . '/../../..' . '/lib/public/Files/ReservedWordException.php', 'OCP\\Files\\Search\\ISearchBinaryOperator' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchBinaryOperator.php', diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php index e9417c8012a..ab5bae316f4 100644 --- a/lib/private/Files/Cache/CacheEntry.php +++ b/lib/private/Files/Cache/CacheEntry.php @@ -110,6 +110,10 @@ class CacheEntry implements ICacheEntry { return $this->data['upload_time'] ?? null; } + public function getParentId(): int { + return $this->data['parent']; + } + public function getData() { return $this->data; } diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index 41ab75caf45..e970fb6ac14 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -3,14 +3,16 @@ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + namespace OC\Files\ObjectStore; use Aws\Result; use Exception; use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\ObjectStore\IObjectStoreMetaData; use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; -class S3 implements IObjectStore, IObjectStoreMultiPartUpload { +class S3 implements IObjectStore, IObjectStoreMultiPartUpload, IObjectStoreMetaData { use S3ConnectionTrait; use S3ObjectTrait; @@ -61,7 +63,7 @@ class S3 implements IObjectStore, IObjectStoreMultiPartUpload { 'Key' => $urn, 'UploadId' => $uploadId, 'MaxParts' => 1000, - 'PartNumberMarker' => $partNumberMarker + 'PartNumberMarker' => $partNumberMarker, ] + $this->getSSECParameters()); $parts = array_merge($parts, $result->get('Parts') ?? []); $isTruncated = $result->get('IsTruncated'); @@ -89,7 +91,41 @@ class S3 implements IObjectStore, IObjectStoreMultiPartUpload { $this->getConnection()->abortMultipartUpload([ 'Bucket' => $this->bucket, 'Key' => $urn, - 'UploadId' => $uploadId + 'UploadId' => $uploadId, ]); } + + public function getObjectMetaData(string $urn): array { + $object = $this->getConnection()->headObject([ + 'Bucket' => $this->bucket, + 'Key' => $urn + ] + $this->getSSECParameters())->toArray(); + return [ + 'mtime' => $object['LastModified'], + 'etag' => trim($object['ETag'], '"'), + 'size' => (int)($object['Size'] ?? $object['ContentLength']), + ]; + } + + public function listObjects(string $prefix = ''): \Iterator { + $results = $this->getConnection()->getPaginator('ListObjectsV2', [ + 'Bucket' => $this->bucket, + 'Prefix' => $prefix, + ] + $this->getSSECParameters()); + + foreach ($results as $result) { + if (is_array($result['Contents'])) { + foreach ($result['Contents'] as $object) { + yield [ + 'urn' => basename($object['Key']), + 'metadata' => [ + 'mtime' => $object['LastModified'], + 'etag' => trim($object['ETag'], '"'), + 'size' => (int)($object['Size'] ?? $object['ContentLength']), + ], + ]; + } + } + } + } } diff --git a/lib/public/Files/Cache/ICacheEntry.php b/lib/public/Files/Cache/ICacheEntry.php index 11d91e74105..28e673071fd 100644 --- a/lib/public/Files/Cache/ICacheEntry.php +++ b/lib/public/Files/Cache/ICacheEntry.php @@ -161,4 +161,12 @@ interface ICacheEntry extends ArrayAccess { * @since 25.0.0 */ public function getUnencryptedSize(): int; + + /** + * Get the file id of the parent folder + * + * @return int + * @since 32.0.0 + */ + public function getParentId(): int; } diff --git a/lib/public/Files/ObjectStore/IObjectStoreMetaData.php b/lib/public/Files/ObjectStore/IObjectStoreMetaData.php new file mode 100644 index 00000000000..8359e83f573 --- /dev/null +++ b/lib/public/Files/ObjectStore/IObjectStoreMetaData.php @@ -0,0 +1,38 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\Files\ObjectStore; + +/** + * Interface IObjectStoreMetaData + * + * @psalm-type ObjectMetaData = array{mtime?: \DateTime, etag?: string, size?: int, mimetype?: string, filename?: string} + * + * @since 32.0.0 + */ +interface IObjectStoreMetaData { + /** + * Get metadata for an object. + * + * @param string $urn + * @return ObjectMetaData + * + * @since 32.0.0 + */ + public function getObjectMetaData(string $urn): array; + + /** + * List all objects in the object store. + * + * If the object store implementation can do it efficiently, the metadata for each object is also included. + * + * @param string $prefix + * @return \Iterator<array{urn: string, metadata: ?ObjectMetaData}> + * + * @since 32.0.0 + */ + public function listObjects(string $prefix = ''): \Iterator; +} diff --git a/lib/public/Template.php b/lib/public/Template.php index 20783d31cb6..3b31ee10a54 100644 --- a/lib/public/Template.php +++ b/lib/public/Template.php @@ -7,6 +7,8 @@ */ namespace OCP; +use OCP\Template\ITemplate; + /* * We have to require the functions file because this class contains aliases to the functions */ @@ -19,7 +21,7 @@ require_once __DIR__ . '/../private/Template/functions.php'; * @since 8.0.0 * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead */ -class Template extends \OC_Template { +class Template extends \OC_Template implements ITemplate { /** * Make OC_Helper::imagePath available as a simple function * diff --git a/package-lock.json b/package-lock.json index c4103896861..3d56fba57a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25363,9 +25363,9 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.3.tgz", + "integrity": "sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/tests/Core/Command/Config/App/SetConfigTest.php b/tests/Core/Command/Config/App/SetConfigTest.php index 1d599bf3234..ffb7e424a0a 100644 --- a/tests/Core/Command/Config/App/SetConfigTest.php +++ b/tests/Core/Command/Config/App/SetConfigTest.php @@ -41,7 +41,7 @@ class SetConfigTest extends TestCase { } - public function setData() { + public static function dataSet() { return [ [ 'name', @@ -63,7 +63,7 @@ class SetConfigTest extends TestCase { } /** - * @dataProvider setData + * @dataProvider dataSet * * @param string $configName * @param mixed $newValue diff --git a/tests/Core/Command/Config/System/SetConfigTest.php b/tests/Core/Command/Config/System/SetConfigTest.php index 2905af5c3d7..6ab624b7918 100644 --- a/tests/Core/Command/Config/System/SetConfigTest.php +++ b/tests/Core/Command/Config/System/SetConfigTest.php @@ -39,7 +39,7 @@ class SetConfigTest extends TestCase { } - public function setData() { + public static function dataTest() { return [ [['name'], 'newvalue', null, 'newvalue'], [['a', 'b', 'c'], 'foobar', null, ['b' => ['c' => 'foobar']]], @@ -48,7 +48,7 @@ class SetConfigTest extends TestCase { } /** - * @dataProvider setData + * @dataProvider dataTest * * @param array $configNames * @param string $newValue diff --git a/tests/Core/Command/Encryption/EnableTest.php b/tests/Core/Command/Encryption/EnableTest.php index 2d463452add..e4f8ae9eacf 100644 --- a/tests/Core/Command/Encryption/EnableTest.php +++ b/tests/Core/Command/Encryption/EnableTest.php @@ -45,7 +45,7 @@ class EnableTest extends TestCase { } - public function dataEnable() { + public static function dataEnable(): array { return [ ['no', null, [], true, 'Encryption enabled', 'No encryption module is loaded'], ['yes', null, [], false, 'Encryption is already enabled', 'No encryption module is loaded'], @@ -57,15 +57,8 @@ class EnableTest extends TestCase { /** * @dataProvider dataEnable - * - * @param string $oldStatus - * @param string $defaultModule - * @param array $availableModules - * @param bool $isUpdating - * @param string $expectedString - * @param string $expectedDefaultModuleString */ - public function testEnable($oldStatus, $defaultModule, $availableModules, $isUpdating, $expectedString, $expectedDefaultModuleString): void { + public function testEnable(string $oldStatus, ?string $defaultModule, array $availableModules, bool $isUpdating, string $expectedString, string $expectedDefaultModuleString): void { if ($isUpdating) { $this->config->expects($this->once()) ->method('setAppValue') @@ -79,27 +72,30 @@ class EnableTest extends TestCase { if (empty($availableModules)) { $this->config->expects($this->once()) ->method('getAppValue') - ->with('core', 'encryption_enabled', $this->anything()) - ->willReturn($oldStatus); + ->willReturnMap([ + ['core', 'encryption_enabled', 'no', $oldStatus], + ]); } else { $this->config->expects($this->exactly(2)) ->method('getAppValue') - ->withConsecutive( - ['core', 'encryption_enabled', $this->anything()], - ['core', 'default_encryption_module', $this->anything()], - )->willReturnOnConsecutiveCalls( - $oldStatus, - $defaultModule, - ); + ->willReturnMap([ + ['core', 'encryption_enabled', 'no', $oldStatus], + ['core', 'default_encryption_module', null, $defaultModule], + ]); } + $calls = [ + [$expectedString, 0], + ['', 0], + [$expectedDefaultModuleString, 0], + ]; $this->consoleOutput->expects($this->exactly(3)) ->method('writeln') - ->withConsecutive( - [$this->stringContains($expectedString)], - [''], - [$this->stringContains($expectedDefaultModuleString)], - ); + ->willReturnCallback(function (string $message, int $level) use (&$calls): void { + $call = array_shift($calls); + $this->assertStringContainsString($call[0], $message); + $this->assertSame($call[1], $level); + }); self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); } diff --git a/tests/Core/Command/Log/ManageTest.php b/tests/Core/Command/Log/ManageTest.php index b354bb17076..9a21fe6dc56 100644 --- a/tests/Core/Command/Log/ManageTest.php +++ b/tests/Core/Command/Log/ManageTest.php @@ -87,7 +87,7 @@ class ManageTest extends TestCase { self::invokePrivate($this->command, 'validateTimezone', ['Mars/OlympusMons']); } - public function convertLevelStringProvider() { + public static function dataConvertLevelString(): array { return [ ['dEbug', 0], ['inFO', 1], @@ -100,9 +100,9 @@ class ManageTest extends TestCase { } /** - * @dataProvider convertLevelStringProvider + * @dataProvider dataConvertLevelString */ - public function testConvertLevelString($levelString, $expectedInt): void { + public function testConvertLevelString(string $levelString, int $expectedInt): void { $this->assertEquals($expectedInt, self::invokePrivate($this->command, 'convertLevelString', [$levelString]) ); @@ -115,7 +115,7 @@ class ManageTest extends TestCase { self::invokePrivate($this->command, 'convertLevelString', ['abc']); } - public function convertLevelNumberProvider() { + public static function dataConvertLevelNumber(): array { return [ [0, 'Debug'], [1, 'Info'], @@ -126,9 +126,9 @@ class ManageTest extends TestCase { } /** - * @dataProvider convertLevelNumberProvider + * @dataProvider dataConvertLevelNumber */ - public function testConvertLevelNumber($levelNum, $expectedString): void { + public function testConvertLevelNumber(int $levelNum, string $expectedString): void { $this->assertEquals($expectedString, self::invokePrivate($this->command, 'convertLevelNumber', [$levelNum]) ); @@ -144,23 +144,23 @@ class ManageTest extends TestCase { public function testGetConfiguration(): void { $this->config->expects($this->exactly(3)) ->method('getSystemValue') - ->withConsecutive( - ['log_type', 'file'], - ['loglevel', 2], - ['logtimezone', 'UTC'], - )->willReturnOnConsecutiveCalls( - 'log_type_value', - 0, - 'logtimezone_value' - ); + ->willReturnMap([ + ['log_type', 'file', 'log_type_value'], + ['loglevel', 2, 0], + ['logtimezone', 'UTC', 'logtimezone_value'], + ]); + $calls = [ + ['Enabled logging backend: log_type_value'], + ['Log level: Debug (0)'], + ['Log timezone: logtimezone_value'], + ]; $this->consoleOutput->expects($this->exactly(3)) ->method('writeln') - ->withConsecutive( - ['Enabled logging backend: log_type_value'], - ['Log level: Debug (0)'], - ['Log timezone: logtimezone_value'], - ); + ->willReturnCallback(function (string $message) use (&$calls): void { + $call = array_shift($calls); + $this->assertStringContainsString($call[0], $message); + }); self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); } diff --git a/tests/Core/Command/User/AuthTokens/DeleteTest.php b/tests/Core/Command/User/AuthTokens/DeleteTest.php index 8588c1d5d54..1396fe53084 100644 --- a/tests/Core/Command/User/AuthTokens/DeleteTest.php +++ b/tests/Core/Command/User/AuthTokens/DeleteTest.php @@ -40,8 +40,10 @@ class DeleteTest extends TestCase { public function testDeleteTokenById(): void { $this->consoleInput->expects($this->exactly(2)) ->method('getArgument') - ->withConsecutive(['uid'], ['id']) - ->willReturnOnConsecutiveCalls('user', 42); + ->willReturnMap([ + ['uid', 'user'], + ['id', '42'] + ]); $this->consoleInput->expects($this->once()) ->method('getOption') @@ -59,8 +61,10 @@ class DeleteTest extends TestCase { public function testDeleteTokenByIdRequiresTokenId(): void { $this->consoleInput->expects($this->exactly(2)) ->method('getArgument') - ->withConsecutive(['uid'], ['id']) - ->willReturnOnConsecutiveCalls('user', null); + ->willReturnMap([ + ['uid', 'user'], + ['id', null] + ]); $this->consoleInput->expects($this->once()) ->method('getOption') @@ -78,8 +82,10 @@ class DeleteTest extends TestCase { public function testDeleteTokensLastUsedBefore(): void { $this->consoleInput->expects($this->exactly(2)) ->method('getArgument') - ->withConsecutive(['uid'], ['id']) - ->willReturnOnConsecutiveCalls('user', null); + ->willReturnMap([ + ['uid', 'user'], + ['id', null] + ]); $this->consoleInput->expects($this->once()) ->method('getOption') @@ -97,8 +103,10 @@ class DeleteTest extends TestCase { public function testLastUsedBeforeAcceptsIso8601Expanded(): void { $this->consoleInput->expects($this->exactly(2)) ->method('getArgument') - ->withConsecutive(['uid'], ['id']) - ->willReturnOnConsecutiveCalls('user', null); + ->willReturnMap([ + ['uid', 'user'], + ['id', null] + ]); $this->consoleInput->expects($this->once()) ->method('getOption') @@ -116,8 +124,10 @@ class DeleteTest extends TestCase { public function testLastUsedBeforeAcceptsYmd(): void { $this->consoleInput->expects($this->exactly(2)) ->method('getArgument') - ->withConsecutive(['uid'], ['id']) - ->willReturnOnConsecutiveCalls('user', null); + ->willReturnMap([ + ['uid', 'user'], + ['id', null] + ]); $this->consoleInput->expects($this->once()) ->method('getOption') @@ -135,8 +145,10 @@ class DeleteTest extends TestCase { public function testIdAndLastUsedBeforeAreMutuallyExclusive(): void { $this->consoleInput->expects($this->exactly(2)) ->method('getArgument') - ->withConsecutive(['uid'], ['id']) - ->willReturnOnConsecutiveCalls('user', 42); + ->willReturnMap([ + ['uid', 'user'], + ['id', '42'] + ]); $this->consoleInput->expects($this->once()) ->method('getOption') diff --git a/tests/Core/Controller/NavigationControllerTest.php b/tests/Core/Controller/NavigationControllerTest.php index 4995bd2fed0..7ebfbc139cd 100644 --- a/tests/Core/Controller/NavigationControllerTest.php +++ b/tests/Core/Controller/NavigationControllerTest.php @@ -42,13 +42,14 @@ class NavigationControllerTest extends TestCase { ); } - public function dataGetNavigation() { + public static function dataGetNavigation(): array { return [ - [false], [true] + [false], + [true], ]; } /** @dataProvider dataGetNavigation */ - public function testGetAppNavigation($absolute): void { + public function testGetAppNavigation(bool $absolute): void { $this->navigationManager->expects($this->once()) ->method('getAll') ->with('link') @@ -59,11 +60,10 @@ class NavigationControllerTest extends TestCase { ->willReturn('http://localhost/'); $this->urlGenerator->expects($this->exactly(2)) ->method('getAbsoluteURL') - ->withConsecutive(['/index.php/apps/files'], ['icon']) - ->willReturnOnConsecutiveCalls( - 'http://localhost/index.php/apps/files', - 'http://localhost/icon' - ); + ->willReturnMap([ + ['/index.php/apps/files', 'http://localhost/index.php/apps/files'], + ['icon', 'http://localhost/icon'], + ]); $actual = $this->controller->getAppsNavigation($absolute); $this->assertInstanceOf(DataResponse::class, $actual); $this->assertEquals('http://localhost/index.php/apps/files', $actual->getData()[0]['href']); @@ -77,7 +77,7 @@ class NavigationControllerTest extends TestCase { } /** @dataProvider dataGetNavigation */ - public function testGetSettingsNavigation($absolute): void { + public function testGetSettingsNavigation(bool $absolute): void { $this->navigationManager->expects($this->once()) ->method('getAll') ->with('settings') @@ -88,14 +88,10 @@ class NavigationControllerTest extends TestCase { ->willReturn('http://localhost/'); $this->urlGenerator->expects($this->exactly(2)) ->method('getAbsoluteURL') - ->withConsecutive( - ['/index.php/settings/user'], - ['/core/img/settings.svg'] - ) - ->willReturnOnConsecutiveCalls( - 'http://localhost/index.php/settings/user', - 'http://localhost/core/img/settings.svg' - ); + ->willReturnMap([ + ['/index.php/settings/user', 'http://localhost/index.php/settings/user'], + ['/core/img/settings.svg', 'http://localhost/core/img/settings.svg'] + ]); $actual = $this->controller->getSettingsNavigation($absolute); $this->assertInstanceOf(DataResponse::class, $actual); $this->assertEquals('http://localhost/index.php/settings/user', $actual->getData()[0]['href']); diff --git a/tests/lib/AppFramework/Http/RequestIdTest.php b/tests/lib/AppFramework/Http/RequestIdTest.php index b3cfe2a8f4a..9cfd3b1785c 100644 --- a/tests/lib/AppFramework/Http/RequestIdTest.php +++ b/tests/lib/AppFramework/Http/RequestIdTest.php @@ -49,11 +49,7 @@ class RequestIdTest extends \Test\TestCase { $this->secureRandom->expects($this->once()) ->method('generate') ->with('20') - ->willReturnOnConsecutiveCalls( - 'GeneratedByNextcloudItself1', - 'GeneratedByNextcloudItself2', - 'GeneratedByNextcloudItself3' - ); + ->willReturn('GeneratedByNextcloudItself1'); $this->assertSame('GeneratedByNextcloudItself1', $requestId->getId()); $this->assertSame('GeneratedByNextcloudItself1', $requestId->getId()); diff --git a/tests/lib/AppFramework/Http/RequestTest.php b/tests/lib/AppFramework/Http/RequestTest.php index fe3614ab88e..781cca256ec 100644 --- a/tests/lib/AppFramework/Http/RequestTest.php +++ b/tests/lib/AppFramework/Http/RequestTest.php @@ -251,7 +251,7 @@ class RequestTest extends \Test\TestCase { $this->assertSame('someothertestvalue', $result['propertyB']); } - public function notJsonDataProvider() { + public static function dataNotJsonData(): array { return [ ['this is not valid json'], ['"just a string"'], @@ -260,9 +260,9 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider notJsonDataProvider + * @dataProvider dataNotJsonData */ - public function testNotJsonPost($testData): void { + public function testNotJsonPost(string $testData): void { global $data; $data = $testData; $vars = [ @@ -544,7 +544,7 @@ class RequestTest extends \Test\TestCase { $this->assertEquals('3', $request->getParams()['id']); } - public function dataGetRemoteAddress(): array { + public static function dataGetRemoteAddress(): array { return [ 'IPv4 without trusted remote' => [ [ @@ -714,14 +714,10 @@ class RequestTest extends \Test\TestCase { public function testGetRemoteAddress(array $headers, array $trustedProxies, array $forwardedForHeaders, string $expected): void { $this->config ->method('getSystemValue') - ->withConsecutive( - ['trusted_proxies'], - ['forwarded_for_headers'], - ) - ->willReturnOnConsecutiveCalls( - $trustedProxies, - $forwardedForHeaders, - ); + ->willReturnMap([ + ['trusted_proxies', [], $trustedProxies], + ['forwarded_for_headers', ['HTTP_X_FORWARDED_FOR'], $forwardedForHeaders], + ]); $request = new Request( [ @@ -736,10 +732,7 @@ class RequestTest extends \Test\TestCase { $this->assertSame($expected, $request->getRemoteAddress()); } - /** - * @return array - */ - public function httpProtocolProvider() { + public static function dataHttpProtocol(): array { return [ // Valid HTTP 1.0 ['HTTP/1.0', 'HTTP/1.0'], @@ -766,7 +759,7 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider httpProtocolProvider + * @dataProvider dataHttpProtocol * * @param mixed $input * @param string $expected @@ -956,7 +949,7 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider userAgentProvider + * @dataProvider dataUserAgent * @param string $testAgent * @param array $userAgent * @param bool $matches @@ -978,7 +971,7 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider userAgentProvider + * @dataProvider dataUserAgent * @param string $testAgent * @param array $userAgent * @param bool $matches @@ -995,10 +988,7 @@ class RequestTest extends \Test\TestCase { $this->assertFalse($request->isUserAgent($userAgent)); } - /** - * @return array - */ - public function userAgentProvider() { + public static function dataUserAgent(): array { return [ [ 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', @@ -1117,7 +1107,7 @@ class RequestTest extends \Test\TestCase { ]; } - public function dataMatchClientVersion(): array { + public static function dataMatchClientVersion(): array { return [ [ 'Mozilla/5.0 (Android) Nextcloud-android/3.24.1', @@ -1373,10 +1363,7 @@ class RequestTest extends \Test\TestCase { $this->assertSame('', $request->getServerHost()); } - /** - * @return array - */ - public function dataGetServerHostTrustedDomain() { + public static function dataGetServerHostTrustedDomain(): array { return [ 'is array' => ['my.trusted.host', ['my.trusted.host']], 'is array but undefined index 0' => ['my.trusted.host', [2 => 'my.trusted.host']], @@ -1387,10 +1374,8 @@ class RequestTest extends \Test\TestCase { /** * @dataProvider dataGetServerHostTrustedDomain - * @param $expected - * @param $trustedDomain */ - public function testGetServerHostTrustedDomain($expected, $trustedDomain): void { + public function testGetServerHostTrustedDomain(string $expected, $trustedDomain): void { $this->config ->method('getSystemValue') ->willReturnCallback(function ($key, $default) use ($trustedDomain) { @@ -1499,7 +1484,7 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider genericPathInfoProvider + * @dataProvider dataGenericPathInfo * @param string $requestUri * @param string $scriptName * @param string $expected @@ -1522,7 +1507,7 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider genericPathInfoProvider + * @dataProvider dataGenericPathInfo * @param string $requestUri * @param string $scriptName * @param string $expected @@ -1545,7 +1530,7 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider rawPathInfoProvider + * @dataProvider dataRawPathInfo * @param string $requestUri * @param string $scriptName * @param string $expected @@ -1568,7 +1553,7 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider pathInfoProvider + * @dataProvider dataPathInfo * @param string $requestUri * @param string $scriptName * @param string $expected @@ -1590,10 +1575,7 @@ class RequestTest extends \Test\TestCase { $this->assertSame($expected, $request->getPathInfo()); } - /** - * @return array - */ - public function genericPathInfoProvider() { + public static function dataGenericPathInfo(): array { return [ ['/core/index.php?XDEBUG_SESSION_START=14600', '/core/index.php', ''], ['/index.php/apps/files/', 'index.php', '/apps/files/'], @@ -1605,19 +1587,13 @@ class RequestTest extends \Test\TestCase { ]; } - /** - * @return array - */ - public function rawPathInfoProvider() { + public static function dataRawPathInfo(): array { return [ ['/foo%2Fbar/subfolder', '', 'foo%2Fbar/subfolder'], ]; } - /** - * @return array - */ - public function pathInfoProvider() { + public static function dataPathInfo(): array { return [ ['/foo%2Fbar/subfolder', '', 'foo/bar/subfolder'], ]; @@ -1645,7 +1621,7 @@ class RequestTest extends \Test\TestCase { $this->assertSame('/test.php', $request->getRequestUri()); } - public function providesGetRequestUriWithOverwriteData() { + public static function dataGetRequestUriWithOverwrite(): array { return [ ['/scriptname.php/some/PathInfo', '/owncloud/', ''], ['/scriptname.php/some/PathInfo', '/owncloud/', '123', '123.123.123.123'], @@ -1653,7 +1629,7 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider providesGetRequestUriWithOverwriteData + * @dataProvider dataGetRequestUriWithOverwrite */ public function testGetRequestUriWithOverwrite($expectedUri, $overwriteWebRoot, $overwriteCondAddr, $remoteAddr = ''): void { $this->config @@ -1665,7 +1641,7 @@ class RequestTest extends \Test\TestCase { ]); $request = $this->getMockBuilder(Request::class) - ->setMethods(['getScriptName']) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -1690,8 +1666,8 @@ class RequestTest extends \Test\TestCase { public function testPassesCSRFCheckWithGet(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'get' => [ @@ -1720,8 +1696,8 @@ class RequestTest extends \Test\TestCase { public function testPassesCSRFCheckWithPost(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'post' => [ @@ -1750,8 +1726,8 @@ class RequestTest extends \Test\TestCase { public function testPassesCSRFCheckWithHeader(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -1780,8 +1756,8 @@ class RequestTest extends \Test\TestCase { public function testPassesCSRFCheckWithGetAndWithoutCookies(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'get' => [ @@ -1804,8 +1780,8 @@ class RequestTest extends \Test\TestCase { public function testPassesCSRFCheckWithPostAndWithoutCookies(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'post' => [ @@ -1828,8 +1804,8 @@ class RequestTest extends \Test\TestCase { public function testPassesCSRFCheckWithHeaderAndWithoutCookies(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -1852,8 +1828,8 @@ class RequestTest extends \Test\TestCase { public function testFailsCSRFCheckWithHeaderAndNotAllChecksPassing(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -1879,8 +1855,8 @@ class RequestTest extends \Test\TestCase { public function testPassesStrictCookieCheckWithAllCookiesAndStrict(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName', 'getCookieParams']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName', 'getCookieParams']) ->setConstructorArgs([ [ 'server' => [ @@ -1911,8 +1887,8 @@ class RequestTest extends \Test\TestCase { public function testFailsStrictCookieCheckWithAllCookiesAndMissingStrict(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName', 'getCookieParams']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName', 'getCookieParams']) ->setConstructorArgs([ [ 'server' => [ @@ -1944,7 +1920,7 @@ class RequestTest extends \Test\TestCase { public function testGetCookieParams(): void { /** @var Request $request */ $request = $this->getMockBuilder(Request::class) - ->setMethods(['getScriptName']) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [], $this->requestId, @@ -1959,8 +1935,8 @@ class RequestTest extends \Test\TestCase { public function testPassesStrictCookieCheckWithAllCookies(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -1984,8 +1960,8 @@ class RequestTest extends \Test\TestCase { public function testPassesStrictCookieCheckWithRandomCookies(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2007,8 +1983,8 @@ class RequestTest extends \Test\TestCase { public function testFailsStrictCookieCheckWithSessionCookie(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2030,8 +2006,8 @@ class RequestTest extends \Test\TestCase { public function testFailsStrictCookieCheckWithRememberMeCookie(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2053,8 +2029,8 @@ class RequestTest extends \Test\TestCase { public function testFailsCSRFCheckWithPostAndWithCookies(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'post' => [ @@ -2080,8 +2056,8 @@ class RequestTest extends \Test\TestCase { public function testFailStrictCookieCheckWithOnlyLaxCookie(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2104,8 +2080,8 @@ class RequestTest extends \Test\TestCase { public function testFailStrictCookieCheckWithOnlyStrictCookie(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2128,8 +2104,8 @@ class RequestTest extends \Test\TestCase { public function testPassesLaxCookieCheck(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2152,8 +2128,8 @@ class RequestTest extends \Test\TestCase { public function testFailsLaxCookieCheckWithOnlyStrictCookie(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2176,8 +2152,8 @@ class RequestTest extends \Test\TestCase { public function testSkipCookieCheckForOCSRequests(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2199,10 +2175,7 @@ class RequestTest extends \Test\TestCase { $this->assertTrue($request->passesStrictCookieCheck()); } - /** - * @return array - */ - public function invalidTokenDataProvider() { + public static function dataInvalidToken(): array { return [ ['InvalidSentToken'], ['InvalidSentToken:InvalidSecret'], @@ -2211,13 +2184,12 @@ class RequestTest extends \Test\TestCase { } /** - * @dataProvider invalidTokenDataProvider - * @param string $invalidToken + * @dataProvider dataInvalidToken */ - public function testPassesCSRFCheckWithInvalidToken($invalidToken): void { + public function testPassesCSRFCheckWithInvalidToken(string $invalidToken): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ @@ -2243,8 +2215,8 @@ class RequestTest extends \Test\TestCase { public function testPassesCSRFCheckWithoutTokenFail(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [], $this->requestId, @@ -2259,8 +2231,8 @@ class RequestTest extends \Test\TestCase { public function testPassesCSRFCheckWithOCSAPIRequestHeader(): void { /** @var Request $request */ - $request = $this->getMockBuilder('\OC\AppFramework\Http\Request') - ->setMethods(['getScriptName']) + $request = $this->getMockBuilder(Request::class) + ->onlyMethods(['getScriptName']) ->setConstructorArgs([ [ 'server' => [ diff --git a/tests/lib/Template/JSCombinerTest.php b/tests/lib/Template/JSCombinerTest.php index 63a52f34047..e4e6594c05c 100644 --- a/tests/lib/Template/JSCombinerTest.php +++ b/tests/lib/Template/JSCombinerTest.php @@ -70,14 +70,10 @@ class JSCombinerTest extends \Test\TestCase { $this->config ->expects($this->exactly(2)) ->method('getValue') - ->withConsecutive( - ['debug'], - ['installed'] - ) - ->willReturnOnConsecutiveCalls( - false, - false - ); + ->willReturnMap([ + ['debug', false], + ['installed', false] + ]); $actual = $this->jsCombiner->process(__DIR__, '/data/combine.json', 'awesomeapp'); $this->assertFalse($actual); @@ -87,14 +83,10 @@ class JSCombinerTest extends \Test\TestCase { $this->config ->expects($this->exactly(2)) ->method('getValue') - ->withConsecutive( - ['debug'], - ['installed'] - ) - ->willReturnOnConsecutiveCalls( - false, - true - ); + ->willReturnMap([ + ['debug', '', false], + ['installed', '', true], + ]); $folder = $this->createMock(ISimpleFolder::class); $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willThrowException(new NotFoundException()); $this->appData->expects($this->once())->method('newFolder')->with('awesomeapp')->willReturn($folder); @@ -127,14 +119,10 @@ class JSCombinerTest extends \Test\TestCase { $this->config ->expects($this->exactly(2)) ->method('getValue') - ->withConsecutive( - ['debug'], - ['installed'] - ) - ->willReturnOnConsecutiveCalls( - false, - true - ); + ->willReturnMap([ + ['debug', '', false], + ['installed', '', true], + ]); $folder = $this->createMock(ISimpleFolder::class); $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willReturn($folder); $file = $this->createMock(ISimpleFile::class); @@ -165,14 +153,10 @@ class JSCombinerTest extends \Test\TestCase { $this->config ->expects($this->exactly(2)) ->method('getValue') - ->withConsecutive( - ['debug'], - ['installed'] - ) - ->willReturnOnConsecutiveCalls( - false, - true - ); + ->willReturnMap([ + ['debug', '', false], + ['installed', '', true], + ]); $folder = $this->createMock(ISimpleFolder::class); $this->appData->expects($this->once())->method('getFolder')->with('awesomeapp')->willReturn($folder); $file = $this->createMock(ISimpleFile::class); @@ -206,14 +190,10 @@ class JSCombinerTest extends \Test\TestCase { $this->config ->expects($this->exactly(2)) ->method('getValue') - ->withConsecutive( - ['debug'], - ['installed'] - ) - ->willReturnOnConsecutiveCalls( - false, - true - ); + ->willReturnMap([ + ['debug', '', false], + ['installed', '', true], + ]); $folder = $this->createMock(ISimpleFolder::class); $this->appData->expects($this->once()) ->method('getFolder') @@ -395,15 +375,11 @@ class JSCombinerTest extends \Test\TestCase { $folder->expects($this->exactly(3)) ->method('getFile') - ->withConsecutive( - [$fileName], - [$fileName . '.deps'], - [$fileName . '.gzip'] - )->willReturnOnConsecutiveCalls( - $file, - $depsFile, - $gzFile - ); + ->willReturnMap([ + [$fileName, $file], + [$fileName . '.deps', $depsFile], + [$fileName . '.gzip', $gzFile] + ]); $file->expects($this->once()) ->method('putContent') @@ -484,7 +460,7 @@ var b = \'world\'; $this->assertTrue($actual); } - public function dataGetCachedSCSS() { + public static function dataGetCachedSCSS(): array { return [ ['awesomeapp', 'core/js/foo.json', '/js/core/foo.js'], ['files', 'apps/files/js/foo.json', '/js/files/foo.js'] diff --git a/tests/lib/User/ManagerTest.php b/tests/lib/User/ManagerTest.php index 53f57eee086..53d63ea6758 100644 --- a/tests/lib/User/ManagerTest.php +++ b/tests/lib/User/ManagerTest.php @@ -305,7 +305,7 @@ class ManagerTest extends TestCase { $this->assertEquals('foo3', array_shift($result)->getUID()); } - public function dataCreateUserInvalid() { + public static function dataCreateUserInvalid(): array { return [ ['te?st', 'foo', 'Only the following characters are allowed in a username:' . ' "a-z", "A-Z", "0-9", spaces and "_.@-\'"'], @@ -760,16 +760,11 @@ class ManagerTest extends TestCase { $backend = $this->createMock(\Test\Util\User\Dummy::class); $backend->expects($this->exactly(3)) ->method('userExists') - ->withConsecutive( - [$this->equalTo('uid1')], - [$this->equalTo('uid99')], - [$this->equalTo('uid2')] - ) - ->willReturnOnConsecutiveCalls( - true, - false, - true - ); + ->willReturnMap([ + ['uid1', true], + ['uid99', false], + ['uid2', true] + ]); $manager = new \OC\User\Manager($config, $this->cacheFactory, $this->eventDispatcher, $this->logger); $manager->registerBackend($backend); |