diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2015-08-30 19:13:01 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2015-10-16 13:17:12 +0200 |
commit | f2889dc6e4aa701f36081b314f38f620cbb1fc88 (patch) | |
tree | 8969d61c3dc2a71cd8eb1b745d88fc10782e1d75 /apps/dav/tests/unit | |
parent | 4b9ec49285081137195c5852682b127a37ea8bfe (diff) | |
download | nextcloud-server-f2889dc6e4aa701f36081b314f38f620cbb1fc88.tar.gz nextcloud-server-f2889dc6e4aa701f36081b314f38f620cbb1fc88.zip |
Consolidate webdav code - move all to one app
Diffstat (limited to 'apps/dav/tests/unit')
20 files changed, 3485 insertions, 0 deletions
diff --git a/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php b/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php new file mode 100644 index 00000000000..1e390cf15f7 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/BlockLegacyClientPluginTest.php @@ -0,0 +1,129 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + +namespace Test\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; +use Test\TestCase; +use OCP\IConfig; + +/** + * Class BlockLegacyClientPluginTest + * + * @package Test\Connector\Sabre + */ +class BlockLegacyClientPluginTest extends TestCase { + /** @var IConfig */ + private $config; + /** @var BlockLegacyClientPlugin */ + private $blockLegacyClientVersionPlugin; + + public function setUp() { + parent::setUp(); + + $this->config = $this->getMock('\OCP\IConfig'); + $this->blockLegacyClientVersionPlugin = new BlockLegacyClientPlugin($this->config); + } + + /** + * @return array + */ + public function oldDesktopClientProvider() { + return [ + ['Mozilla/5.0 (1.5.0) mirall/1.5.0'], + ['mirall/1.5.0'], + ['mirall/1.5.4'], + ['mirall/1.6.0'], + ['Mozilla/5.0 (Bogus Text) mirall/1.6.9'], + ]; + } + + /** + * @dataProvider oldDesktopClientProvider + * @param string $userAgent + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Unsupported client version. + */ + public function testBeforeHandlerException($userAgent) { + /** @var \Sabre\HTTP\RequestInterface $request */ + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $request + ->expects($this->once()) + ->method('getHeader') + ->with('User-Agent') + ->will($this->returnValue($userAgent)); + + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('minimum.supported.desktop.version', '1.7.0') + ->will($this->returnValue('1.7.0')); + + $this->blockLegacyClientVersionPlugin->beforeHandler($request); + } + + /** + * @return array + */ + public function newAndAlternateDesktopClientProvider() { + return [ + ['Mozilla/5.0 (1.7.0) mirall/1.7.0'], + ['mirall/1.8.3'], + ['mirall/1.7.2'], + ['mirall/1.7.0'], + ['Mozilla/5.0 (Bogus Text) mirall/1.9.3'], + ]; + } + + /** + * @dataProvider newAndAlternateDesktopClientProvider + * @param string $userAgent + */ + public function testBeforeHandlerSuccess($userAgent) { + /** @var \Sabre\HTTP\RequestInterface $request */ + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $request + ->expects($this->once()) + ->method('getHeader') + ->with('User-Agent') + ->will($this->returnValue($userAgent)); + + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('minimum.supported.desktop.version', '1.7.0') + ->will($this->returnValue('1.7.0')); + + $this->blockLegacyClientVersionPlugin->beforeHandler($request); + } + + public function testBeforeHandlerNoUserAgent() { + /** @var \Sabre\HTTP\RequestInterface $request */ + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $request + ->expects($this->once()) + ->method('getHeader') + ->with('User-Agent') + ->will($this->returnValue(null)); + $this->blockLegacyClientVersionPlugin->beforeHandler($request); + } + +} diff --git a/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php b/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php new file mode 100644 index 00000000000..1fd89c84ff6 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/DummyGetResponsePluginTest.php @@ -0,0 +1,69 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + +namespace Test\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\DummyGetResponsePlugin; +use Test\TestCase; + +/** + * Class DummyGetResponsePluginTest + * + * @package Test\Connector\Sabre + */ +class DummyGetResponsePluginTest extends TestCase { + /** @var DummyGetResponsePlugin */ + private $dummyGetResponsePlugin; + + public function setUp() { + parent::setUp(); + + $this->dummyGetResponsePlugin = new DummyGetResponsePlugin(); + } + + public function testInitialize() { + /** @var \Sabre\DAV\Server $server */ + $server = $this->getMock('\Sabre\DAV\Server'); + $server + ->expects($this->once()) + ->method('on') + ->with('method:GET', [$this->dummyGetResponsePlugin, 'httpGet'], 200); + + $this->dummyGetResponsePlugin->initialize($server); + } + + + public function testHttpGet() { + /** @var \Sabre\HTTP\RequestInterface $request */ + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + /** @var \Sabre\HTTP\ResponseInterface $response */ + $response = $server = $this->getMock('\Sabre\HTTP\ResponseInterface'); + $response + ->expects($this->once()) + ->method('setBody'); + $response + ->expects($this->once()) + ->method('setStatus') + ->with(200); + + $this->assertSame(false, $this->dummyGetResponsePlugin->httpGet($request, $response)); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php b/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php new file mode 100644 index 00000000000..c0acd4fc3de --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/MaintenancePluginTest.php @@ -0,0 +1,73 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/> + * + */ + +namespace Test\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\MaintenancePlugin; +use Test\TestCase; +use OCP\IConfig; + +/** + * Class MaintenancePluginTest + * + * @package Test\Connector\Sabre + */ +class MaintenancePluginTest extends TestCase { + /** @var IConfig */ + private $config; + /** @var MaintenancePlugin */ + private $maintenancePlugin; + + public function setUp() { + parent::setUp(); + + $this->config = $this->getMock('\OCP\IConfig'); + $this->maintenancePlugin = new MaintenancePlugin($this->config); + } + + /** + * @expectedException \Sabre\DAV\Exception\ServiceUnavailable + * @expectedExceptionMessage System in single user mode. + */ + public function testSingleUserMode() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('singleuser', false) + ->will($this->returnValue(true)); + + $this->maintenancePlugin->checkMaintenanceMode(); + } + + /** + * @expectedException \Sabre\DAV\Exception\ServiceUnavailable + * @expectedExceptionMessage System in single user mode. + */ + public function testMaintenanceMode() { + $this->config + ->expects($this->exactly(1)) + ->method('getSystemValue') + ->will($this->onConsecutiveCalls([false, true])); + + $this->maintenancePlugin->checkMaintenanceMode(); + } + +} diff --git a/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php b/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php new file mode 100644 index 00000000000..2080755cd51 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/copyetagheaderplugintest.php @@ -0,0 +1,43 @@ +<?php + +namespace Tests\Connector\Sabre; + +/** + * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class CopyEtagPluginTest extends \Test\TestCase { + + /** + * @var \OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->plugin = new \OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin(); + $this->plugin->initialize($this->server); + } + + public function testCopyEtag() { + $request = new \Sabre\Http\Request(); + $response = new \Sabre\Http\Response(); + $response->setHeader('Etag', 'abcd'); + + $this->plugin->afterMethod($request, $response); + + $this->assertEquals('abcd', $response->getHeader('OC-Etag')); + } + + public function testNoopWhenEmpty() { + $request = new \Sabre\Http\Request(); + $response = new \Sabre\Http\Response(); + + $this->plugin->afterMethod($request, $response); + + $this->assertNull($response->getHeader('OC-Etag')); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php b/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php new file mode 100644 index 00000000000..973a5d4c27b --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/custompropertiesbackend.php @@ -0,0 +1,286 @@ +<?php + +namespace Tests\Connector\Sabre; + +/** + * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class CustomPropertiesBackend extends \Test\TestCase { + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\ObjectTree + */ + private $tree; + + /** + * @var \OCA\DAV\Connector\Sabre\CustomPropertiesBackend + */ + private $plugin; + + /** + * @var \OCP\IUser + */ + private $user; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + + $userId = $this->getUniqueID('testcustompropertiesuser'); + + $this->user = $this->getMock('\OCP\IUser'); + $this->user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue($userId)); + + $this->plugin = new \OCA\DAV\Connector\Sabre\CustomPropertiesBackend( + $this->tree, + \OC::$server->getDatabaseConnection(), + $this->user + ); + } + + public function tearDown() { + $connection = \OC::$server->getDatabaseConnection(); + $deleteStatement = $connection->prepare( + 'DELETE FROM `*PREFIX*properties`' . + ' WHERE `userid` = ?' + ); + $deleteStatement->execute( + array( + $this->user->getUID(), + ) + ); + $deleteStatement->closeCursor(); + } + + private function createTestNode($class) { + $node = $this->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $node->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/dummypath')); + + return $node; + } + + private function applyDefaultProps($path = '/dummypath') { + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + 'customprop' => 'value1', + 'customprop2' => 'value2', + )); + + $this->plugin->propPatch( + $path, + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result['customprop']); + $this->assertEquals(200, $result['customprop2']); + } + + /** + * Test that propFind on a missing file soft fails + */ + public function testPropFindMissingFileSoftFail() { + $this->tree->expects($this->at(0)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->throwException(new \Sabre\DAV\Exception\NotFound())); + + $this->tree->expects($this->at(1)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->throwException(new \Sabre\DAV\Exception\ServiceUnavailable())); + + $propFind = new \Sabre\DAV\PropFind( + '/dummypath', + array( + 'customprop', + 'customprop2', + 'unsetprop', + ), + 0 + ); + + $this->plugin->propFind( + '/dummypath', + $propFind + ); + + $this->plugin->propFind( + '/dummypath', + $propFind + ); + + // no exception, soft fail + $this->assertTrue(true); + } + + /** + * Test setting/getting properties + */ + public function testSetGetPropertiesForFile() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->applyDefaultProps(); + + $propFind = new \Sabre\DAV\PropFind( + '/dummypath', + array( + 'customprop', + 'customprop2', + 'unsetprop', + ), + 0 + ); + + $this->plugin->propFind( + '/dummypath', + $propFind + ); + + $this->assertEquals('value1', $propFind->get('customprop')); + $this->assertEquals('value2', $propFind->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFind->get404Properties()); + } + + /** + * Test getting properties from directory + */ + public function testGetPropertiesForDirectory() { + $rootNode = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); + + $nodeSub = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $nodeSub->expects($this->any()) + ->method('getId') + ->will($this->returnValue(456)); + + $nodeSub->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/dummypath/test.txt')); + + $rootNode->expects($this->once()) + ->method('getChildren') + ->will($this->returnValue(array($nodeSub))); + + $this->tree->expects($this->at(0)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($rootNode)); + + $this->tree->expects($this->at(1)) + ->method('getNodeForPath') + ->with('/dummypath/test.txt') + ->will($this->returnValue($nodeSub)); + + $this->tree->expects($this->at(2)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($rootNode)); + + $this->tree->expects($this->at(3)) + ->method('getNodeForPath') + ->with('/dummypath/test.txt') + ->will($this->returnValue($nodeSub)); + + $this->applyDefaultProps('/dummypath'); + $this->applyDefaultProps('/dummypath/test.txt'); + + $propNames = array( + 'customprop', + 'customprop2', + 'unsetprop', + ); + + $propFindRoot = new \Sabre\DAV\PropFind( + '/dummypath', + $propNames, + 1 + ); + + $propFindSub = new \Sabre\DAV\PropFind( + '/dummypath/test.txt', + $propNames, + 0 + ); + + $this->plugin->propFind( + '/dummypath', + $propFindRoot + ); + + $this->plugin->propFind( + '/dummypath/test.txt', + $propFindSub + ); + + // TODO: find a way to assert that no additional SQL queries were + // run while doing the second propFind + + $this->assertEquals('value1', $propFindRoot->get('customprop')); + $this->assertEquals('value2', $propFindRoot->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFindRoot->get404Properties()); + + $this->assertEquals('value1', $propFindSub->get('customprop')); + $this->assertEquals('value2', $propFindSub->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFindSub->get404Properties()); + } + + /** + * Test delete property + */ + public function testDeleteProperty() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->applyDefaultProps(); + + $propPatch = new \Sabre\DAV\PropPatch(array( + 'customprop' => null, + )); + + $this->plugin->propPatch( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(204, $result['customprop']); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/directory.php b/apps/dav/tests/unit/connector/sabre/directory.php new file mode 100644 index 00000000000..d85290df80a --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/directory.php @@ -0,0 +1,192 @@ +<?php + +/** + * Copyright (c) 2013 Thomas Müller <thomas.mueller@tmit.eu> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class Test_OC_Connector_Sabre_Directory extends \Test\TestCase { + + /** @var OC\Files\View | PHPUnit_Framework_MockObject_MockObject */ + private $view; + /** @var OC\Files\FileInfo | PHPUnit_Framework_MockObject_MockObject */ + private $info; + + protected function setUp() { + parent::setUp(); + + $this->view = $this->getMock('OC\Files\View', array(), array(), '', false); + $this->info = $this->getMock('OC\Files\FileInfo', array(), array(), '', false); + } + + private function getDir($path = '/') { + $this->view->expects($this->once()) + ->method('getRelativePath') + ->will($this->returnValue($path)); + + $this->info->expects($this->once()) + ->method('getPath') + ->will($this->returnValue($path)); + + return new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteRootFolderFails() { + $this->info->expects($this->any()) + ->method('isDeletable') + ->will($this->returnValue(true)); + $this->view->expects($this->never()) + ->method('rmdir'); + $dir = $this->getDir(); + $dir->delete(); + } + + /** + * + */ + public function testDeleteFolderWhenAllowed() { + // deletion allowed + $this->info->expects($this->once()) + ->method('isDeletable') + ->will($this->returnValue(true)); + + // but fails + $this->view->expects($this->once()) + ->method('rmdir') + ->with('sub') + ->will($this->returnValue(true)); + + $dir = $this->getDir('sub'); + $dir->delete(); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteFolderFailsWhenNotAllowed() { + $this->info->expects($this->once()) + ->method('isDeletable') + ->will($this->returnValue(false)); + + $dir = $this->getDir('sub'); + $dir->delete(); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteFolderThrowsWhenDeletionFailed() { + // deletion allowed + $this->info->expects($this->once()) + ->method('isDeletable') + ->will($this->returnValue(true)); + + // but fails + $this->view->expects($this->once()) + ->method('rmdir') + ->with('sub') + ->will($this->returnValue(false)); + + $dir = $this->getDir('sub'); + $dir->delete(); + } + + public function testGetChildren() { + $info1 = $this->getMockBuilder('OC\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $info2 = $this->getMockBuilder('OC\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $info1->expects($this->any()) + ->method('getName') + ->will($this->returnValue('first')); + $info1->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('abc')); + $info2->expects($this->any()) + ->method('getName') + ->will($this->returnValue('second')); + $info2->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('def')); + + $this->view->expects($this->once()) + ->method('getDirectoryContent') + ->with('') + ->will($this->returnValue(array($info1, $info2))); + + $this->view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnValue('')); + + $dir = new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + $nodes = $dir->getChildren(); + + $this->assertEquals(2, count($nodes)); + + // calling a second time just returns the cached values, + // does not call getDirectoryContents again + $dir->getChildren(); + } + + /** + * @expectedException \Sabre\DAV\Exception\ServiceUnavailable + */ + public function testGetChildThrowStorageNotAvailableException() { + $this->view->expects($this->once()) + ->method('getFileInfo') + ->willThrowException(new \OCP\Files\StorageNotAvailableException()); + + $dir = new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + $dir->getChild('.'); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\InvalidPath + */ + public function testGetChildThrowInvalidPath() { + $this->view->expects($this->once()) + ->method('verifyPath') + ->willThrowException(new \OCP\Files\InvalidPathException()); + $this->view->expects($this->never()) + ->method('getFileInfo'); + + $dir = new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + $dir->getChild('.'); + } + + public function testGetQuotaInfo() { + $storage = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Quota') + ->disableOriginalConstructor() + ->getMock(); + + $storage->expects($this->once()) + ->method('instanceOfStorage') + ->with('\OC\Files\Storage\Wrapper\Quota') + ->will($this->returnValue(true)); + + $storage->expects($this->once()) + ->method('getQuota') + ->will($this->returnValue(1000)); + + $storage->expects($this->once()) + ->method('free_space') + ->will($this->returnValue(800)); + + $this->info->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(200)); + + $this->info->expects($this->once()) + ->method('getStorage') + ->will($this->returnValue($storage)); + + $dir = new \OCA\DAV\Connector\Sabre\Directory($this->view, $this->info); + $this->assertEquals([200, 800], $dir->getQuotaInfo()); //200 used, 800 free + } +} diff --git a/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php b/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php new file mode 100644 index 00000000000..4c0af58ffea --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/exception/invalidpathtest.php @@ -0,0 +1,44 @@ +<?php + +namespace Test\Connector\Sabre\Exception; + +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; + +/** + * Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class InvalidPathTest extends \Test\TestCase { + + public function testSerialization() { + + // create xml doc + $DOM = new \DOMDocument('1.0','utf-8'); + $DOM->formatOutput = true; + $error = $DOM->createElementNS('DAV:','d:error'); + $error->setAttribute('xmlns:s', \Sabre\DAV\Server::NS_SABREDAV); + $DOM->appendChild($error); + + // serialize the exception + $message = "1234567890"; + $retry = false; + $expectedXml = <<<EOD +<?xml version="1.0" encoding="utf-8"?> +<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:o="http://owncloud.org/ns"> + <o:retry xmlns:o="o:">false</o:retry> + <o:reason xmlns:o="o:">1234567890</o:reason> +</d:error> + +EOD; + + $ex = new InvalidPath($message, $retry); + $server = $this->getMock('Sabre\DAV\Server'); + $ex->serialize($server, $error); + + // assert + $xml = $DOM->saveXML(); + $this->assertEquals($expectedXml, $xml); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php b/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php new file mode 100644 index 00000000000..d85aa5a9cc3 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/exceptionloggerplugin.php @@ -0,0 +1,71 @@ +<?php + +/** + * Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin as PluginToTest; +use OC\Log; +use OCP\ILogger; +use PHPUnit_Framework_MockObject_MockObject; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Server; +use Test\TestCase; + +class TestLogger extends Log { + public $message; + public $level; + + public function __construct($logger = null) { + //disable original constructor + } + + public function log($level, $message, array $context = array()) { + $this->level = $level; + $this->message = $message; + } +} + +class ExceptionLoggerPlugin extends TestCase { + + /** @var Server */ + private $server; + + /** @var PluginToTest */ + private $plugin; + + /** @var TestLogger | PHPUnit_Framework_MockObject_MockObject */ + private $logger; + + private function init() { + $this->server = new Server(); + $this->logger = new TestLogger(); + $this->plugin = new PluginToTest('unit-test', $this->logger); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider providesExceptions + */ + public function testLogging($expectedLogLevel, $expectedMessage, $exception) { + $this->init(); + $this->plugin->logException($exception); + + $this->assertEquals($expectedLogLevel, $this->logger->level); + $this->assertStringStartsWith('Exception: {"Message":"' . $expectedMessage, $this->logger->message); + } + + public function providesExceptions() { + return [ + [0, 'HTTP\/1.1 404 Not Found', new NotFound()], + [4, 'HTTP\/1.1 400 This path leads to nowhere', new InvalidPath('This path leads to nowhere')] + ]; + } + +} diff --git a/apps/dav/tests/unit/connector/sabre/file.php b/apps/dav/tests/unit/connector/sabre/file.php new file mode 100644 index 00000000000..6685ddac913 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/file.php @@ -0,0 +1,829 @@ +<?php +/** + * Copyright (c) 2013 Thomas Müller <thomas.mueller@tmit.eu> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre; + +use Test\HookHelper; +use OC\Files\Filesystem; +use OCP\Lock\ILockingProvider; + +class File extends \Test\TestCase { + + /** + * @var string + */ + private $user; + + public function setUp() { + parent::setUp(); + + \OC_Hook::clear(); + + $this->user = $this->getUniqueID('user_'); + $userManager = \OC::$server->getUserManager(); + $userManager->createUser($this->user, 'pass'); + + $this->loginAsUser($this->user); + } + + public function tearDown() { + $userManager = \OC::$server->getUserManager(); + $userManager->get($this->user)->delete(); + unset($_SERVER['HTTP_OC_CHUNKED']); + + parent::tearDown(); + } + + private function getStream($string) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $string); + fseek($stream, 0); + return $stream; + } + + + public function fopenFailuresProvider() { + return [ + [ + // return false + null, + '\Sabre\Dav\Exception', + false + ], + [ + new \OCP\Files\NotPermittedException(), + 'Sabre\DAV\Exception\Forbidden' + ], + [ + new \OCP\Files\EntityTooLargeException(), + 'OCA\DAV\Connector\Sabre\Exception\EntityTooLarge' + ], + [ + new \OCP\Files\InvalidContentException(), + 'OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType' + ], + [ + new \OCP\Files\InvalidPathException(), + 'Sabre\DAV\Exception\Forbidden' + ], + [ + new \OCP\Files\LockNotAcquiredException('/test.txt', 1), + 'OCA\DAV\Connector\Sabre\Exception\FileLocked' + ], + [ + new \OCP\Lock\LockedException('/test.txt'), + 'OCA\DAV\Connector\Sabre\Exception\FileLocked' + ], + [ + new \OCP\Encryption\Exceptions\GenericEncryptionException(), + 'Sabre\DAV\Exception\ServiceUnavailable' + ], + [ + new \OCP\Files\StorageNotAvailableException(), + 'Sabre\DAV\Exception\ServiceUnavailable' + ], + [ + new \Sabre\DAV\Exception('Generic sabre exception'), + 'Sabre\DAV\Exception', + false + ], + [ + new \Exception('Generic exception'), + 'Sabre\DAV\Exception' + ], + ]; + } + + /** + * @dataProvider fopenFailuresProvider + */ + public function testSimplePutFails($thrownException, $expectedException, $checkPreviousClass = true) { + // setup + $storage = $this->getMock( + '\OC\Files\Storage\Local', + ['fopen'], + [['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]] + ); + \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); + $view = $this->getMock('\OC\Files\View', array('getRelativePath', 'resolvePath'), array()); + $view->expects($this->atLeastOnce()) + ->method('resolvePath') + ->will($this->returnCallback( + function ($path) use ($storage) { + return [$storage, $path]; + } + )); + + if ($thrownException !== null) { + $storage->expects($this->once()) + ->method('fopen') + ->will($this->throwException($thrownException)); + } else { + $storage->expects($this->once()) + ->method('fopen') + ->will($this->returnValue(false)); + } + + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $caughtException = null; + try { + $file->put('test data'); + } catch (\Exception $e) { + $caughtException = $e; + } + + $this->assertInstanceOf($expectedException, $caughtException); + if ($checkPreviousClass) { + $this->assertInstanceOf(get_class($thrownException), $caughtException->getPrevious()); + } + + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test putting a file using chunking + * + * @dataProvider fopenFailuresProvider + */ + public function testChunkedPutFails($thrownException, $expectedException, $checkPreviousClass = false) { + // setup + $storage = $this->getMock( + '\OC\Files\Storage\Local', + ['fopen'], + [['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]] + ); + \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); + $view = $this->getMock('\OC\Files\View', ['getRelativePath', 'resolvePath'], []); + $view->expects($this->atLeastOnce()) + ->method('resolvePath') + ->will($this->returnCallback( + function ($path) use ($storage) { + return [$storage, $path]; + } + )); + + if ($thrownException !== null) { + $storage->expects($this->once()) + ->method('fopen') + ->will($this->throwException($thrownException)); + } else { + $storage->expects($this->once()) + ->method('fopen') + ->will($this->returnValue(false)); + } + + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $_SERVER['HTTP_OC_CHUNKED'] = true; + + $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-0', null, null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // put first chunk + $this->assertNull($file->put('test data one')); + + $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-1', null, null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $caughtException = null; + try { + // last chunk + $file->acquireLock(ILockingProvider::LOCK_SHARED); + $file->put('test data two'); + $file->releaseLock(ILockingProvider::LOCK_SHARED); + } catch (\Exception $e) { + $caughtException = $e; + } + + $this->assertInstanceOf($expectedException, $caughtException); + if ($checkPreviousClass) { + $this->assertInstanceOf(get_class($thrownException), $caughtException->getPrevious()); + } + + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Simulate putting a file to the given path. + * + * @param string $path path to put the file into + * @param string $viewRoot root to use for the view + * + * @return result of the PUT operaiton which is usually the etag + */ + private function doPut($path, $viewRoot = null) { + $view = \OC\Files\Filesystem::getView(); + if (!is_null($viewRoot)) { + $view = new \OC\Files\View($viewRoot); + } else { + $viewRoot = '/' . $this->user . '/files'; + } + + $info = new \OC\Files\FileInfo( + $viewRoot . '/' . ltrim($path, '/'), + null, + null, + ['permissions' => \OCP\Constants::PERMISSION_ALL], + null + ); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // beforeMethod locks + $view->lockFile($path, ILockingProvider::LOCK_SHARED); + + $result = $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile($path, ILockingProvider::LOCK_SHARED); + + return $result; + } + + /** + * Test putting a single file + */ + public function testPutSingleFile() { + $this->assertNotEmpty($this->doPut('/foo.txt')); + } + + /** + * Test putting a file using chunking + */ + public function testChunkedPut() { + $_SERVER['HTTP_OC_CHUNKED'] = true; + $this->assertNull($this->doPut('/test.txt-chunking-12345-2-0')); + $this->assertNotEmpty($this->doPut('/test.txt-chunking-12345-2-1')); + } + + /** + * Test that putting a file triggers create hooks + */ + public function testPutSingleFileTriggersHooks() { + HookHelper::setUpHooks(); + + $this->assertNotEmpty($this->doPut('/foo.txt')); + + $this->assertCount(4, HookHelper::$hookCalls); + $this->assertHookCall( + HookHelper::$hookCalls[0], + Filesystem::signal_create, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[1], + Filesystem::signal_write, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[2], + Filesystem::signal_post_create, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[3], + Filesystem::signal_post_write, + '/foo.txt' + ); + } + + /** + * Test that putting a file triggers update hooks + */ + public function testPutOverwriteFileTriggersHooks() { + $view = \OC\Files\Filesystem::getView(); + $view->file_put_contents('/foo.txt', 'some content that will be replaced'); + + HookHelper::setUpHooks(); + + $this->assertNotEmpty($this->doPut('/foo.txt')); + + $this->assertCount(4, HookHelper::$hookCalls); + $this->assertHookCall( + HookHelper::$hookCalls[0], + Filesystem::signal_update, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[1], + Filesystem::signal_write, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[2], + Filesystem::signal_post_update, + '/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[3], + Filesystem::signal_post_write, + '/foo.txt' + ); + } + + /** + * Test that putting a file triggers hooks with the correct path + * if the passed view was chrooted (can happen with public webdav + * where the root is the share root) + */ + public function testPutSingleFileTriggersHooksDifferentRoot() { + $view = \OC\Files\Filesystem::getView(); + $view->mkdir('noderoot'); + + HookHelper::setUpHooks(); + + // happens with public webdav where the view root is the share root + $this->assertNotEmpty($this->doPut('/foo.txt', '/' . $this->user . '/files/noderoot')); + + $this->assertCount(4, HookHelper::$hookCalls); + $this->assertHookCall( + HookHelper::$hookCalls[0], + Filesystem::signal_create, + '/noderoot/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[1], + Filesystem::signal_write, + '/noderoot/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[2], + Filesystem::signal_post_create, + '/noderoot/foo.txt' + ); + $this->assertHookCall( + HookHelper::$hookCalls[3], + Filesystem::signal_post_write, + '/noderoot/foo.txt' + ); + } + + public static function cancellingHook($params) { + self::$hookCalls[] = array( + 'signal' => Filesystem::signal_post_create, + 'params' => $params + ); + } + + /** + * Test put file with cancelled hook + */ + public function testPutSingleFileCancelPreHook() { + \OCP\Util::connectHook( + Filesystem::CLASSNAME, + Filesystem::signal_create, + '\Test\HookHelper', + 'cancellingCallback' + ); + + // action + $thrown = false; + try { + $this->doPut('/foo.txt'); + } catch (\Sabre\DAV\Exception $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles(), 'No stray part files'); + } + + /** + * Test exception when the uploaded size did not match + */ + public function testSimplePutFailsSizeCheck() { + // setup + $view = $this->getMock('\OC\Files\View', + array('rename', 'getRelativePath', 'filesize')); + $view->expects($this->any()) + ->method('rename') + ->withAnyParameters() + ->will($this->returnValue(false)); + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $view->expects($this->any()) + ->method('filesize') + ->will($this->returnValue(123456)); + + $_SERVER['CONTENT_LENGTH'] = 123456; + $_SERVER['REQUEST_METHOD'] = 'PUT'; + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $view->lockFile('/test.txt', ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile('/test.txt', ILockingProvider::LOCK_SHARED); + } catch (\Sabre\DAV\Exception\BadRequest $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test exception during final rename in simple upload mode + */ + public function testSimplePutFailsMoveFromStorage() { + $view = new \OC\Files\View('/' . $this->user . '/files'); + + // simulate situation where the target file is locked + $view->lockFile('/test.txt', ILockingProvider::LOCK_EXCLUSIVE); + + $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $view->lockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test exception during final rename in chunk upload mode + */ + public function testChunkedPutFailsFinalRename() { + $view = new \OC\Files\View('/' . $this->user . '/files'); + + // simulate situation where the target file is locked + $view->lockFile('/test.txt', ILockingProvider::LOCK_EXCLUSIVE); + + $_SERVER['HTTP_OC_CHUNKED'] = true; + + $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-0', null, null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $this->assertNull($file->put('test data one')); + + $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-1', null, null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + $file->put($this->getStream('test data')); + } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test put file with invalid chars + */ + public function testSimplePutInvalidChars() { + // setup + $view = $this->getMock('\OC\Files\View', array('getRelativePath')); + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $info = new \OC\Files\FileInfo('/*', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $view->lockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + } catch (\OCA\DAV\Connector\Sabre\Exception\InvalidPath $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * Test setting name with setName() with invalid chars + * + * @expectedException \OCA\DAV\Connector\Sabre\Exception\InvalidPath + */ + public function testSetNameInvalidChars() { + // setup + $view = $this->getMock('\OC\Files\View', array('getRelativePath')); + + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + + $info = new \OC\Files\FileInfo('/*', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file->setName('/super*star.txt'); + } + + /** + */ + public function testUploadAbort() { + // setup + $view = $this->getMock('\OC\Files\View', + array('rename', 'getRelativePath', 'filesize')); + $view->expects($this->any()) + ->method('rename') + ->withAnyParameters() + ->will($this->returnValue(false)); + $view->expects($this->any()) + ->method('getRelativePath') + ->will($this->returnArgument(0)); + $view->expects($this->any()) + ->method('filesize') + ->will($this->returnValue(123456)); + + $_SERVER['CONTENT_LENGTH'] = 12345; + $_SERVER['REQUEST_METHOD'] = 'PUT'; + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $view->lockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $view->unlockFile($info->getPath(), ILockingProvider::LOCK_SHARED); + } catch (\Sabre\DAV\Exception\BadRequest $e) { + $thrown = true; + } + + $this->assertTrue($thrown); + $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); + } + + /** + * + */ + public function testDeleteWhenAllowed() { + // setup + $view = $this->getMock('\OC\Files\View', + array()); + + $view->expects($this->once()) + ->method('unlink') + ->will($this->returnValue(true)); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $file->delete(); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteThrowsWhenDeletionNotAllowed() { + // setup + $view = $this->getMock('\OC\Files\View', + array()); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => 0 + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $file->delete(); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testDeleteThrowsWhenDeletionFailed() { + // setup + $view = $this->getMock('\OC\Files\View', + array()); + + // but fails + $view->expects($this->once()) + ->method('unlink') + ->will($this->returnValue(false)); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $file->delete(); + } + + /** + * Asserts hook call + * + * @param array $callData hook call data to check + * @param string $signal signal name + * @param string $hookPath hook path + */ + protected function assertHookCall($callData, $signal, $hookPath) { + $this->assertEquals($signal, $callData['signal']); + $params = $callData['params']; + $this->assertEquals( + $hookPath, + $params[Filesystem::signal_param_path] + ); + } + + /** + * Test whether locks are set before and after the operation + */ + public function testPutLocking() { + $view = new \OC\Files\View('/' . $this->user . '/files/'); + + $path = 'test-locking.txt'; + $info = new \OC\Files\FileInfo( + '/' . $this->user . '/files/' . $path, + null, + null, + ['permissions' => \OCP\Constants::PERMISSION_ALL], + null + ); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + $this->assertFalse( + $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED), + 'File unlocked before put' + ); + $this->assertFalse( + $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE), + 'File unlocked before put' + ); + + $wasLockedPre = false; + $wasLockedPost = false; + $eventHandler = $this->getMockBuilder('\stdclass') + ->setMethods(['writeCallback', 'postWriteCallback']) + ->getMock(); + + // both pre and post hooks might need access to the file, + // so only shared lock is acceptable + $eventHandler->expects($this->once()) + ->method('writeCallback') + ->will($this->returnCallback( + function () use ($view, $path, &$wasLockedPre) { + $wasLockedPre = $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED); + $wasLockedPre = $wasLockedPre && !$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE); + } + )); + $eventHandler->expects($this->once()) + ->method('postWriteCallback') + ->will($this->returnCallback( + function () use ($view, $path, &$wasLockedPost) { + $wasLockedPost = $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED); + $wasLockedPost = $wasLockedPost && !$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE); + } + )); + + \OCP\Util::connectHook( + Filesystem::CLASSNAME, + Filesystem::signal_write, + $eventHandler, + 'writeCallback' + ); + \OCP\Util::connectHook( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + $eventHandler, + 'postWriteCallback' + ); + + // beforeMethod locks + $view->lockFile($path, ILockingProvider::LOCK_SHARED); + + $this->assertNotEmpty($file->put($this->getStream('test data'))); + + // afterMethod unlocks + $view->unlockFile($path, ILockingProvider::LOCK_SHARED); + + $this->assertTrue($wasLockedPre, 'File was locked during pre-hooks'); + $this->assertTrue($wasLockedPost, 'File was locked during post-hooks'); + + $this->assertFalse( + $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED), + 'File unlocked after put' + ); + $this->assertFalse( + $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE), + 'File unlocked after put' + ); + } + + /** + * Returns part files in the given path + * + * @param \OC\Files\View view which root is the current user's "files" folder + * @param string $path path for which to list part files + * + * @return array list of part files + */ + private function listPartFiles(\OC\Files\View $userView = null, $path = '') { + if ($userView === null) { + $userView = \OC\Files\Filesystem::getView(); + } + $files = []; + list($storage, $internalPath) = $userView->resolvePath($path); + $realPath = $storage->getSourcePath($internalPath); + $dh = opendir($realPath); + while (($file = readdir($dh)) !== false) { + if (substr($file, strlen($file) - 5, 5) === '.part') { + $files[] = $file; + } + } + closedir($dh); + return $files; + } + + /** + * @expectedException \Sabre\DAV\Exception\ServiceUnavailable + */ + public function testGetFopenFails() { + $view = $this->getMock('\OC\Files\View', ['fopen'], array()); + $view->expects($this->atLeastOnce()) + ->method('fopen') + ->will($this->returnValue(false)); + + $info = new \OC\Files\FileInfo('/test.txt', null, null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OC\Connector\Sabre\File($view, $info); + + $file->get(); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/filesplugin.php b/apps/dav/tests/unit/connector/sabre/filesplugin.php new file mode 100644 index 00000000000..a91ca7a4ff7 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/filesplugin.php @@ -0,0 +1,254 @@ +<?php + +namespace Tests\Connector\Sabre; + +/** + * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class FilesPlugin extends \Test\TestCase { + const GETETAG_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::GETETAG_PROPERTYNAME; + const FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::FILEID_PROPERTYNAME; + const SIZE_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::SIZE_PROPERTYNAME; + const PERMISSIONS_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::PERMISSIONS_PROPERTYNAME; + const LASTMODIFIED_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::LASTMODIFIED_PROPERTYNAME; + const DOWNLOADURL_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::DOWNLOADURL_PROPERTYNAME; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @var \OCA\DAV\Connector\Sabre\FilesPlugin + */ + private $plugin; + + /** + * @var \OC\Files\View + */ + private $view; + + public function setUp() { + parent::setUp(); + $this->server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + $this->view = $this->getMockBuilder('\OC\Files\View') + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view); + $this->plugin->initialize($this->server); + } + + private function createTestNode($class) { + $node = $this->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $node->expects($this->any()) + ->method('getFileId') + ->will($this->returnValue(123)); + $node->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('"abc"')); + $node->expects($this->any()) + ->method('getDavPermissions') + ->will($this->returnValue('DWCKMSR')); + + return $node; + } + + /** + */ + public function testGetPropertiesForFile() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + ), + 0 + ); + + $node->expects($this->once()) + ->method('getDirectDownload') + ->will($this->returnValue(array('url' => 'http://example.com/'))); + $node->expects($this->never()) + ->method('getSize'); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); + $this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::SIZE_PROPERTYNAME)); + $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals(array(self::SIZE_PROPERTYNAME), $propFind->get404Properties()); + } + + public function testGetPublicPermissions() { + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view, true); + $this->plugin->initialize($this->server); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + [ + self::PERMISSIONS_PROPERTYNAME, + ], + 0 + ); + + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + $node->expects($this->any()) + ->method('getDavPermissions') + ->will($this->returnValue('DWCKMSR')); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('DWCKR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + } + + public function testGetPropertiesForDirectory() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + ), + 0 + ); + + $node->expects($this->never()) + ->method('getDirectDownload'); + $node->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(1025)); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); + $this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME)); + $this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME)); + $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals(array(self::DOWNLOADURL_PROPERTYNAME), $propFind->get404Properties()); + } + + public function testUpdateProps() { + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + + $testDate = 'Fri, 13 Feb 2015 00:01:02 GMT'; + + $node->expects($this->once()) + ->method('touch') + ->with($testDate); + + $node->expects($this->once()) + ->method('setEtag') + ->with('newetag') + ->will($this->returnValue(true)); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::GETETAG_PROPERTYNAME => 'newetag', + self::LASTMODIFIED_PROPERTYNAME => $testDate + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::LASTMODIFIED_PROPERTYNAME]); + $this->assertEquals(200, $result[self::GETETAG_PROPERTYNAME]); + } + + /** + * Testcase from https://github.com/owncloud/core/issues/5251 + * + * |-FolderA + * |-text.txt + * |-test.txt + * + * FolderA is an incomming shared folder and there are no delete permissions. + * Thus moving /FolderA/test.txt to /test.txt should fail already on that check + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage FolderA/test.txt cannot be deleted + */ + public function testMoveSrcNotDeletable() { + $fileInfoFolderATestTXT = $this->getMockBuilder('\OCP\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $fileInfoFolderATestTXT->expects($this->once()) + ->method('isDeletable') + ->willReturn(false); + + $this->view->expects($this->once()) + ->method('getFileInfo') + ->with('FolderA/test.txt') + ->willReturn($fileInfoFolderATestTXT); + + $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); + } + + public function testMoveSrcDeletable() { + $fileInfoFolderATestTXT = $this->getMockBuilder('\OCP\Files\FileInfo') + ->disableOriginalConstructor() + ->getMock(); + $fileInfoFolderATestTXT->expects($this->once()) + ->method('isDeletable') + ->willReturn(true); + + $this->view->expects($this->once()) + ->method('getFileInfo') + ->with('FolderA/test.txt') + ->willReturn($fileInfoFolderATestTXT); + + $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/node.php b/apps/dav/tests/unit/connector/sabre/node.php new file mode 100644 index 00000000000..a9610fd84b3 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/node.php @@ -0,0 +1,52 @@ +<?php + +/** + * Copyright (c) 2014 Thomas Müller <thomas.mueller@tmit.eu> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre; + +class Node extends \Test\TestCase { + public function davPermissionsProvider() { + return array( + array(\OCP\Constants::PERMISSION_ALL, 'file', false, false, 'RDNVW'), + array(\OCP\Constants::PERMISSION_ALL, 'dir', false, false, 'RDNVCK'), + array(\OCP\Constants::PERMISSION_ALL, 'file', true, false, 'SRDNVW'), + array(\OCP\Constants::PERMISSION_ALL, 'file', true, true, 'SRMDNVW'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_SHARE, 'file', true, false, 'SDNVW'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_UPDATE, 'file', false, false, 'RDNV'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_DELETE, 'file', false, false, 'RW'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, 'file', false, false, 'RDNVW'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, 'dir', false, false, 'RDNV'), + ); + } + + /** + * @dataProvider davPermissionsProvider + */ + public function testDavPermissions($permissions, $type, $shared, $mounted, $expected) { + $info = $this->getMockBuilder('\OC\Files\FileInfo') + ->disableOriginalConstructor() + ->setMethods(array('getPermissions', 'isShared', 'isMounted', 'getType')) + ->getMock(); + $info->expects($this->any()) + ->method('getPermissions') + ->will($this->returnValue($permissions)); + $info->expects($this->any()) + ->method('isShared') + ->will($this->returnValue($shared)); + $info->expects($this->any()) + ->method('isMounted') + ->will($this->returnValue($mounted)); + $info->expects($this->any()) + ->method('getType') + ->will($this->returnValue($type)); + $view = $this->getMock('\OC\Files\View'); + + $node = new \OCA\DAV\Connector\Sabre\File($view, $info); + $this->assertEquals($expected, $node->getDavPermissions()); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/objecttree.php b/apps/dav/tests/unit/connector/sabre/objecttree.php new file mode 100644 index 00000000000..2691385c1c1 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/objecttree.php @@ -0,0 +1,291 @@ +<?php +/** + * Copyright (c) 2013 Thomas Müller <thomas.mueller@tmit.eu> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\OCA\DAV\Connector\Sabre; + + +use OC\Files\FileInfo; +use OCA\DAV\Connector\Sabre\Directory; +use OC\Files\Storage\Temporary; + +class TestDoubleFileView extends \OC\Files\View { + + public function __construct($updatables, $deletables, $canRename = true) { + $this->updatables = $updatables; + $this->deletables = $deletables; + $this->canRename = $canRename; + } + + public function isUpdatable($path) { + return $this->updatables[$path]; + } + + public function isCreatable($path) { + return $this->updatables[$path]; + } + + public function isDeletable($path) { + return $this->deletables[$path]; + } + + public function rename($path1, $path2) { + return $this->canRename; + } + + public function getRelativePath($path) { + return $path; + } +} + +class ObjectTree extends \Test\TestCase { + + /** + * @dataProvider moveFailedProvider + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + public function testMoveFailed($source, $destination, $updatables, $deletables) { + $this->moveTest($source, $destination, $updatables, $deletables); + } + + /** + * @dataProvider moveSuccessProvider + */ + public function testMoveSuccess($source, $destination, $updatables, $deletables) { + $this->moveTest($source, $destination, $updatables, $deletables); + $this->assertTrue(true); + } + + /** + * @dataProvider moveFailedInvalidCharsProvider + * @expectedException \OCA\DAV\Connector\Sabre\Exception\InvalidPath + */ + public function testMoveFailedInvalidChars($source, $destination, $updatables, $deletables) { + $this->moveTest($source, $destination, $updatables, $deletables); + } + + function moveFailedInvalidCharsProvider() { + return array( + array('a/b', 'a/*', array('a' => true, 'a/b' => true, 'a/c*' => false), array()), + ); + } + + function moveFailedProvider() { + return array( + array('a/b', 'a/c', array('a' => false, 'a/b' => false, 'a/c' => false), array()), + array('a/b', 'b/b', array('a' => false, 'a/b' => false, 'b' => false, 'b/b' => false), array()), + array('a/b', 'b/b', array('a' => false, 'a/b' => true, 'b' => false, 'b/b' => false), array()), + array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => false, 'b/b' => false), array()), + array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => true, 'b/b' => false), array('a/b' => false)), + array('a/b', 'a/c', array('a' => false, 'a/b' => true, 'a/c' => false), array()), + ); + } + + function moveSuccessProvider() { + return array( + array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => true, 'b/b' => false), array('a/b' => true)), + // older files with special chars can still be renamed to valid names + array('a/b*', 'b/b', array('a' => true, 'a/b*' => true, 'b' => true, 'b/b' => false), array('a/b*' => true)), + ); + } + + /** + * @param $source + * @param $destination + * @param $updatables + */ + private function moveTest($source, $destination, $updatables, $deletables) { + $view = new TestDoubleFileView($updatables, $deletables); + + $info = new FileInfo('', null, null, array(), null); + + $rootDir = new Directory($view, $info); + $objectTree = $this->getMock('\OCA\DAV\Connector\Sabre\ObjectTree', + array('nodeExists', 'getNodeForPath'), + array($rootDir, $view)); + + $objectTree->expects($this->once()) + ->method('getNodeForPath') + ->with($this->identicalTo($source)) + ->will($this->returnValue(false)); + + /** @var $objectTree \OCA\DAV\Connector\Sabre\ObjectTree */ + $mountManager = \OC\Files\Filesystem::getMountManager(); + $objectTree->init($rootDir, $view, $mountManager); + $objectTree->move($source, $destination); + } + + /** + * @dataProvider nodeForPathProvider + */ + public function testGetNodeForPath( + $inputFileName, + $fileInfoQueryPath, + $outputFileName, + $type, + $enableChunkingHeader + ) { + + if ($enableChunkingHeader) { + $_SERVER['HTTP_OC_CHUNKED'] = true; + } + + $rootNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->getMock('\OC\Files\Mount\Manager'); + $view = $this->getMock('\OC\Files\View'); + $fileInfo = $this->getMock('\OCP\Files\FileInfo'); + $fileInfo->expects($this->once()) + ->method('getType') + ->will($this->returnValue($type)); + $fileInfo->expects($this->once()) + ->method('getName') + ->will($this->returnValue($outputFileName)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with($fileInfoQueryPath) + ->will($this->returnValue($fileInfo)); + + $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree->init($rootNode, $view, $mountManager); + + $node = $tree->getNodeForPath($inputFileName); + + $this->assertNotNull($node); + $this->assertEquals($outputFileName, $node->getName()); + + if ($type === 'file') { + $this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\File); + } else { + $this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\Directory); + } + + unset($_SERVER['HTTP_OC_CHUNKED']); + } + + function nodeForPathProvider() { + return array( + // regular file + array( + 'regularfile.txt', + 'regularfile.txt', + 'regularfile.txt', + 'file', + false + ), + // regular directory + array( + 'regulardir', + 'regulardir', + 'regulardir', + 'dir', + false + ), + // regular file with chunking + array( + 'regularfile.txt', + 'regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + // regular directory with chunking + array( + 'regulardir', + 'regulardir', + 'regulardir', + 'dir', + true + ), + // file with chunky file name + array( + 'regularfile.txt-chunking-123566789-10-1', + 'regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + // regular file in subdir + array( + 'subdir/regularfile.txt', + 'subdir/regularfile.txt', + 'regularfile.txt', + 'file', + false + ), + // regular directory in subdir + array( + 'subdir/regulardir', + 'subdir/regulardir', + 'regulardir', + 'dir', + false + ), + // file with chunky file name in subdir + array( + 'subdir/regularfile.txt-chunking-123566789-10-1', + 'subdir/regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + ); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\InvalidPath + */ + public function testGetNodeForPathInvalidPath() { + $path = '/foo\bar'; + + + $storage = new Temporary([]); + + $view = $this->getMock('\OC\Files\View', ['resolvePath']); + $view->expects($this->once()) + ->method('resolvePath') + ->will($this->returnCallback(function($path) use ($storage){ + return [$storage, ltrim($path, '/')]; + })); + + $rootNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->getMock('\OC\Files\Mount\Manager'); + + $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree->init($rootNode, $view, $mountManager); + + $tree->getNodeForPath($path); + } + + public function testGetNodeForPathRoot() { + $path = '/'; + + + $storage = new Temporary([]); + + $view = $this->getMock('\OC\Files\View', ['resolvePath']); + $view->expects($this->any()) + ->method('resolvePath') + ->will($this->returnCallback(function ($path) use ($storage) { + return [$storage, ltrim($path, '/')]; + })); + + $rootNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->getMock('\OC\Files\Mount\Manager'); + + $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree->init($rootNode, $view, $mountManager); + + $this->assertInstanceOf('\Sabre\DAV\INode', $tree->getNodeForPath($path)); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/principal.php b/apps/dav/tests/unit/connector/sabre/principal.php new file mode 100644 index 00000000000..3c0abeac3f1 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/principal.php @@ -0,0 +1,250 @@ +<?php +/** + * @author Lukas Reschke + * @copyright 2014 Lukas Reschke lukas@owncloud.com + * + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre; + +use \Sabre\DAV\PropPatch; +use OCP\IUserManager; +use OCP\IConfig; + +class Principal extends \Test\TestCase { + /** @var IUserManager */ + private $userManager; + /** @var IConfig */ + private $config; + /** @var \OCA\DAV\Connector\Sabre\Principal */ + private $connector; + + public function setUp() { + $this->userManager = $this->getMockBuilder('\OCP\IUserManager') + ->disableOriginalConstructor()->getMock(); + $this->config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor()->getMock(); + + $this->connector = new \OCA\DAV\Connector\Sabre\Principal($this->config, $this->userManager); + parent::setUp(); + } + + public function testGetPrincipalsByPrefixWithoutPrefix() { + $response = $this->connector->getPrincipalsByPrefix(''); + $this->assertSame([], $response); + } + + public function testGetPrincipalsByPrefixWithUsers() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $barUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $barUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('bar')); + $this->userManager + ->expects($this->once()) + ->method('search') + ->with('') + ->will($this->returnValue([$fooUser, $barUser])); + $this->config + ->expects($this->at(0)) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('')); + $this->config + ->expects($this->at(1)) + ->method('getUserValue') + ->with('bar', 'settings', 'email') + ->will($this->returnValue('bar@owncloud.org')); + + $expectedResponse = [ + 0 => [ + 'uri' => 'principals/foo', + '{DAV:}displayname' => 'foo' + ], + 1 => [ + 'uri' => 'principals/bar', + '{DAV:}displayname' => 'bar', + '{http://sabredav.org/ns}email-address' => 'bar@owncloud.org' + ] + ]; + $response = $this->connector->getPrincipalsByPrefix('principals'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPrefixEmpty() { + $this->userManager + ->expects($this->once()) + ->method('search') + ->with('') + ->will($this->returnValue([])); + + $response = $this->connector->getPrincipalsByPrefix('principals'); + $this->assertSame([], $response); + } + + public function testGetPrincipalsByPathWithoutMail() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('')); + + $expectedResponse = [ + 'uri' => 'principals/foo', + '{DAV:}displayname' => 'foo' + ]; + $response = $this->connector->getPrincipalByPath('principals/foo'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPathWithMail() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('foo@owncloud.org')); + + $expectedResponse = [ + 'uri' => 'principals/foo', + '{DAV:}displayname' => 'foo', + '{http://sabredav.org/ns}email-address' => 'foo@owncloud.org' + ]; + $response = $this->connector->getPrincipalByPath('principals/foo'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPathEmpty() { + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue(null)); + + $response = $this->connector->getPrincipalByPath('principals/foo'); + $this->assertSame(null, $response); + } + + public function testGetGroupMemberSet() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('foo@owncloud.org')); + + $response = $this->connector->getGroupMemberSet('principals/foo'); + $this->assertSame(['principals/foo'], $response); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Principal not found + */ + public function testGetGroupMemberSetEmpty() { + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue(null)); + + $this->connector->getGroupMemberSet('principals/foo'); + } + + public function testGetGroupMembership() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(3)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('foo', 'settings', 'email') + ->will($this->returnValue('foo@owncloud.org')); + + $expectedResponse = [ + 'principals/foo/calendar-proxy-read', + 'principals/foo/calendar-proxy-write' + ]; + $response = $this->connector->getGroupMembership('principals/foo'); + $this->assertSame($expectedResponse, $response); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Principal not found + */ + public function testGetGroupMembershipEmpty() { + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue(null)); + + $this->connector->getGroupMembership('principals/foo'); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Setting members of the group is not supported yet + */ + public function testSetGroupMembership() { + $this->connector->setGroupMemberSet('principals/foo', ['foo']); + } + + public function testUpdatePrincipal() { + $this->assertSame(0, $this->connector->updatePrincipal('foo', new PropPatch(array()))); + } + + public function testSearchPrincipals() { + $this->assertSame([], $this->connector->searchPrincipals('principals', [])); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/quotaplugin.php b/apps/dav/tests/unit/connector/sabre/quotaplugin.php new file mode 100644 index 00000000000..5d3364e1f8c --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/quotaplugin.php @@ -0,0 +1,103 @@ +<?php + +/** + * Copyright (c) 2013 Thomas Müller <thomas.mueller@tmit.eu> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class Test_OC_Connector_Sabre_QuotaPlugin extends \Test\TestCase { + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \OCA\DAV\Connector\Sabre\QuotaPlugin + */ + private $plugin; + + private function init($quota) { + $view = $this->buildFileViewMock($quota); + $this->server = new \Sabre\DAV\Server(); + $this->plugin = new \OCA\DAV\Connector\Sabre\QuotaPlugin($view); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider lengthProvider + */ + public function testLength($expected, $headers) { + $this->init(0); + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $length = $this->plugin->getLength(); + $this->assertEquals($expected, $length); + } + + /** + * @dataProvider quotaOkayProvider + */ + public function testCheckQuota($quota, $headers) { + $this->init($quota); + + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $result = $this->plugin->checkQuota(''); + $this->assertTrue($result); + } + + /** + * @expectedException \Sabre\DAV\Exception\InsufficientStorage + * @dataProvider quotaExceededProvider + */ + public function testCheckExceededQuota($quota, $headers) { + $this->init($quota); + + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $this->plugin->checkQuota(''); + } + + public function quotaOkayProvider() { + return array( + array(1024, array()), + array(1024, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(1024, array('CONTENT-LENGTH' => '512')), + array(1024, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + // \OCP\Files\FileInfo::SPACE-UNKNOWN = -2 + array(-2, array()), + array(-2, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(-2, array('CONTENT-LENGTH' => '512')), + array(-2, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + ); + } + + public function quotaExceededProvider() { + return array( + array(1023, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(511, array('CONTENT-LENGTH' => '512')), + array(2047, array('OC-TOTAL-LENGTH' => '2048', 'CONTENT-LENGTH' => '1024')), + ); + } + + public function lengthProvider() { + return array( + array(null, array()), + array(1024, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(512, array('CONTENT-LENGTH' => '512')), + array(2048, array('OC-TOTAL-LENGTH' => '2048', 'CONTENT-LENGTH' => '1024')), + array(4096, array('OC-TOTAL-LENGTH' => '2048', 'X-EXPECTED-ENTITY-LENGTH' => '4096')), + ); + } + + private function buildFileViewMock($quota) { + // mock filesysten + $view = $this->getMock('\OC\Files\View', array('free_space'), array(), '', false); + $view->expects($this->any()) + ->method('free_space') + ->with($this->identicalTo('')) + ->will($this->returnValue($quota)); + + return $view; + } + +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/auth.php b/apps/dav/tests/unit/connector/sabre/requesttest/auth.php new file mode 100644 index 00000000000..7cab4da5264 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/auth.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre\RequestTest; + +use Sabre\DAV\Auth\Backend\BackendInterface; + +class Auth implements BackendInterface { + /** + * @var string + */ + private $user; + + /** + * @var string + */ + private $password; + + /** + * Auth constructor. + * + * @param string $user + * @param string $password + */ + public function __construct($user, $password) { + $this->user = $user; + $this->password = $password; + } + + + /** + * Authenticates the user based on the current request. + * + * If authentication is successful, true must be returned. + * If authentication fails, an exception must be thrown. + * + * @param \Sabre\DAV\Server $server + * @param string $realm + * @return bool + */ + function authenticate(\Sabre\DAV\Server $server, $realm) { + $userSession = \OC::$server->getUserSession(); + $result = $userSession->login($this->user, $this->password); + if ($result) { + //we need to pass the user name, which may differ from login name + $user = $userSession->getUser()->getUID(); + \OC_Util::setupFS($user); + //trigger creation of user home and /files folder + \OC::$server->getUserFolder($user); + } + return $result; + } + + /** + * Returns information about the currently logged in username. + * + * If nobody is currently logged in, this method should return null. + * + * @return string|null + */ + function getCurrentUser() { + return $this->user; + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/exceptionplugin.php b/apps/dav/tests/unit/connector/sabre/requesttest/exceptionplugin.php new file mode 100644 index 00000000000..606b5a27984 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/exceptionplugin.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre\RequestTest; + +use Sabre\DAV\Exception; + +class ExceptionPlugin extends \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin { + /** + * @var \Exception[] + */ + protected $exceptions = []; + + public function logException(\Exception $ex) { + $exceptionClass = get_class($ex); + if (!isset($this->nonFatalExceptions[$exceptionClass])) { + $this->exceptions[] = $ex; + } + } + + /** + * @return \Exception[] + */ + public function getExceptions() { + return $this->exceptions; + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/requesttest.php b/apps/dav/tests/unit/connector/sabre/requesttest/requesttest.php new file mode 100644 index 00000000000..4e1bc16819c --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/requesttest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre\RequestTest; + +use OCA\DAV\Connector\Sabre\Server; +use OCA\DAV\Connector\Sabre\ServerFactory; +use OC\Files\Mount\MountPoint; +use OC\Files\Storage\StorageFactory; +use OC\Files\Storage\Temporary; +use OC\Files\View; +use OCP\IUser; +use Sabre\HTTP\Request; +use Test\TestCase; +use Test\Traits\MountProviderTrait; +use Test\Traits\UserTrait; + +abstract class RequestTest extends TestCase { + use UserTrait; + use MountProviderTrait; + + /** + * @var \OCA\DAV\Connector\Sabre\ServerFactory + */ + protected $serverFactory; + + protected function getStream($string) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $string); + fseek($stream, 0); + return $stream; + } + + protected function setUp() { + parent::setUp(); + + $this->serverFactory = new ServerFactory( + \OC::$server->getConfig(), + \OC::$server->getLogger(), + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserSession(), + \OC::$server->getMountManager(), + \OC::$server->getTagManager(), + \OC::$server->getEventDispatcher() + ); + } + + protected function setupUser($name, $password) { + $this->createUser($name, $password); + $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); + $this->loginAsUser($name); + return new View('/' . $name . '/files'); + } + + /** + * @param \OC\Files\View $view the view to run the webdav server against + * @param string $user + * @param string $password + * @param string $method + * @param string $url + * @param resource|string|null $body + * @param array|null $headers + * @return \Sabre\HTTP\Response + */ + protected function request($view, $user, $password, $method, $url, $body = null, $headers = null) { + if (is_string($body)) { + $body = $this->getStream($body); + } + $this->logout(); + $exceptionPlugin = new ExceptionPlugin('webdav', null); + $server = $this->getSabreServer($view, $user, $password, $exceptionPlugin); + $request = new Request($method, $url, $headers, $body); + + // since sabre catches all exceptions we need to save them and throw them from outside the sabre server + + $originalServer = $_SERVER; + + if (is_array($headers)) { + foreach ($headers as $header => $value) { + $_SERVER['HTTP_' . strtoupper(str_replace('-', '_', $header))] = $value; + } + } + + $result = $this->makeRequest($server, $request); + + foreach ($exceptionPlugin->getExceptions() as $exception) { + throw $exception; + } + $_SERVER = $originalServer; + return $result; + } + + /** + * @param Server $server + * @param Request $request + * @return \Sabre\HTTP\Response + */ + protected function makeRequest(Server $server, Request $request) { + $sapi = new Sapi($request); + $server->sapi = $sapi; + $server->httpRequest = $request; + $server->exec(); + return $sapi->getResponse(); + } + + /** + * @param View $view + * @param string $user + * @param string $password + * @param ExceptionPlugin $exceptionPlugin + * @return Server + */ + protected function getSabreServer(View $view, $user, $password, ExceptionPlugin $exceptionPlugin) { + $authBackend = new Auth($user, $password); + + $server = $this->serverFactory->createServer('/', 'dummy', $authBackend, function () use ($view) { + return $view; + }); + $server->addPlugin($exceptionPlugin); + + return $server; + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/sapi.php b/apps/dav/tests/unit/connector/sabre/requesttest/sapi.php new file mode 100644 index 00000000000..cda9fdb70f5 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/sapi.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre\RequestTest; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class Sapi { + /** + * @var \Sabre\HTTP\Request + */ + private $request; + + /** + * @var \Sabre\HTTP\Response + */ + private $response; + + /** + * This static method will create a new Request object, based on the + * current PHP request. + * + * @return \Sabre\HTTP\Request + */ + public function getRequest() { + return $this->request; + } + + public function __construct(Request $request) { + $this->request = $request; + } + + /** + * @param \Sabre\HTTP\Response $response + * @return void + */ + public function sendResponse(Response $response) { + // we need to copy the body since we close the source stream + $copyStream = fopen('php://temp', 'r+'); + if (is_string($response->getBody())) { + fwrite($copyStream, $response->getBody()); + } else if (is_resource($response->getBody())) { + stream_copy_to_stream($response->getBody(), $copyStream); + } + rewind($copyStream); + $this->response = new Response($response->getStatus(), $response->getHeaders(), $copyStream); + } + + /** + * @return \Sabre\HTTP\Response + */ + public function getResponse() { + return $this->response; + } +} diff --git a/apps/dav/tests/unit/connector/sabre/requesttest/uploadtest.php b/apps/dav/tests/unit/connector/sabre/requesttest/uploadtest.php new file mode 100644 index 00000000000..8f39aff81b9 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/requesttest/uploadtest.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright (c) 2015 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Connector\Sabre\RequestTest; + +use OC\AppFramework\Http; + +class UploadTest extends RequestTest { + public function testBasicUpload() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + $this->assertEquals('asd', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(3, $info->getSize()); + } + + public function testUploadOverWrite() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'foobar'); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + + $this->assertEquals(Http::STATUS_NO_CONTENT, $response->getStatus()); + $this->assertEquals('asd', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(3, $info->getSize()); + } + + public function testChunkedUpload() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(6, $info->getSize()); + } + + public function testChunkedUploadOverWrite() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertEquals('bar', $view->file_get_contents('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(6, $info->getSize()); + } + + public function testChunkedUploadOutOfOrder() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + + $this->assertEquals(201, $response->getStatus()); + $this->assertTrue($view->file_exists('foo.txt')); + + $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); + + $info = $view->getFileInfo('foo.txt'); + $this->assertInstanceOf('\OC\Files\FileInfo', $info); + $this->assertEquals(6, $info->getSize()); + } +} diff --git a/apps/dav/tests/unit/connector/sabre/tagsplugin.php b/apps/dav/tests/unit/connector/sabre/tagsplugin.php new file mode 100644 index 00000000000..4731e770cfa --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/tagsplugin.php @@ -0,0 +1,398 @@ +<?php + +namespace Tests\Connector\Sabre; + +/** + * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class TagsPlugin extends \Test\TestCase { + + const TAGS_PROPERTYNAME = \OCA\DAV\Connector\Sabre\TagsPlugin::TAGS_PROPERTYNAME; + const FAVORITE_PROPERTYNAME = \OCA\DAV\Connector\Sabre\TagsPlugin::FAVORITE_PROPERTYNAME; + const TAG_FAVORITE = \OCA\DAV\Connector\Sabre\TagsPlugin::TAG_FAVORITE; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\ObjectTree + */ + private $tree; + + /** + * @var \OCP\ITagManager + */ + private $tagManager; + + /** + * @var \OCP\ITags + */ + private $tagger; + + /** + * @var \OCA\DAV\Connector\Sabre\TagsPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + $this->tagger = $this->getMock('\OCP\ITags'); + $this->tagManager = $this->getMock('\OCP\ITagManager'); + $this->tagManager->expects($this->any()) + ->method('load') + ->with('files') + ->will($this->returnValue($this->tagger)); + $this->plugin = new \OCA\DAV\Connector\Sabre\TagsPlugin($this->tree, $this->tagManager); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider tagsGetPropertiesDataProvider + */ + public function testGetProperties($tags, $requestedProperties, $expectedProperties) { + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $expectedCallCount = 0; + if (count($requestedProperties) > 0) { + $expectedCallCount = 1; + } + + $this->tagger->expects($this->exactly($expectedCallCount)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array(123 => $tags))); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + $requestedProperties, + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $result = $propFind->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($expectedProperties, $result); + } + + /** + * @dataProvider tagsGetPropertiesDataProvider + */ + public function testPreloadThenGetProperties($tags, $requestedProperties, $expectedProperties) { + $node1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $node1->expects($this->any()) + ->method('getId') + ->will($this->returnValue(111)); + $node2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $node2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(222)); + + $expectedCallCount = 0; + if (count($requestedProperties) > 0) { + // this guarantees that getTagsForObjects + // is only called once and then the tags + // are cached + $expectedCallCount = 1; + } + + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + $node->expects($this->exactly($expectedCallCount)) + ->method('getChildren') + ->will($this->returnValue(array($node1, $node2))); + + $this->tagger->expects($this->exactly($expectedCallCount)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123, 111, 222))) + ->will($this->returnValue( + array( + 111 => $tags, + 123 => $tags + ) + )); + + // simulate sabre recursive PROPFIND traversal + $propFindRoot = new \Sabre\DAV\PropFind( + '/subdir', + $requestedProperties, + 1 + ); + $propFind1 = new \Sabre\DAV\PropFind( + '/subdir/test.txt', + $requestedProperties, + 0 + ); + $propFind2 = new \Sabre\DAV\PropFind( + '/subdir/test2.txt', + $requestedProperties, + 0 + ); + + $this->plugin->handleGetProperties( + $propFindRoot, + $node + ); + $this->plugin->handleGetProperties( + $propFind1, + $node1 + ); + $this->plugin->handleGetProperties( + $propFind2, + $node2 + ); + + $result = $propFind1->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($expectedProperties, $result); + } + + function tagsGetPropertiesDataProvider() { + return array( + // request both, receive both + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array('tag1', 'tag2')), + self::FAVORITE_PROPERTYNAME => true, + ) + ) + ), + // request tags alone + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::TAGS_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array('tag1', 'tag2')), + ) + ) + ), + // request fav alone + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::FAVORITE_PROPERTYNAME => true, + ) + ) + ), + // request none + array( + array('tag1', 'tag2', self::TAG_FAVORITE), + array(), + array( + 200 => array() + ), + ), + // request both with none set, receive both + array( + array(), + array(self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME), + array( + 200 => array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array()), + self::FAVORITE_PROPERTYNAME => false, + ) + ) + ), + ); + } + + public function testUpdateTags() { + // this test will replace the existing tags "tagremove" with "tag1" and "tag2" + // and keep "tagkeep" + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->tagger->expects($this->at(0)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array(123 => array('tagkeep', 'tagremove', self::TAG_FAVORITE)))); + + // then tag as tag1 and tag2 + $this->tagger->expects($this->at(1)) + ->method('tagAs') + ->with(123, 'tag1'); + $this->tagger->expects($this->at(2)) + ->method('tagAs') + ->with(123, 'tag2'); + + // it will untag tag3 + $this->tagger->expects($this->at(3)) + ->method('unTag') + ->with(123, 'tagremove'); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')) + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]); + $this->assertFalse(isset($result[self::FAVORITE_PROPERTYNAME])); + } + + public function testUpdateTagsFromScratch() { + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->tagger->expects($this->at(0)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array())); + + // then tag as tag1 and tag2 + $this->tagger->expects($this->at(1)) + ->method('tagAs') + ->with(123, 'tag1'); + $this->tagger->expects($this->at(2)) + ->method('tagAs') + ->with(123, 'tag2'); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')) + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]); + $this->assertFalse(false, isset($result[self::FAVORITE_PROPERTYNAME])); + } + + public function testUpdateFav() { + // this test will replace the existing tags "tagremove" with "tag1" and "tag2" + // and keep "tagkeep" + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + // set favorite tag + $this->tagger->expects($this->once()) + ->method('tagAs') + ->with(123, self::TAG_FAVORITE); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::FAVORITE_PROPERTYNAME => true + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertFalse(false, isset($result[self::TAGS_PROPERTYNAME])); + $this->assertEquals(200, isset($result[self::FAVORITE_PROPERTYNAME])); + + // unfavorite now + // set favorite tag + $this->tagger->expects($this->once()) + ->method('unTag') + ->with(123, self::TAG_FAVORITE); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::FAVORITE_PROPERTYNAME => false + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertFalse(false, isset($result[self::TAGS_PROPERTYNAME])); + $this->assertEquals(200, isset($result[self::FAVORITE_PROPERTYNAME])); + } + +} |