aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/tests/unit/Files
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/tests/unit/Files')
-rw-r--r--apps/dav/tests/unit/Files/FileSearchBackendTest.php421
-rw-r--r--apps/dav/tests/unit/Files/MultipartRequestParserTest.php322
-rw-r--r--apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php258
3 files changed, 1001 insertions, 0 deletions
diff --git a/apps/dav/tests/unit/Files/FileSearchBackendTest.php b/apps/dav/tests/unit/Files/FileSearchBackendTest.php
new file mode 100644
index 00000000000..c6d6f85347b
--- /dev/null
+++ b/apps/dav/tests/unit/Files/FileSearchBackendTest.php
@@ -0,0 +1,421 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\DAV\Tests\unit\Files;
+
+use OC\Files\Search\SearchComparison;
+use OC\Files\Search\SearchQuery;
+use OC\Files\View;
+use OCA\DAV\Connector\Sabre\Directory;
+use OCA\DAV\Connector\Sabre\File;
+use OCA\DAV\Connector\Sabre\FilesPlugin;
+use OCA\DAV\Connector\Sabre\ObjectTree;
+use OCA\DAV\Connector\Sabre\Server;
+use OCA\DAV\Files\FileSearchBackend;
+use OCP\Files\FileInfo;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\Search\ISearchBinaryOperator;
+use OCP\Files\Search\ISearchComparison;
+use OCP\Files\Search\ISearchQuery;
+use OCP\FilesMetadata\IFilesMetadataManager;
+use OCP\IUser;
+use OCP\Share\IManager;
+use PHPUnit\Framework\MockObject\MockObject;
+use SearchDAV\Backend\SearchPropertyDefinition;
+use SearchDAV\Query\Limit;
+use SearchDAV\Query\Literal;
+use SearchDAV\Query\Operator;
+use SearchDAV\Query\Query;
+use SearchDAV\Query\Scope;
+use Test\TestCase;
+
+class FileSearchBackendTest extends TestCase {
+ private ObjectTree&MockObject $tree;
+ private Server&MockObject $server;
+ private IUser&MockObject $user;
+ private IRootFolder&MockObject $rootFolder;
+ private IManager&MockObject $shareManager;
+ private View&MockObject $view;
+ private Folder&MockObject $searchFolder;
+ private Directory&MockObject $davFolder;
+ private FileSearchBackend $search;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->user = $this->createMock(IUser::class);
+ $this->user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('test');
+
+ $this->tree = $this->createMock(ObjectTree::class);
+ $this->server = $this->createMock(Server::class);
+ $this->view = $this->createMock(View::class);
+ $this->rootFolder = $this->createMock(IRootFolder::class);
+ $this->shareManager = $this->createMock(IManager::class);
+ $this->searchFolder = $this->createMock(Folder::class);
+ $fileInfo = $this->createMock(FileInfo::class);
+ $this->davFolder = $this->createMock(Directory::class);
+
+ $this->view->expects($this->any())
+ ->method('getRoot')
+ ->willReturn('');
+
+ $this->view->expects($this->any())
+ ->method('getRelativePath')
+ ->willReturnArgument(0);
+
+ $this->davFolder->expects($this->any())
+ ->method('getFileInfo')
+ ->willReturn($fileInfo);
+
+ $this->rootFolder->expects($this->any())
+ ->method('get')
+ ->willReturn($this->searchFolder);
+
+ $filesMetadataManager = $this->createMock(IFilesMetadataManager::class);
+
+ $this->search = new FileSearchBackend($this->server, $this->tree, $this->user, $this->rootFolder, $this->shareManager, $this->view, $filesMetadataManager);
+ }
+
+ public function testSearchFilename(): void {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ $this->searchFolder->expects($this->once())
+ ->method('search')
+ ->with(new SearchQuery(
+ new SearchComparison(
+ ISearchComparison::COMPARE_EQUAL,
+ 'name',
+ 'foo'
+ ),
+ 0,
+ 0,
+ [],
+ $this->user
+ ))
+ ->willReturn([
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path'),
+ ]);
+
+ $query = $this->getBasicQuery(Operator::OPERATION_EQUAL, '{DAV:}displayname', 'foo');
+ $result = $this->search->search($query);
+
+ $this->assertCount(1, $result);
+ $this->assertEquals('/files/test/test/path', $result[0]->href);
+ }
+
+ public function testSearchMimetype(): void {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ $this->searchFolder->expects($this->once())
+ ->method('search')
+ ->with(new SearchQuery(
+ new SearchComparison(
+ ISearchComparison::COMPARE_EQUAL,
+ 'mimetype',
+ 'foo'
+ ),
+ 0,
+ 0,
+ [],
+ $this->user
+ ))
+ ->willReturn([
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path'),
+ ]);
+
+ $query = $this->getBasicQuery(Operator::OPERATION_EQUAL, '{DAV:}getcontenttype', 'foo');
+ $result = $this->search->search($query);
+
+ $this->assertCount(1, $result);
+ $this->assertEquals('/files/test/test/path', $result[0]->href);
+ }
+
+ public function testSearchSize(): void {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ $this->searchFolder->expects($this->once())
+ ->method('search')
+ ->with(new SearchQuery(
+ new SearchComparison(
+ ISearchComparison::COMPARE_GREATER_THAN,
+ 'size',
+ 10
+ ),
+ 0,
+ 0,
+ [],
+ $this->user
+ ))
+ ->willReturn([
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path'),
+ ]);
+
+ $query = $this->getBasicQuery(Operator::OPERATION_GREATER_THAN, FilesPlugin::SIZE_PROPERTYNAME, 10);
+ $result = $this->search->search($query);
+
+ $this->assertCount(1, $result);
+ $this->assertEquals('/files/test/test/path', $result[0]->href);
+ }
+
+ public function testSearchMtime(): void {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ $this->searchFolder->expects($this->once())
+ ->method('search')
+ ->with(new SearchQuery(
+ new SearchComparison(
+ ISearchComparison::COMPARE_GREATER_THAN,
+ 'mtime',
+ 10
+ ),
+ 0,
+ 0,
+ [],
+ $this->user
+ ))
+ ->willReturn([
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path'),
+ ]);
+
+ $query = $this->getBasicQuery(Operator::OPERATION_GREATER_THAN, '{DAV:}getlastmodified', 10);
+ $result = $this->search->search($query);
+
+ $this->assertCount(1, $result);
+ $this->assertEquals('/files/test/test/path', $result[0]->href);
+ }
+
+ public function testSearchIsCollection(): void {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ $this->searchFolder->expects($this->once())
+ ->method('search')
+ ->with(new SearchQuery(
+ new SearchComparison(
+ ISearchComparison::COMPARE_EQUAL,
+ 'mimetype',
+ FileInfo::MIMETYPE_FOLDER
+ ),
+ 0,
+ 0,
+ [],
+ $this->user
+ ))
+ ->willReturn([
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path'),
+ ]);
+
+ $query = $this->getBasicQuery(Operator::OPERATION_IS_COLLECTION, 'yes');
+ $result = $this->search->search($query);
+
+ $this->assertCount(1, $result);
+ $this->assertEquals('/files/test/test/path', $result[0]->href);
+ }
+
+
+ public function testSearchInvalidProp(): void {
+ $this->expectException(\InvalidArgumentException::class);
+
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ $this->searchFolder->expects($this->never())
+ ->method('search');
+
+ $query = $this->getBasicQuery(Operator::OPERATION_EQUAL, '{DAV:}getetag', 'foo');
+ $this->search->search($query);
+ }
+
+ private function getBasicQuery(string $type, string $property, int|string|null $value = null) {
+ $scope = new Scope('/', 'infinite');
+ $scope->path = '/';
+ $from = [$scope];
+ $orderBy = [];
+ $select = [];
+ if (is_null($value)) {
+ $where = new Operator(
+ $type,
+ [new Literal($property)]
+ );
+ } else {
+ $where = new Operator(
+ $type,
+ [new SearchPropertyDefinition($property, true, true, true), new Literal($value)]
+ );
+ }
+ $limit = new Limit();
+
+ return new Query($select, $from, $where, $orderBy, $limit);
+ }
+
+
+ public function testSearchNonFolder(): void {
+ $this->expectException(\InvalidArgumentException::class);
+
+ $davNode = $this->createMock(File::class);
+
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($davNode);
+
+ $query = $this->getBasicQuery(Operator::OPERATION_EQUAL, '{DAV:}displayname', 'foo');
+ $this->search->search($query);
+ }
+
+ public function testSearchLimitOwnerBasic(): void {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ /** @var ISearchQuery|null $receivedQuery */
+ $receivedQuery = null;
+ $this->searchFolder
+ ->method('search')
+ ->willReturnCallback(function ($query) use (&$receivedQuery) {
+ $receivedQuery = $query;
+ return [
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path'),
+ ];
+ });
+
+ $query = $this->getBasicQuery(Operator::OPERATION_EQUAL, FilesPlugin::OWNER_ID_PROPERTYNAME, $this->user->getUID());
+ $this->search->search($query);
+
+ $this->assertNotNull($receivedQuery);
+ $this->assertTrue($receivedQuery->limitToHome());
+
+ /** @var ISearchBinaryOperator $operator */
+ $operator = $receivedQuery->getSearchOperation();
+ $this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
+ $this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
+ $this->assertEmpty($operator->getArguments());
+ }
+
+ public function testSearchLimitOwnerNested(): void {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ /** @var ISearchQuery|null $receivedQuery */
+ $receivedQuery = null;
+ $this->searchFolder
+ ->method('search')
+ ->willReturnCallback(function ($query) use (&$receivedQuery) {
+ $receivedQuery = $query;
+ return [
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path'),
+ ];
+ });
+
+ $query = $this->getBasicQuery(Operator::OPERATION_EQUAL, FilesPlugin::OWNER_ID_PROPERTYNAME, $this->user->getUID());
+ $query->where = new Operator(
+ Operator::OPERATION_AND,
+ [
+ new Operator(
+ Operator::OPERATION_EQUAL,
+ [new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new Literal('image/png')]
+ ),
+ new Operator(
+ Operator::OPERATION_EQUAL,
+ [new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, true), new Literal($this->user->getUID())]
+ ),
+ ]
+ );
+ $this->search->search($query);
+
+ $this->assertNotNull($receivedQuery);
+ $this->assertTrue($receivedQuery->limitToHome());
+
+ /** @var ISearchBinaryOperator $operator */
+ $operator = $receivedQuery->getSearchOperation();
+ $this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
+ $this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
+ $this->assertCount(2, $operator->getArguments());
+
+ /** @var ISearchBinaryOperator $operator */
+ $operator = $operator->getArguments()[1];
+ $this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
+ $this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
+ $this->assertEmpty($operator->getArguments());
+ }
+
+ public function testSearchOperatorLimit(): void {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ $innerOperator = new Operator(
+ Operator::OPERATION_EQUAL,
+ [new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new Literal('image/png')]
+ );
+ // 5 child operators
+ $level1Operator = new Operator(
+ Operator::OPERATION_AND,
+ [
+ $innerOperator,
+ $innerOperator,
+ $innerOperator,
+ $innerOperator,
+ $innerOperator,
+ ]
+ );
+ // 5^2 = 25 child operators
+ $level2Operator = new Operator(
+ Operator::OPERATION_AND,
+ [
+ $level1Operator,
+ $level1Operator,
+ $level1Operator,
+ $level1Operator,
+ $level1Operator,
+ ]
+ );
+ // 5^3 = 125 child operators
+ $level3Operator = new Operator(
+ Operator::OPERATION_AND,
+ [
+ $level2Operator,
+ $level2Operator,
+ $level2Operator,
+ $level2Operator,
+ $level2Operator,
+ ]
+ );
+
+ $query = $this->getBasicQuery(Operator::OPERATION_EQUAL, FilesPlugin::OWNER_ID_PROPERTYNAME, $this->user->getUID());
+ $query->where = $level3Operator;
+ $this->expectException(\InvalidArgumentException::class);
+ $this->search->search($query);
+ }
+
+ public function testPreloadPropertyFor(): void {
+ $node1 = $this->createMock(File::class);
+ $node2 = $this->createMock(Directory::class);
+ $nodes = [$node1, $node2];
+ $requestProperties = ['{DAV:}getcontenttype', '{DAV:}getlastmodified'];
+
+ $this->server->expects($this->once())
+ ->method('emit')
+ ->with('preloadProperties', [$nodes, $requestProperties]);
+
+ $this->search->preloadPropertyFor($nodes, $requestProperties);
+ }
+}
diff --git a/apps/dav/tests/unit/Files/MultipartRequestParserTest.php b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php
new file mode 100644
index 00000000000..dc0e884f07c
--- /dev/null
+++ b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php
@@ -0,0 +1,322 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace OCA\DAV\Tests\unit\Files;
+
+use OCA\DAV\BulkUpload\MultipartRequestParser;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Sabre\HTTP\RequestInterface;
+use Test\TestCase;
+
+class MultipartRequestParserTest extends TestCase {
+
+ protected LoggerInterface&MockObject $logger;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->logger = $this->createMock(LoggerInterface::class);
+ }
+
+ private static function getValidBodyObject(): array {
+ return [
+ [
+ 'headers' => [
+ 'Content-Length' => 7,
+ 'X-File-MD5' => '4f2377b4d911f7ec46325fe603c3af03',
+ 'OC-Checksum' => 'md5:4f2377b4d911f7ec46325fe603c3af03',
+ 'X-File-Path' => '/coucou.txt'
+ ],
+ 'content' => "Coucou\n"
+ ]
+ ];
+ }
+
+ private function getMultipartParser(array $parts, array $headers = [], string $boundary = 'boundary_azertyuiop'): MultipartRequestParser {
+ /** @var RequestInterface&MockObject $request */
+ $request = $this->getMockBuilder(RequestInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $headers = array_merge(['Content-Type' => 'multipart/related; boundary=' . $boundary], $headers);
+ $request->expects($this->any())
+ ->method('getHeader')
+ ->willReturnCallback(function (string $key) use (&$headers) {
+ return $headers[$key];
+ });
+
+ $body = '';
+ foreach ($parts as $part) {
+ $body .= '--' . $boundary . "\r\n";
+
+ foreach ($part['headers'] as $headerKey => $headerPart) {
+ $body .= $headerKey . ': ' . $headerPart . "\r\n";
+ }
+
+ $body .= "\r\n";
+ $body .= $part['content'] . "\r\n";
+ }
+
+ $body .= '--' . $boundary . '--';
+
+ $stream = fopen('php://temp', 'r+');
+ fwrite($stream, $body);
+ rewind($stream);
+
+ $request->expects($this->any())
+ ->method('getBody')
+ ->willReturn($stream);
+
+ return new MultipartRequestParser($request, $this->logger);
+ }
+
+
+ /**
+ * Test validation of the request's body type
+ */
+ public function testBodyTypeValidation(): void {
+ $bodyStream = 'I am not a stream, but pretend to be';
+ /** @var RequestInterface&MockObject $request */
+ $request = $this->getMockBuilder(RequestInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $request->expects($this->any())
+ ->method('getBody')
+ ->willReturn($bodyStream);
+
+ $this->expectExceptionMessage('Body should be of type resource');
+ new MultipartRequestParser($request, $this->logger);
+ }
+
+ /**
+ * Test with valid request.
+ * - valid boundary
+ * - valid hash
+ * - valid content-length
+ * - valid file content
+ * - valid file path
+ */
+ public function testValidRequest(): void {
+ $bodyObject = self::getValidBodyObject();
+ unset($bodyObject['0']['headers']['X-File-MD5']);
+
+ $multipartParser = $this->getMultipartParser($bodyObject);
+
+ [$headers, $content] = $multipartParser->parseNextPart();
+
+ $this->assertSame((int)$headers['content-length'], 7, 'Content-Length header should be the same as provided.');
+ $this->assertSame($headers['oc-checksum'], 'md5:4f2377b4d911f7ec46325fe603c3af03', 'OC-Checksum header should be the same as provided.');
+ $this->assertSame($headers['x-file-path'], '/coucou.txt', 'X-File-Path header should be the same as provided.');
+
+ $this->assertSame($content, "Coucou\n", 'Content should be the same');
+ }
+
+ /**
+ * Test with valid request.
+ * - valid boundary
+ * - valid md5 hash
+ * - valid content-length
+ * - valid file content
+ * - valid file path
+ */
+ public function testValidRequestWithMd5(): void {
+ $bodyObject = self::getValidBodyObject();
+ unset($bodyObject['0']['headers']['OC-Checksum']);
+
+ $multipartParser = $this->getMultipartParser($bodyObject);
+
+ [$headers, $content] = $multipartParser->parseNextPart();
+
+ $this->assertSame((int)$headers['content-length'], 7, 'Content-Length header should be the same as provided.');
+ $this->assertSame($headers['x-file-md5'], '4f2377b4d911f7ec46325fe603c3af03', 'X-File-MD5 header should be the same as provided.');
+ $this->assertSame($headers['x-file-path'], '/coucou.txt', 'X-File-Path header should be the same as provided.');
+
+ $this->assertSame($content, "Coucou\n", 'Content should be the same');
+ }
+
+ /**
+ * Test with invalid hash.
+ */
+ public function testInvalidHash(): void {
+ $bodyObject = self::getValidBodyObject();
+ $bodyObject['0']['headers']['OC-Checksum'] = 'md5:f2377b4d911f7ec46325fe603c3af03';
+ unset($bodyObject['0']['headers']['X-File-MD5']);
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject
+ );
+
+ $this->expectExceptionMessage('Computed md5 hash is incorrect (4f2377b4d911f7ec46325fe603c3af03).');
+ $multipartParser->parseNextPart();
+ }
+
+ /**
+ * Test with invalid md5 hash.
+ */
+ public function testInvalidMd5Hash(): void {
+ $bodyObject = self::getValidBodyObject();
+ unset($bodyObject['0']['headers']['OC-Checksum']);
+ $bodyObject['0']['headers']['X-File-MD5'] = 'f2377b4d911f7ec46325fe603c3af03';
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject
+ );
+
+ $this->expectExceptionMessage('Computed md5 hash is incorrect (4f2377b4d911f7ec46325fe603c3af03).');
+ $multipartParser->parseNextPart();
+ }
+
+ /**
+ * Test with a null hash headers.
+ */
+ public function testNullHash(): void {
+ $bodyObject = self::getValidBodyObject();
+ unset($bodyObject['0']['headers']['OC-Checksum']);
+ unset($bodyObject['0']['headers']['X-File-MD5']);
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject
+ );
+
+ $this->expectExceptionMessage('The hash headers must not be null.');
+ $multipartParser->parseNextPart();
+ }
+
+ /**
+ * Test with a null Content-Length.
+ */
+ public function testNullContentLength(): void {
+ $bodyObject = self::getValidBodyObject();
+ unset($bodyObject['0']['headers']['Content-Length']);
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject
+ );
+
+ $this->expectExceptionMessage('The Content-Length header must not be null.');
+ $multipartParser->parseNextPart();
+ }
+
+ /**
+ * Test with a lower Content-Length.
+ */
+ public function testLowerContentLength(): void {
+ $bodyObject = self::getValidBodyObject();
+ $bodyObject['0']['headers']['Content-Length'] = 6;
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject
+ );
+
+ $this->expectExceptionMessage('Computed md5 hash is incorrect (41060d3ddfdf63e68fc2bf196f652ee9).');
+ $multipartParser->parseNextPart();
+ }
+
+ /**
+ * Test with a higher Content-Length.
+ */
+ public function testHigherContentLength(): void {
+ $bodyObject = self::getValidBodyObject();
+ $bodyObject['0']['headers']['Content-Length'] = 8;
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject
+ );
+
+ $this->expectExceptionMessage('Computed md5 hash is incorrect (0161002bbee6a744f18741b8a914e413).');
+ $multipartParser->parseNextPart();
+ }
+
+ /**
+ * Test with wrong boundary in body.
+ */
+ public function testWrongBoundary(): void {
+ $bodyObject = self::getValidBodyObject();
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject,
+ ['Content-Type' => 'multipart/related; boundary=boundary_poiuytreza']
+ );
+
+ $this->expectExceptionMessage('Boundary not found where it should be.');
+ $multipartParser->parseNextPart();
+ }
+
+ /**
+ * Test with no boundary in request headers.
+ */
+ public function testNoBoundaryInHeader(): void {
+ $bodyObject = self::getValidBodyObject();
+ $this->expectExceptionMessage('Error while parsing boundary in Content-Type header.');
+ $this->getMultipartParser(
+ $bodyObject,
+ ['Content-Type' => 'multipart/related']
+ );
+ }
+
+ /**
+ * Test with no boundary in the request's headers.
+ */
+ public function testNoBoundaryInBody(): void {
+ $bodyObject = self::getValidBodyObject();
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject,
+ ['Content-Type' => 'multipart/related; boundary=boundary_azertyuiop'],
+ ''
+ );
+
+ $this->expectExceptionMessage('Boundary not found where it should be.');
+ $multipartParser->parseNextPart();
+ }
+
+ /**
+ * Test with a boundary with quotes in the request's headers.
+ */
+ public function testBoundaryWithQuotes(): void {
+ $bodyObject = self::getValidBodyObject();
+ $multipartParser = $this->getMultipartParser(
+ $bodyObject,
+ ['Content-Type' => 'multipart/related; boundary="boundary_azertyuiop"'],
+ );
+
+ $multipartParser->parseNextPart();
+
+ // Dummy assertion, we just want to test that the parsing works.
+ $this->assertTrue(true);
+ }
+
+ /**
+ * Test with a wrong Content-Type in the request's headers.
+ */
+ public function testWrongContentType(): void {
+ $bodyObject = self::getValidBodyObject();
+ $this->expectExceptionMessage('Content-Type must be multipart/related');
+ $this->getMultipartParser(
+ $bodyObject,
+ ['Content-Type' => 'multipart/form-data; boundary="boundary_azertyuiop"'],
+ );
+ }
+
+ /**
+ * Test with a wrong key after the content type in the request's headers.
+ */
+ public function testWrongKeyInContentType(): void {
+ $bodyObject = self::getValidBodyObject();
+ $this->expectExceptionMessage('Boundary is invalid');
+ $this->getMultipartParser(
+ $bodyObject,
+ ['Content-Type' => 'multipart/related; wrongkey="boundary_azertyuiop"'],
+ );
+ }
+
+ /**
+ * Test with a null Content-Type in the request's headers.
+ */
+ public function testNullContentType(): void {
+ $bodyObject = self::getValidBodyObject();
+ $this->expectExceptionMessage('Content-Type can not be null');
+ $this->getMultipartParser(
+ $bodyObject,
+ ['Content-Type' => null],
+
+ );
+ }
+}
diff --git a/apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php b/apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php
new file mode 100644
index 00000000000..1a7ab7179e1
--- /dev/null
+++ b/apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php
@@ -0,0 +1,258 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\DAV\Tests\unit\Files\Sharing;
+
+use OCA\DAV\Files\Sharing\FilesDropPlugin;
+use OCP\Files\Folder;
+use OCP\Files\NotFoundException;
+use OCP\Share\IAttributes;
+use OCP\Share\IShare;
+use PHPUnit\Framework\MockObject\MockObject;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Server;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Test\TestCase;
+
+class FilesDropPluginTest extends TestCase {
+
+ private FilesDropPlugin $plugin;
+
+ private Folder&MockObject $node;
+ private IShare&MockObject $share;
+ private Server&MockObject $server;
+ private RequestInterface&MockObject $request;
+ private ResponseInterface&MockObject $response;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->node = $this->createMock(Folder::class);
+ $this->node->method('getPath')
+ ->willReturn('/files/token');
+
+ $this->share = $this->createMock(IShare::class);
+ $this->share->expects(self::any())
+ ->method('getNode')
+ ->willReturn($this->node);
+ $this->server = $this->createMock(Server::class);
+ $this->plugin = new FilesDropPlugin();
+
+ $this->request = $this->createMock(RequestInterface::class);
+ $this->response = $this->createMock(ResponseInterface::class);
+
+ $attributes = $this->createMock(IAttributes::class);
+ $this->share->expects($this->any())
+ ->method('getAttributes')
+ ->willReturn($attributes);
+
+ $this->share
+ ->method('getToken')
+ ->willReturn('token');
+ }
+
+ public function testNotEnabled(): void {
+ $this->request->expects($this->never())
+ ->method($this->anything());
+
+ $this->plugin->beforeMethod($this->request, $this->response);
+ }
+
+ public function testValid(): void {
+ $this->plugin->enable();
+ $this->plugin->setShare($this->share);
+
+ $this->request->method('getMethod')
+ ->willReturn('PUT');
+
+ $this->request->method('getPath')
+ ->willReturn('/files/token/file.txt');
+
+ $this->request->method('getBaseUrl')
+ ->willReturn('https://example.com');
+
+ $this->node->expects(self::once())
+ ->method('getNonExistingName')
+ ->with('file.txt')
+ ->willReturn('file.txt');
+
+ $this->request->expects($this->once())
+ ->method('setUrl')
+ ->with('https://example.com/files/token/file.txt');
+
+ $this->plugin->beforeMethod($this->request, $this->response);
+ }
+
+ public function testFileAlreadyExistsValid(): void {
+ $this->plugin->enable();
+ $this->plugin->setShare($this->share);
+
+ $this->request->method('getMethod')
+ ->willReturn('PUT');
+
+ $this->request->method('getPath')
+ ->willReturn('/files/token/file.txt');
+
+ $this->request->method('getBaseUrl')
+ ->willReturn('https://example.com');
+
+ $this->node->method('getNonExistingName')
+ ->with('file.txt')
+ ->willReturn('file (2).txt');
+
+ $this->request->expects($this->once())
+ ->method('setUrl')
+ ->with($this->equalTo('https://example.com/files/token/file (2).txt'));
+
+ $this->plugin->beforeMethod($this->request, $this->response);
+ }
+
+ public function testNoMKCOLWithoutNickname(): void {
+ $this->plugin->enable();
+ $this->plugin->setShare($this->share);
+
+ $this->request->method('getMethod')
+ ->willReturn('MKCOL');
+
+ $this->expectException(BadRequest::class);
+
+ $this->plugin->beforeMethod($this->request, $this->response);
+ }
+
+ public function testMKCOLWithNickname(): void {
+ $this->plugin->enable();
+ $this->plugin->setShare($this->share);
+
+ $this->request->method('getMethod')
+ ->willReturn('MKCOL');
+
+ $this->request->method('hasHeader')
+ ->with('X-NC-Nickname')
+ ->willReturn(true);
+ $this->request->method('getHeader')
+ ->with('X-NC-Nickname')
+ ->willReturn('nickname');
+
+ $this->expectNotToPerformAssertions();
+
+ $this->plugin->beforeMethod($this->request, $this->response);
+ }
+
+ public function testSubdirPut(): void {
+ $this->plugin->enable();
+ $this->plugin->setShare($this->share);
+
+ $this->request->method('getMethod')
+ ->willReturn('PUT');
+
+ $this->request->method('hasHeader')
+ ->with('X-NC-Nickname')
+ ->willReturn(true);
+ $this->request->method('getHeader')
+ ->with('X-NC-Nickname')
+ ->willReturn('nickname');
+
+ $this->request->method('getPath')
+ ->willReturn('/files/token/folder/file.txt');
+
+ $this->request->method('getBaseUrl')
+ ->willReturn('https://example.com');
+
+ $nodeName = $this->createMock(Folder::class);
+ $nodeFolder = $this->createMock(Folder::class);
+ $nodeFolder->expects(self::once())
+ ->method('getPath')
+ ->willReturn('/files/token/nickname/folder');
+ $nodeFolder->method('getNonExistingName')
+ ->with('file.txt')
+ ->willReturn('file.txt');
+ $nodeName->expects(self::once())
+ ->method('get')
+ ->with('folder')
+ ->willThrowException(new NotFoundException());
+ $nodeName->expects(self::once())
+ ->method('newFolder')
+ ->with('folder')
+ ->willReturn($nodeFolder);
+
+ $this->node->expects(self::once())
+ ->method('get')
+ ->willThrowException(new NotFoundException());
+ $this->node->expects(self::once())
+ ->method('newFolder')
+ ->with('nickname')
+ ->willReturn($nodeName);
+
+ $this->request->expects($this->once())
+ ->method('setUrl')
+ ->with($this->equalTo('https://example.com/files/token/nickname/folder/file.txt'));
+
+ $this->plugin->beforeMethod($this->request, $this->response);
+ }
+
+ public function testRecursiveFolderCreation(): void {
+ $this->plugin->enable();
+ $this->plugin->setShare($this->share);
+
+ $this->request->method('getMethod')
+ ->willReturn('PUT');
+ $this->request->method('hasHeader')
+ ->with('X-NC-Nickname')
+ ->willReturn(true);
+ $this->request->method('getHeader')
+ ->with('X-NC-Nickname')
+ ->willReturn('nickname');
+
+ $this->request->method('getPath')
+ ->willReturn('/files/token/folder/subfolder/file.txt');
+ $this->request->method('getBaseUrl')
+ ->willReturn('https://example.com');
+
+ $this->request->expects($this->once())
+ ->method('setUrl')
+ ->with($this->equalTo('https://example.com/files/token/nickname/folder/subfolder/file.txt'));
+
+ $subfolder = $this->createMock(Folder::class);
+ $subfolder->expects(self::once())
+ ->method('getNonExistingName')
+ ->with('file.txt')
+ ->willReturn('file.txt');
+ $subfolder->expects(self::once())
+ ->method('getPath')
+ ->willReturn('/files/token/nickname/folder/subfolder');
+
+ $folder = $this->createMock(Folder::class);
+ $folder->expects(self::once())
+ ->method('get')
+ ->with('subfolder')
+ ->willReturn($subfolder);
+
+ $nickname = $this->createMock(Folder::class);
+ $nickname->expects(self::once())
+ ->method('get')
+ ->with('folder')
+ ->willReturn($folder);
+
+ $this->node->method('get')
+ ->with('nickname')
+ ->willReturn($nickname);
+ $this->plugin->beforeMethod($this->request, $this->response);
+ }
+
+ public function testOnMkcol(): void {
+ $this->plugin->enable();
+ $this->plugin->setShare($this->share);
+
+ $this->response->expects($this->once())
+ ->method('setStatus')
+ ->with(201);
+
+ $response = $this->plugin->onMkcol($this->request, $this->response);
+ $this->assertFalse($response);
+ }
+}