diff options
Diffstat (limited to 'apps/dav/tests/unit/Upload')
-rw-r--r-- | apps/dav/tests/unit/Upload/AssemblyStreamTest.php | 133 | ||||
-rw-r--r-- | apps/dav/tests/unit/Upload/ChunkingPluginTest.php | 146 | ||||
-rw-r--r-- | apps/dav/tests/unit/Upload/FutureFileTest.php | 63 | ||||
-rw-r--r-- | apps/dav/tests/unit/Upload/UploadAutoMkcolPluginTest.php | 133 |
4 files changed, 287 insertions, 188 deletions
diff --git a/apps/dav/tests/unit/Upload/AssemblyStreamTest.php b/apps/dav/tests/unit/Upload/AssemblyStreamTest.php index c7d2fea2871..ec5d0a9ab5b 100644 --- a/apps/dav/tests/unit/Upload/AssemblyStreamTest.php +++ b/apps/dav/tests/unit/Upload/AssemblyStreamTest.php @@ -1,66 +1,57 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Markus Goetz <markus@woboq.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\DAV\Tests\unit\Upload; +use OCA\DAV\Upload\AssemblyStream; use Sabre\DAV\File; class AssemblyStreamTest extends \Test\TestCase { - /** - * @dataProvider providesNodes() - */ - public function testGetContents($expected, $nodes) { - $stream = \OCA\DAV\Upload\AssemblyStream::wrap($nodes); + #[\PHPUnit\Framework\Attributes\DataProvider('providesNodes')] + public function testGetContents(string $expected, array $nodeData): void { + $nodes = []; + foreach ($nodeData as $data) { + $nodes[] = $this->buildNode(...$data); + } + $stream = AssemblyStream::wrap($nodes); $content = stream_get_contents($stream); $this->assertEquals($expected, $content); } - /** - * @dataProvider providesNodes() - */ - public function testGetContentsFread($expected, $nodes) { - $stream = \OCA\DAV\Upload\AssemblyStream::wrap($nodes); + #[\PHPUnit\Framework\Attributes\DataProvider('providesNodes')] + public function testGetContentsFread(string $expected, array $nodeData, int $chunkLength = 3): void { + $nodes = []; + foreach ($nodeData as $data) { + $nodes[] = $this->buildNode(...$data); + } + $stream = AssemblyStream::wrap($nodes); $content = ''; while (!feof($stream)) { - $content .= fread($stream, 3); + $chunk = fread($stream, $chunkLength); + $content .= $chunk; + if ($chunkLength !== 3) { + $this->assertEquals($chunkLength, strlen($chunk)); + } } $this->assertEquals($expected, $content); } - /** - * @dataProvider providesNodes() - */ - public function testSeek($expected, $nodes) { - $stream = \OCA\DAV\Upload\AssemblyStream::wrap($nodes); + #[\PHPUnit\Framework\Attributes\DataProvider('providesNodes')] + public function testSeek(string $expected, array $nodeData): void { + $nodes = []; + foreach ($nodeData as $data) { + $nodes[] = $this->buildNode(...$data); + } + + $stream = AssemblyStream::wrap($nodes); $offset = floor(strlen($expected) * 0.6); if (fseek($stream, $offset) === -1) { @@ -71,63 +62,75 @@ class AssemblyStreamTest extends \Test\TestCase { $this->assertEquals(substr($expected, $offset), $content); } - public function providesNodes() { - $data8k = $this->makeData(8192); - $dataLess8k = $this->makeData(8191); + public static function providesNodes(): array { + $data8k = self::makeData(8192); + $dataLess8k = self::makeData(8191); $tonofnodes = []; - $tonofdata = ""; + $tonofdata = ''; for ($i = 0; $i < 101; $i++) { - $thisdata = rand(0,100); // variable length and content + $thisdata = random_int(0, 100); // variable length and content $tonofdata .= $thisdata; - array_push($tonofnodes, $this->buildNode($i,$thisdata)); + $tonofnodes[] = [(string)$i, (string)$thisdata]; } return[ 'one node zero bytes' => [ '', [ - $this->buildNode('0', '') + ['0', ''], ]], 'one node only' => [ '1234567890', [ - $this->buildNode('0', '1234567890') + ['0', '1234567890'], ]], 'one node buffer boundary' => [ $data8k, [ - $this->buildNode('0', $data8k) + ['0', $data8k], ]], 'two nodes' => [ '1234567890', [ - $this->buildNode('1', '67890'), - $this->buildNode('0', '12345') + ['1', '67890'], + ['0', '12345'], ]], 'two nodes end on buffer boundary' => [ $data8k . $data8k, [ - $this->buildNode('1', $data8k), - $this->buildNode('0', $data8k) + ['1', $data8k], + ['0', $data8k], ]], 'two nodes with one on buffer boundary' => [ $data8k . $dataLess8k, [ - $this->buildNode('1', $dataLess8k), - $this->buildNode('0', $data8k) + ['1', $dataLess8k], + ['0', $data8k], ]], 'two nodes on buffer boundary plus one byte' => [ $data8k . 'X' . $data8k, [ - $this->buildNode('1', $data8k), - $this->buildNode('0', $data8k . 'X') + ['1', $data8k], + ['0', $data8k . 'X'], ]], 'two nodes on buffer boundary plus one byte at the end' => [ $data8k . $data8k . 'X', [ - $this->buildNode('1', $data8k . 'X'), - $this->buildNode('0', $data8k) + ['1', $data8k . 'X'], + ['0', $data8k], ]], 'a ton of nodes' => [ $tonofdata, $tonofnodes - ] + ], + 'one read over multiple nodes' => [ + '1234567890', [ + ['0', '1234'], + ['1', '5678'], + ['2', '90'], + ], 10], + 'two reads over multiple nodes' => [ + '1234567890', [ + ['0', '1234'], + ['1', '5678'], + ['2', '90'], + ], 5], ]; } - private function makeData($count) { + private static function makeData(int $count): string { $data = ''; $base = '1234567890'; $j = 0; @@ -141,10 +144,10 @@ class AssemblyStreamTest extends \Test\TestCase { return $data; } - private function buildNode($name, $data) { + private function buildNode(string $name, string $data) { $node = $this->getMockBuilder(File::class) - ->setMethods(['getName', 'get', 'getSize']) - ->getMockForAbstractClass(); + ->onlyMethods(['getName', 'get', 'getSize']) + ->getMock(); $node->expects($this->any()) ->method('getName') diff --git a/apps/dav/tests/unit/Upload/ChunkingPluginTest.php b/apps/dav/tests/unit/Upload/ChunkingPluginTest.php index 766b3e1f457..00ed7657dd3 100644 --- a/apps/dav/tests/unit/Upload/ChunkingPluginTest.php +++ b/apps/dav/tests/unit/Upload/ChunkingPluginTest.php @@ -1,70 +1,34 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2017, ownCloud GmbH - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * @author Julius Härtl <jus@bitgrid.net> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2017 ownCloud GmbH + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\DAV\Tests\unit\Upload; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Upload\ChunkingPlugin; use OCA\DAV\Upload\FutureFile; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Exception\NotFound; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Test\TestCase; class ChunkingPluginTest extends TestCase { - - - /** - * @var \Sabre\DAV\Server | \PHPUnit\Framework\MockObject\MockObject - */ - private $server; - - /** - * @var \Sabre\DAV\Tree | \PHPUnit\Framework\MockObject\MockObject - */ - private $tree; - - /** - * @var ChunkingPlugin - */ - private $plugin; - /** @var RequestInterface | \PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var ResponseInterface | \PHPUnit\Framework\MockObject\MockObject */ - private $response; + private \Sabre\DAV\Server&MockObject $server; + private \Sabre\DAV\Tree&MockObject $tree; + private ChunkingPlugin $plugin; + private RequestInterface&MockObject $request; + private ResponseInterface&MockObject $response; protected function setUp(): void { parent::setUp(); - $this->server = $this->getMockBuilder('\Sabre\DAV\Server') - ->disableOriginalConstructor() - ->getMock(); - $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') - ->disableOriginalConstructor() - ->getMock(); + $this->server = $this->createMock('\Sabre\DAV\Server'); + $this->tree = $this->createMock('\Sabre\DAV\Tree'); $this->server->tree = $this->tree; $this->plugin = new ChunkingPlugin(); @@ -77,7 +41,7 @@ class ChunkingPluginTest extends TestCase { $this->plugin->initialize($this->server); } - public function testBeforeMoveFutureFileSkip() { + public function testBeforeMoveFutureFileSkip(): void { $node = $this->createMock(Directory::class); $this->tree->expects($this->any()) @@ -90,41 +54,45 @@ class ChunkingPluginTest extends TestCase { $this->assertNull($this->plugin->beforeMove('source', 'target')); } - public function testBeforeMoveDestinationIsDirectory() { + public function testBeforeMoveDestinationIsDirectory(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('The given destination target is a directory.'); $sourceNode = $this->createMock(FutureFile::class); $targetNode = $this->createMock(Directory::class); - $this->tree->expects($this->at(0)) + $this->tree->expects($this->exactly(2)) ->method('getNodeForPath') - ->with('source') - ->willReturn($sourceNode); - $this->tree->expects($this->at(1)) - ->method('getNodeForPath') - ->with('target') - ->willReturn($targetNode); + ->willReturnMap([ + ['source', $sourceNode], + ['target', $targetNode], + ]); $this->response->expects($this->never()) ->method('setStatus'); $this->assertNull($this->plugin->beforeMove('source', 'target')); } - public function testBeforeMoveFutureFileSkipNonExisting() { + public function testBeforeMoveFutureFileSkipNonExisting(): void { $sourceNode = $this->createMock(FutureFile::class); $sourceNode->expects($this->once()) ->method('getSize') ->willReturn(4); - $this->tree->expects($this->at(0)) - ->method('getNodeForPath') - ->with('source') - ->willReturn($sourceNode); - $this->tree->expects($this->at(1)) + $calls = [ + ['source', $sourceNode], + ['target', new NotFound()], + ]; + $this->tree->expects($this->exactly(2)) ->method('getNodeForPath') - ->with('target') - ->willThrowException(new NotFound()); + ->willReturnCallback(function (string $path) use (&$calls) { + $expected = array_shift($calls); + $this->assertSame($expected[0], $path); + if ($expected[1] instanceof \Throwable) { + throw $expected[1]; + } + return $expected[1]; + }); $this->tree->expects($this->any()) ->method('nodeExists') ->with('target') @@ -143,20 +111,27 @@ class ChunkingPluginTest extends TestCase { $this->assertFalse($this->plugin->beforeMove('source', 'target')); } - public function testBeforeMoveFutureFileMoveIt() { + public function testBeforeMoveFutureFileMoveIt(): void { $sourceNode = $this->createMock(FutureFile::class); $sourceNode->expects($this->once()) ->method('getSize') ->willReturn(4); - $this->tree->expects($this->at(0)) - ->method('getNodeForPath') - ->with('source') - ->willReturn($sourceNode); - $this->tree->expects($this->at(1)) + $calls = [ + ['source', $sourceNode], + ['target', new NotFound()], + ]; + $this->tree->expects($this->exactly(2)) ->method('getNodeForPath') - ->with('target') - ->willThrowException(new NotFound()); + ->willReturnCallback(function (string $path) use (&$calls) { + $expected = array_shift($calls); + $this->assertSame($expected[0], $path); + if ($expected[1] instanceof \Throwable) { + throw $expected[1]; + } + return $expected[1]; + }); + $this->tree->expects($this->any()) ->method('nodeExists') ->with('target') @@ -180,7 +155,7 @@ class ChunkingPluginTest extends TestCase { } - public function testBeforeMoveSizeIsWrong() { + public function testBeforeMoveSizeIsWrong(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('Chunks on server do not sum up to 4 but to 3 bytes'); @@ -189,14 +164,21 @@ class ChunkingPluginTest extends TestCase { ->method('getSize') ->willReturn(3); - $this->tree->expects($this->at(0)) + $calls = [ + ['source', $sourceNode], + ['target', new NotFound()], + ]; + $this->tree->expects($this->exactly(2)) ->method('getNodeForPath') - ->with('source') - ->willReturn($sourceNode); - $this->tree->expects($this->at(1)) - ->method('getNodeForPath') - ->with('target') - ->willThrowException(new NotFound()); + ->willReturnCallback(function (string $path) use (&$calls) { + $expected = array_shift($calls); + $this->assertSame($expected[0], $path); + if ($expected[1] instanceof \Throwable) { + throw $expected[1]; + } + return $expected[1]; + }); + $this->request->expects($this->once()) ->method('getHeader') ->with('OC-Total-Length') diff --git a/apps/dav/tests/unit/Upload/FutureFileTest.php b/apps/dav/tests/unit/Upload/FutureFileTest.php index 0c276167e1c..1409df937c0 100644 --- a/apps/dav/tests/unit/Upload/FutureFileTest.php +++ b/apps/dav/tests/unit/Upload/FutureFileTest.php @@ -1,100 +1,81 @@ <?php + +declare(strict_types=1); /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\DAV\Tests\unit\Upload; use OCA\DAV\Connector\Sabre\Directory; +use OCA\DAV\Upload\FutureFile; class FutureFileTest extends \Test\TestCase { - public function testGetContentType() { + public function testGetContentType(): void { $f = $this->mockFutureFile(); $this->assertEquals('application/octet-stream', $f->getContentType()); } - public function testGetETag() { + public function testGetETag(): void { $f = $this->mockFutureFile(); $this->assertEquals('1234567890', $f->getETag()); } - public function testGetName() { + public function testGetName(): void { $f = $this->mockFutureFile(); $this->assertEquals('foo.txt', $f->getName()); } - public function testGetLastModified() { + public function testGetLastModified(): void { $f = $this->mockFutureFile(); $this->assertEquals(12121212, $f->getLastModified()); } - public function testGetSize() { + public function testGetSize(): void { $f = $this->mockFutureFile(); $this->assertEquals(0, $f->getSize()); } - public function testGet() { + public function testGet(): void { $f = $this->mockFutureFile(); $stream = $f->get(); $this->assertTrue(is_resource($stream)); } - public function testDelete() { + public function testDelete(): void { $d = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() - ->setMethods(['delete']) + ->onlyMethods(['delete']) ->getMock(); $d->expects($this->once()) ->method('delete'); - $f = new \OCA\DAV\Upload\FutureFile($d, 'foo.txt'); + $f = new FutureFile($d, 'foo.txt'); $f->delete(); } - - public function testPut() { + + public function testPut(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $f = $this->mockFutureFile(); $f->put(''); } - - public function testSetName() { + + public function testSetName(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $f = $this->mockFutureFile(); $f->setName(''); } - /** - * @return \OCA\DAV\Upload\FutureFile - */ - private function mockFutureFile() { + private function mockFutureFile(): FutureFile { $d = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() - ->setMethods(['getETag', 'getLastModified', 'getChildren']) + ->onlyMethods(['getETag', 'getLastModified', 'getChildren']) ->getMock(); $d->expects($this->any()) @@ -109,6 +90,6 @@ class FutureFileTest extends \Test\TestCase { ->method('getChildren') ->willReturn([]); - return new \OCA\DAV\Upload\FutureFile($d, 'foo.txt'); + return new FutureFile($d, 'foo.txt'); } } diff --git a/apps/dav/tests/unit/Upload/UploadAutoMkcolPluginTest.php b/apps/dav/tests/unit/Upload/UploadAutoMkcolPluginTest.php new file mode 100644 index 00000000000..baae839c8da --- /dev/null +++ b/apps/dav/tests/unit/Upload/UploadAutoMkcolPluginTest.php @@ -0,0 +1,133 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\Upload; + +use Generator; +use OCA\DAV\Upload\UploadAutoMkcolPlugin; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\ICollection; +use Sabre\DAV\INode; +use Sabre\DAV\Server; +use Sabre\DAV\Tree; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Test\TestCase; + +class UploadAutoMkcolPluginTest extends TestCase { + + private Tree&MockObject $tree; + private RequestInterface&MockObject $request; + private ResponseInterface&MockObject $response; + + public static function dataMissingHeaderShouldReturnTrue(): Generator { + yield 'missing X-NC-WebDAV-Auto-Mkcol header' => [null]; + yield 'empty X-NC-WebDAV-Auto-Mkcol header' => ['']; + yield 'invalid X-NC-WebDAV-Auto-Mkcol header' => ['enable']; + } + + public function testBeforeMethodWithRootNodeNotAnICollectionShouldReturnTrue(): void { + $this->request->method('getHeader')->willReturn('1'); + $this->request->expects(self::once()) + ->method('getPath') + ->willReturn('/non-relevant/path.txt'); + $this->tree->expects(self::once()) + ->method('nodeExists') + ->with('/non-relevant') + ->willReturn(false); + + $mockNode = $this->getMockBuilder(INode::class); + $this->tree->expects(self::once()) + ->method('getNodeForPath') + ->willReturn($mockNode); + + $return = $this->plugin->beforeMethod($this->request, $this->response); + $this->assertTrue($return); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataMissingHeaderShouldReturnTrue')] + public function testBeforeMethodWithMissingHeaderShouldReturnTrue(?string $header): void { + $this->request->expects(self::once()) + ->method('getHeader') + ->with('X-NC-WebDAV-Auto-Mkcol') + ->willReturn($header); + + $this->request->expects(self::never()) + ->method('getPath'); + + $return = $this->plugin->beforeMethod($this->request, $this->response); + self::assertTrue($return); + } + + public function testBeforeMethodWithExistingPathShouldReturnTrue(): void { + $this->request->method('getHeader')->willReturn('1'); + $this->request->expects(self::once()) + ->method('getPath') + ->willReturn('/files/user/deep/image.jpg'); + $this->tree->expects(self::once()) + ->method('nodeExists') + ->with('/files/user/deep') + ->willReturn(true); + + $this->tree->expects(self::never()) + ->method('getNodeForPath'); + + $return = $this->plugin->beforeMethod($this->request, $this->response); + self::assertTrue($return); + } + + public function testBeforeMethodShouldSucceed(): void { + $this->request->method('getHeader')->willReturn('1'); + $this->request->expects(self::once()) + ->method('getPath') + ->willReturn('/files/user/my/deep/path/image.jpg'); + $this->tree->expects(self::once()) + ->method('nodeExists') + ->with('/files/user/my/deep/path') + ->willReturn(false); + + $mockNode = $this->createMock(ICollection::class); + $this->tree->expects(self::once()) + ->method('getNodeForPath') + ->with('/files') + ->willReturn($mockNode); + $mockNode->expects(self::exactly(4)) + ->method('childExists') + ->willReturnMap([ + ['user', true], + ['my', true], + ['deep', false], + ['path', false], + ]); + $mockNode->expects(self::exactly(2)) + ->method('createDirectory'); + $mockNode->expects(self::exactly(4)) + ->method('getChild') + ->willReturn($mockNode); + + $return = $this->plugin->beforeMethod($this->request, $this->response); + self::assertTrue($return); + } + + protected function setUp(): void { + parent::setUp(); + + $server = $this->createMock(Server::class); + $this->tree = $this->createMock(Tree::class); + + $server->tree = $this->tree; + $this->plugin = new UploadAutoMkcolPlugin(); + + $this->request = $this->createMock(RequestInterface::class); + $this->response = $this->createMock(ResponseInterface::class); + $server->httpRequest = $this->request; + $server->httpResponse = $this->response; + + $this->plugin->initialize($server); + } +} |