diff options
author | Joas Schilling <nickvergessen@owncloud.com> | 2016-05-25 16:04:15 +0200 |
---|---|---|
committer | Joas Schilling <nickvergessen@owncloud.com> | 2016-05-25 16:09:18 +0200 |
commit | 5882e21b3bff0afe2152eb3971d674bdb91e45a2 (patch) | |
tree | 39dee4b6791c25405b31f2df13b430e7d37c4767 /apps/dav/tests/unit/Connector/Sabre | |
parent | 42ba61db044f1d08e22089fbc3badafbc35d5ea4 (diff) | |
download | nextcloud-server-5882e21b3bff0afe2152eb3971d674bdb91e45a2.tar.gz nextcloud-server-5882e21b3bff0afe2152eb3971d674bdb91e45a2.zip |
Update DAV unit tests to PSR-4
Diffstat (limited to 'apps/dav/tests/unit/Connector/Sabre')
29 files changed, 6560 insertions, 0 deletions
diff --git a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php new file mode 100644 index 00000000000..1c29d2dbd6f --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php @@ -0,0 +1,611 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Roeland Jago Douma <rullzer@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OCP\IRequest; +use OCP\IUser; +use Test\TestCase; +use OCP\ISession; +use OC\User\Session; + +/** + * Class AuthTest + * + * @package OCA\DAV\Tests\unit\Connector\Sabre + * @group DB + */ +class AuthTest extends TestCase { + /** @var ISession */ + private $session; + /** @var \OCA\DAV\Connector\Sabre\Auth */ + private $auth; + /** @var Session */ + private $userSession; + /** @var IRequest */ + private $request; + + public function setUp() { + parent::setUp(); + $this->session = $this->getMockBuilder('\OCP\ISession') + ->disableOriginalConstructor()->getMock(); + $this->userSession = $this->getMockBuilder('\OC\User\Session') + ->disableOriginalConstructor()->getMock(); + $this->request = $this->getMockBuilder('\OCP\IRequest') + ->disableOriginalConstructor()->getMock(); + $this->auth = new \OCA\DAV\Connector\Sabre\Auth( + $this->session, + $this->userSession, + $this->request + ); + } + + public function testIsDavAuthenticatedWithoutDavSession() { + $this->session + ->expects($this->once()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue(null)); + + $this->assertFalse($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + } + + public function testIsDavAuthenticatedWithWrongDavSession() { + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('AnotherUser')); + + $this->assertFalse($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + } + + public function testIsDavAuthenticatedWithCorrectDavSession() { + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('MyTestUser')); + + $this->assertTrue($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + } + + public function testValidateUserPassOfAlreadyDAVAuthenticatedUser() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->exactly(2)) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->exactly(2)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('MyTestUser')); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertTrue($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + } + + public function testValidateUserPassOfInvalidDAVAuthenticatedUser() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('AnotherUser')); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertFalse($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + } + + public function testValidateUserPassOfInvalidDAVAuthenticatedUserWithValidPassword() { + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->exactly(4)) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->exactly(4)) + ->method('getUser') + ->will($this->returnValue($user)); + $this->session + ->expects($this->exactly(2)) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('AnotherUser')); + $this->userSession + ->expects($this->once()) + ->method('logClientIn') + ->with('MyTestUser', 'MyTestPassword') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->once()) + ->method('createSessionToken') + ->with($this->request, 'MyTestUser', 'MyTestUser', 'MyTestPassword'); + $this->session + ->expects($this->once()) + ->method('set') + ->with('AUTHENTICATED_TO_DAV_BACKEND', 'MyTestUser'); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertTrue($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + } + + public function testValidateUserPassWithInvalidPassword() { + $this->userSession + ->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(false)); + $this->userSession + ->expects($this->once()) + ->method('logClientIn') + ->with('MyTestUser', 'MyTestPassword') + ->will($this->returnValue(false)); + $this->session + ->expects($this->once()) + ->method('close'); + + $this->assertFalse($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + } + + + public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForNonGet() { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->request + ->expects($this->any()) + ->method('getMethod') + ->willReturn('POST'); + $this->session + ->expects($this->any()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue(null)); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('MyWrongDavUser')); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->request + ->expects($this->once()) + ->method('passesCSRFCheck') + ->willReturn(false); + + $expectedResponse = [ + false, + "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is mis-configured", + ]; + $response = $this->auth->check($request, $response); + $this->assertSame($expectedResponse, $response); + } + + public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenAndCorrectlyDavAuthenticated() { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->willReturn(true); + $this->request + ->expects($this->any()) + ->method('getMethod') + ->willReturn('PROPFIND'); + $this->request + ->expects($this->any()) + ->method('isUserAgent') + ->with([ + '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/', + '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/', + '/^Mozilla\/5\.0 \(iOS\) ownCloud\-iOS.*$/', + ]) + ->willReturn(false); + $this->session + ->expects($this->any()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('LoggedInUser')); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('LoggedInUser')); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->request + ->expects($this->once()) + ->method('passesCSRFCheck') + ->willReturn(false); + $this->auth->check($request, $response); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotAuthenticated + * @expectedExceptionMessage CSRF check not passed. + */ + public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenAndIncorrectlyDavAuthenticated() { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->willReturn(true); + $this->request + ->expects($this->any()) + ->method('getMethod') + ->willReturn('PROPFIND'); + $this->request + ->expects($this->any()) + ->method('isUserAgent') + ->with([ + '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/', + '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/', + '/^Mozilla\/5\.0 \(iOS\) ownCloud\-iOS.*$/', + ]) + ->willReturn(false); + $this->session + ->expects($this->any()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('AnotherUser')); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('LoggedInUser')); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->request + ->expects($this->once()) + ->method('passesCSRFCheck') + ->willReturn(false); + $this->auth->check($request, $response); + } + + public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForNonGetAndDesktopClient() { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->request + ->expects($this->any()) + ->method('getMethod') + ->willReturn('POST'); + $this->request + ->expects($this->any()) + ->method('isUserAgent') + ->with([ + '/^Mozilla\/5\.0 \([A-Za-z ]+\) (mirall|csyncoC)\/.*$/', + '/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/', + '/^Mozilla\/5\.0 \(iOS\) ownCloud\-iOS.*$/', + ]) + ->willReturn(true); + $this->session + ->expects($this->any()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue(null)); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('MyWrongDavUser')); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->request + ->expects($this->once()) + ->method('passesCSRFCheck') + ->willReturn(false); + + $this->auth->check($request, $response); + } + + public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForGet() { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->session + ->expects($this->any()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue(null)); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('MyWrongDavUser')); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->request + ->expects($this->any()) + ->method('getMethod') + ->willReturn('GET'); + + $response = $this->auth->check($request, $response); + $this->assertEquals([true, 'principals/users/MyWrongDavUser'], $response); + } + + public function testAuthenticateAlreadyLoggedInWithCsrfTokenForGet() { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->session + ->expects($this->any()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue(null)); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('MyWrongDavUser')); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($user)); + $this->request + ->expects($this->once()) + ->method('passesCSRFCheck') + ->willReturn(true); + + $response = $this->auth->check($request, $response); + $this->assertEquals([true, 'principals/users/MyWrongDavUser'], $response); + } + + public function testAuthenticateNoBasicAuthenticateHeadersProvided() { + $server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $server->httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->auth->check($server->httpRequest, $server->httpResponse); + $this->assertEquals([false, 'No \'Authorization: Basic\' header found. Either the client didn\'t send one, or the server is mis-configured'], $response); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotAuthenticated + * @expectedExceptionMessage Cannot authenticate over ajax calls + */ + public function testAuthenticateNoBasicAuthenticateHeadersProvidedWithAjax() { + /** @var \Sabre\HTTP\RequestInterface $httpRequest */ + $httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + /** @var \Sabre\HTTP\ResponseInterface $httpResponse */ + $httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(false)); + $httpRequest + ->expects($this->once()) + ->method('getHeader') + ->with('X-Requested-With') + ->will($this->returnValue('XMLHttpRequest')); + $this->auth->check($httpRequest, $httpResponse); + } + + public function testAuthenticateNoBasicAuthenticateHeadersProvidedWithAjaxButUserIsStillLoggedIn() { + /** @var \Sabre\HTTP\RequestInterface $httpRequest */ + $httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + /** @var \Sabre\HTTP\ResponseInterface $httpResponse */ + $httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + /** @var IUser */ + $user = $this->getMock('OCP\IUser'); + $user->method('getUID')->willReturn('MyTestUser'); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $this->session + ->expects($this->atLeastOnce()) + ->method('get') + ->with('AUTHENTICATED_TO_DAV_BACKEND') + ->will($this->returnValue('MyTestUser')); + $this->request + ->expects($this->once()) + ->method('getMethod') + ->willReturn('GET'); + $httpRequest + ->expects($this->atLeastOnce()) + ->method('getHeader') + ->with('Authorization') + ->will($this->returnValue(null)); + $this->assertEquals( + [true, 'principals/users/MyTestUser'], + $this->auth->check($httpRequest, $httpResponse) + ); + } + + public function testAuthenticateValidCredentials() { + $server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest + ->expects($this->at(0)) + ->method('getHeader') + ->with('X-Requested-With') + ->will($this->returnValue(null)); + $server->httpRequest + ->expects($this->at(1)) + ->method('getHeader') + ->with('Authorization') + ->will($this->returnValue('basic dXNlcm5hbWU6cGFzc3dvcmQ=')); + $server->httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->once()) + ->method('logClientIn') + ->with('username', 'password') + ->will($this->returnValue(true)); + $this->userSession + ->expects($this->once()) + ->method('createSessionToken'); + $user = $this->getMockBuilder('\OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->exactly(4)) + ->method('getUID') + ->will($this->returnValue('MyTestUser')); + $this->userSession + ->expects($this->exactly(4)) + ->method('getUser') + ->will($this->returnValue($user)); + $response = $this->auth->check($server->httpRequest, $server->httpResponse); + $this->assertEquals([true, 'principals/users/MyTestUser'], $response); + } + + public function testAuthenticateInvalidCredentials() { + $server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest = $this->getMockBuilder('\Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $server->httpRequest + ->expects($this->at(0)) + ->method('getHeader') + ->with('X-Requested-With') + ->will($this->returnValue(null)); + $server->httpRequest + ->expects($this->at(1)) + ->method('getHeader') + ->with('Authorization') + ->will($this->returnValue('basic dXNlcm5hbWU6cGFzc3dvcmQ=')); + $server->httpResponse = $this->getMockBuilder('\Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->userSession + ->expects($this->once()) + ->method('logClientIn') + ->with('username', 'password') + ->will($this->returnValue(false)); + $response = $this->auth->check($server->httpRequest, $server->httpResponse); + $this->assertEquals([false, 'Username or password was incorrect'], $response); + } +} 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..00378fd4139 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/BlockLegacyClientPluginTest.php @@ -0,0 +1,130 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; +use Test\TestCase; +use OCP\IConfig; + +/** + * Class BlockLegacyClientPluginTest + * + * @package OCA\DAV\Tests\unit\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/CommentsPropertiesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/CommentsPropertiesPluginTest.php new file mode 100644 index 00000000000..d98c2228ebd --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/CommentsPropertiesPluginTest.php @@ -0,0 +1,149 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin as CommentPropertiesPluginImplementation; + +class CommentsPropertiesPluginTest extends \Test\TestCase { + + /** @var CommentPropertiesPluginImplementation */ + protected $plugin; + protected $commentsManager; + protected $userSession; + protected $server; + + public function setUp() { + parent::setUp(); + + $this->commentsManager = $this->getMock('\OCP\Comments\ICommentsManager'); + $this->userSession = $this->getMock('\OCP\IUserSession'); + + $this->server = $this->getMockBuilder('\Sabre\DAV\Server') + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new CommentPropertiesPluginImplementation($this->commentsManager, $this->userSession); + $this->plugin->initialize($this->server); + } + + public function nodeProvider() { + $mocks = []; + foreach(['\OCA\DAV\Connector\Sabre\File', '\OCA\DAV\Connector\Sabre\Directory', '\Sabre\DAV\INode'] as $class) { + $mocks[] = $this->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + } + + return [ + [$mocks[0], true], + [$mocks[1], true], + [$mocks[2], false] + ]; + } + + /** + * @dataProvider nodeProvider + * @param $node + * @param $expectedSuccessful + */ + public function testHandleGetProperties($node, $expectedSuccessful) { + $propFind = $this->getMockBuilder('\Sabre\DAV\PropFind') + ->disableOriginalConstructor() + ->getMock(); + + if($expectedSuccessful) { + $propFind->expects($this->exactly(3)) + ->method('handle'); + } else { + $propFind->expects($this->never()) + ->method('handle'); + } + + $this->plugin->handleGetProperties($propFind, $node); + } + + public function baseUriProvider() { + return [ + ['owncloud/remote.php/webdav/', '4567', 'owncloud/remote.php/dav/comments/files/4567'], + ['owncloud/remote.php/files/', '4567', 'owncloud/remote.php/dav/comments/files/4567'], + ['owncloud/wicked.php/files/', '4567', null] + ]; + } + + /** + * @dataProvider baseUriProvider + * @param $baseUri + * @param $fid + * @param $expectedHref + */ + public function testGetCommentsLink($baseUri, $fid, $expectedHref) { + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue($fid)); + + $this->server->expects($this->once()) + ->method('getBaseUri') + ->will($this->returnValue($baseUri)); + + $href = $this->plugin->getCommentsLink($node); + $this->assertSame($expectedHref, $href); + } + + public function userProvider() { + return [ + [$this->getMock('\OCP\IUser')], + [null] + ]; + } + + /** + * @dataProvider userProvider + * @param $user + */ + public function testGetUnreadCount($user) { + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue('4567')); + + $this->userSession->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + + $this->commentsManager->expects($this->any()) + ->method('getNumberOfCommentsForObject') + ->will($this->returnValue(42)); + + $unread = $this->plugin->getUnreadCount($node); + if(is_null($user)) { + $this->assertNull($unread); + } else { + $this->assertSame($unread, 42); + } + } + +} 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..b8695e0a5d5 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/CopyEtagHeaderPluginTest.php @@ -0,0 +1,62 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\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 CopyEtagHeaderPluginTest 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/CustomPropertiesBackendTest.php b/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php new file mode 100644 index 00000000000..0ae6bb014ad --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php @@ -0,0 +1,313 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\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 + * + * @group DB + * + * @package OCA\DAV\Tests\unit\Connector\Sabre + */ +class CustomPropertiesBackendTest extends \Test\TestCase { + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\Tree + */ + 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/DirectoryTest.php b/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php new file mode 100644 index 00000000000..66f0a803807 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php @@ -0,0 +1,267 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\Unit\Connector\Sabre; + +use OCP\Files\ForbiddenException; + +/** + * @group DB + */ +class DirectoryTest 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(); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden + */ + public function testDeleteForbidden() { + // deletion allowed + $this->info->expects($this->once()) + ->method('isDeletable') + ->will($this->returnValue(true)); + + // but fails + $this->view->expects($this->once()) + ->method('rmdir') + ->with('sub') + ->willThrowException(new ForbiddenException('', true)); + + $dir = $this->getDir('sub'); + $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 testGetQuotaInfoUnlimited() { + $storage = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Quota') + ->disableOriginalConstructor() + ->getMock(); + + $storage->expects($this->any()) + ->method('instanceOfStorage') + ->will($this->returnValueMap([ + '\OC\Files\Storage\Shared' => false, + '\OC\Files\Storage\Wrapper\Quota' => false, + ])); + + $storage->expects($this->never()) + ->method('getQuota'); + + $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, -3], $dir->getQuotaInfo()); //200 used, unlimited + } + + public function testGetQuotaInfoSpecific() { + $storage = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Quota') + ->disableOriginalConstructor() + ->getMock(); + + $storage->expects($this->any()) + ->method('instanceOfStorage') + ->will($this->returnValueMap([ + ['\OC\Files\Storage\Shared', false], + ['\OC\Files\Storage\Wrapper\Quota', 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/DummyGetResponsePluginTest.php b/apps/dav/tests/unit/Connector/Sabre/DummyGetResponsePluginTest.php new file mode 100644 index 00000000000..125cb174c58 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/DummyGetResponsePluginTest.php @@ -0,0 +1,70 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\DummyGetResponsePlugin; +use Test\TestCase; + +/** + * Class DummyGetResponsePluginTest + * + * @package OCA\DAV\Tests\unit\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/Exception/ForbiddenTest.php b/apps/dav/tests/unit/Connector/Sabre/Exception/ForbiddenTest.php new file mode 100644 index 00000000000..44e9e17b695 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/Exception/ForbiddenTest.php @@ -0,0 +1,57 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre\Exception; + +use OCA\DAV\Connector\Sabre\Exception\Forbidden; + +class ForbiddenTest 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 Forbidden($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/Exception/InvalidPathTest.php b/apps/dav/tests/unit/Connector/Sabre/Exception/InvalidPathTest.php new file mode 100644 index 00000000000..0e1224f596a --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/Exception/InvalidPathTest.php @@ -0,0 +1,58 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre\Exception; + +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; + +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/ExceptionLoggerPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/ExceptionLoggerPluginTest.php new file mode 100644 index 00000000000..f6102fa4e9b --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/ExceptionLoggerPluginTest.php @@ -0,0 +1,83 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\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 ExceptionLoggerPluginTest 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/FakeLockerPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FakeLockerPluginTest.php new file mode 100644 index 00000000000..98edfb3c5b4 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/FakeLockerPluginTest.php @@ -0,0 +1,174 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\FakeLockerPlugin; +use Sabre\HTTP\Response; +use Test\TestCase; + +/** + * Class FakeLockerPluginTest + * + * @package OCA\DAV\Tests\unit\Connector\Sabre + */ +class FakeLockerPluginTest extends TestCase { + /** @var FakeLockerPlugin */ + private $fakeLockerPlugin; + + public function setUp() { + parent::setUp(); + $this->fakeLockerPlugin = new FakeLockerPlugin(); + } + + public function testInitialize() { + /** @var \Sabre\DAV\Server $server */ + $server = $this->getMock('\Sabre\DAV\Server'); + $server + ->expects($this->at(0)) + ->method('on') + ->with('method:LOCK', [$this->fakeLockerPlugin, 'fakeLockProvider'], 1); + $server + ->expects($this->at(1)) + ->method('on') + ->with('method:UNLOCK', [$this->fakeLockerPlugin, 'fakeUnlockProvider'], 1); + $server + ->expects($this->at(2)) + ->method('on') + ->with('propFind', [$this->fakeLockerPlugin, 'propFind']); + $server + ->expects($this->at(3)) + ->method('on') + ->with('validateTokens', [$this->fakeLockerPlugin, 'validateTokens']); + + $this->fakeLockerPlugin->initialize($server); + } + + public function testGetHTTPMethods() { + $expected = [ + 'LOCK', + 'UNLOCK', + ]; + $this->assertSame($expected, $this->fakeLockerPlugin->getHTTPMethods('Test')); + } + + public function testGetFeatures() { + $expected = [ + 2, + ]; + $this->assertSame($expected, $this->fakeLockerPlugin->getFeatures()); + } + + public function testPropFind() { + $propFind = $this->getMockBuilder('\Sabre\DAV\PropFind') + ->disableOriginalConstructor() + ->getMock(); + $node = $this->getMock('\Sabre\DAV\INode'); + + $propFind->expects($this->at(0)) + ->method('handle') + ->with('{DAV:}supportedlock'); + $propFind->expects($this->at(1)) + ->method('handle') + ->with('{DAV:}lockdiscovery'); + + $this->fakeLockerPlugin->propFind($propFind, $node); + } + + public function tokenDataProvider() { + return [ + [ + [ + [ + 'tokens' => [ + [ + 'token' => 'aToken', + 'validToken' => false, + ], + [], + [ + 'token' => 'opaquelocktoken:asdf', + 'validToken' => false, + ] + ], + ] + ], + [ + [ + 'tokens' => [ + [ + 'token' => 'aToken', + 'validToken' => false, + ], + [], + [ + 'token' => 'opaquelocktoken:asdf', + 'validToken' => true, + ] + ], + ] + ], + ] + ]; + } + + /** + * @dataProvider tokenDataProvider + * @param array $input + * @param array $expected + */ + public function testValidateTokens(array $input, array $expected) { + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $this->fakeLockerPlugin->validateTokens($request, $input); + $this->assertSame($expected, $input); + } + + public function testFakeLockProvider() { + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $response = new Response(); + $server = $this->getMock('\Sabre\DAV\Server'); + $this->fakeLockerPlugin->initialize($server); + + $request->expects($this->exactly(2)) + ->method('getPath') + ->will($this->returnValue('MyPath')); + + $this->assertSame(false, $this->fakeLockerPlugin->fakeLockProvider($request, $response)); + + $expectedXml = '<?xml version="1.0" encoding="utf-8"?><d:prop xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"><d:lockdiscovery><d:activelock><d:lockscope><d:exclusive/></d:lockscope><d:locktype><d:write/></d:locktype><d:lockroot><d:href>MyPath</d:href></d:lockroot><d:depth>infinity</d:depth><d:timeout>Second-1800</d:timeout><d:locktoken><d:href>opaquelocktoken:fe4f7f2437b151fbcb4e9f5c8118c6b1</d:href></d:locktoken><d:owner/></d:activelock></d:lockdiscovery></d:prop>'; + + $this->assertXmlStringEqualsXmlString($expectedXml, $response->getBody()); + } + + public function testFakeUnlockProvider() { + $request = $this->getMock('\Sabre\HTTP\RequestInterface'); + $response = $this->getMock('\Sabre\HTTP\ResponseInterface'); + + $response->expects($this->once()) + ->method('setStatus') + ->with('204'); + $response->expects($this->once()) + ->method('setHeader') + ->with('Content-Length', '0'); + + $this->assertSame(false, $this->fakeLockerPlugin->fakeUnlockProvider($request, $response)); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php new file mode 100644 index 00000000000..0f404a6a16c --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -0,0 +1,987 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OC\Files\Storage\Local; +use OCP\Files\ForbiddenException; +use Test\HookHelper; +use OC\Files\Filesystem; +use OCP\Lock\ILockingProvider; + +/** + * Class File + * + * @group DB + * + * @package OCA\DAV\Tests\unit\Connector\Sabre + */ +class FileTest 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 getMockStorage() { + $storage = $this->getMock('\OCP\Files\Storage'); + $storage->expects($this->any()) + ->method('getId') + ->will($this->returnValue('home::someuser')); + return $storage; + } + + /** + * @param string $string + */ + 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\ForbiddenException('', true), + 'OCA\DAV\Connector\Sabre\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', $this->getMockStorage(), 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', $this->getMockStorage(), null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // put first chunk + $file->acquireLock(ILockingProvider::LOCK_SHARED); + $this->assertNull($file->put('test data one')); + $file->releaseLock(ILockingProvider::LOCK_SHARED); + + $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-1', $this->getMockStorage(), 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 null|string 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, '/'), + $this->getMockStorage(), + 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' + ); + } + + /** + * Test that putting a file with chunks triggers create hooks + */ + public function testPutChunkedFileTriggersHooks() { + HookHelper::setUpHooks(); + + $_SERVER['HTTP_OC_CHUNKED'] = true; + $this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0')); + $this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1')); + + $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 chunked file triggers update hooks + */ + public function testPutOverwriteChunkedFileTriggersHooks() { + $view = \OC\Files\Filesystem::getView(); + $view->file_put_contents('/foo.txt', 'some content that will be replaced'); + + HookHelper::setUpHooks(); + + $_SERVER['HTTP_OC_CHUNKED'] = true; + $this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0')); + $this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1')); + + $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' + ); + } + + 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', $this->getMockStorage(), null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + // beforeMethod locks + $file->acquireLock(ILockingProvider::LOCK_SHARED); + + $file->put($this->getStream('test data')); + + // afterMethod unlocks + $file->releaseLock(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', $this->getMockStorage(), 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', $this->getMockStorage(), null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file->acquireLock(ILockingProvider::LOCK_SHARED); + $this->assertNull($file->put('test data one')); + $file->releaseLock(ILockingProvider::LOCK_SHARED); + + $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-1', $this->getMockStorage(), null, [ + 'permissions' => \OCP\Constants::PERMISSION_ALL + ], null); + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $thrown = false; + try { + $file->acquireLock(ILockingProvider::LOCK_SHARED); + $file->put($this->getStream('test data')); + $file->releaseLock(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 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('/*', $this->getMockStorage(), 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('/*', $this->getMockStorage(), 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', $this->getMockStorage(), 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', $this->getMockStorage(), 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', $this->getMockStorage(), 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', $this->getMockStorage(), null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + // action + $file->delete(); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden + */ + public function testDeleteThrowsWhenDeletionThrows() { + // setup + $view = $this->getMock('\OC\Files\View', + array()); + + // but fails + $view->expects($this->once()) + ->method('unlink') + ->willThrowException(new ForbiddenException('', true)); + + $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), 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, + $this->getMockStorage(), + 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); + if($storage instanceof Local) { + $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', $this->getMockStorage(), null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + $file->get(); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\Forbidden + */ + public function testGetFopenThrows() { + $view = $this->getMock('\OC\Files\View', ['fopen'], array()); + $view->expects($this->atLeastOnce()) + ->method('fopen') + ->willThrowException(new ForbiddenException('', true)); + + $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, array( + 'permissions' => \OCP\Constants::PERMISSION_ALL + ), null); + + $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + + $file->get(); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php new file mode 100644 index 00000000000..5a944e74fca --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php @@ -0,0 +1,469 @@ +<?php +/** + * @author Roeland Jago Douma <rullzer@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OCP\Files\StorageNotAvailableException; +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Test\TestCase; + +/** + * 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 FilesPluginTest extends TestCase { + const GETETAG_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::GETETAG_PROPERTYNAME; + const FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::FILEID_PROPERTYNAME; + const INTERNAL_FILEID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::INTERNAL_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; + const OWNER_ID_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::OWNER_ID_PROPERTYNAME; + const OWNER_DISPLAY_NAME_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME; + const DATA_FINGERPRINT_PROPERTYNAME = \OCA\DAV\Connector\Sabre\FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME; + + /** + * @var \Sabre\DAV\Server | \PHPUnit_Framework_MockObject_MockObject + */ + private $server; + + /** + * @var \Sabre\DAV\Tree | \PHPUnit_Framework_MockObject_MockObject + */ + private $tree; + + /** + * @var \OCA\DAV\Connector\Sabre\FilesPlugin + */ + private $plugin; + + /** + * @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject + */ + private $view; + + /** + * @var \OCP\IConfig | \PHPUnit_Framework_MockObject_MockObject + */ + private $config; + + 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->config = $this->getMock('\OCP\IConfig'); + $this->config->method('getSystemValue') + ->with($this->equalTo('data-fingerprint'), $this->equalTo('')) + ->willReturn('my_fingerprint'); + + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $this->config + ); + $this->plugin->initialize($this->server); + } + + /** + * @param string $class + * @return \PHPUnit_Framework_MockObject_MockObject + */ + 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('00000123instanceid')); + $node->expects($this->any()) + ->method('getInternalFileId') + ->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() { + /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + + $propFind = new PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::INTERNAL_FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + self::OWNER_ID_PROPERTYNAME, + self::OWNER_DISPLAY_NAME_PROPERTYNAME, + self::DATA_FINGERPRINT_PROPERTYNAME, + ), + 0 + ); + + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user + ->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('foo')); + $user + ->expects($this->once()) + ->method('getDisplayName') + ->will($this->returnValue('M. Foo')); + + $node->expects($this->once()) + ->method('getDirectDownload') + ->will($this->returnValue(array('url' => 'http://example.com/'))); + $node->expects($this->exactly(2)) + ->method('getOwner') + ->will($this->returnValue($user)); + $node->expects($this->never()) + ->method('getSize'); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); + $this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME)); + $this->assertEquals('123', $propFind->get(self::INTERNAL_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('foo', $propFind->get(self::OWNER_ID_PROPERTYNAME)); + $this->assertEquals('M. Foo', $propFind->get(self::OWNER_DISPLAY_NAME_PROPERTYNAME)); + $this->assertEquals([self::SIZE_PROPERTYNAME, self::DATA_FINGERPRINT_PROPERTYNAME], $propFind->get404Properties()); + } + + public function testGetPropertiesForFileHome() { + /** @var \OCA\DAV\Files\FilesHome | \PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->getMockBuilder('\OCA\DAV\Files\FilesHome') + ->disableOriginalConstructor() + ->getMock(); + + $propFind = new PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::INTERNAL_FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + self::OWNER_ID_PROPERTYNAME, + self::OWNER_DISPLAY_NAME_PROPERTYNAME, + self::DATA_FINGERPRINT_PROPERTYNAME, + ), + 0 + ); + + $user = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $user->expects($this->never())->method('getUID'); + $user->expects($this->never())->method('getDisplayName'); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals(null, $propFind->get(self::GETETAG_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::FILEID_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::INTERNAL_FILEID_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::SIZE_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::OWNER_ID_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::OWNER_DISPLAY_NAME_PROPERTYNAME)); + $this->assertEquals(['{DAV:}getetag', + '{http://owncloud.org/ns}id', + '{http://owncloud.org/ns}fileid', + '{http://owncloud.org/ns}size', + '{http://owncloud.org/ns}permissions', + '{http://owncloud.org/ns}downloadURL', + '{http://owncloud.org/ns}owner-id', + '{http://owncloud.org/ns}owner-display-name' + ], $propFind->get404Properties()); + $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); + } + + public function testGetPropertiesStorageNotAvailable() { + /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + + $propFind = new PropFind( + '/dummyPath', + array( + self::DOWNLOADURL_PROPERTYNAME, + ), + 0 + ); + + $node->expects($this->once()) + ->method('getDirectDownload') + ->will($this->throwException(new StorageNotAvailableException())); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + } + + public function testGetPublicPermissions() { + $this->plugin = new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $this->config, + true); + $this->plugin->initialize($this->server); + + $propFind = new PropFind( + '/dummyPath', + [ + self::PERMISSIONS_PROPERTYNAME, + ], + 0 + ); + + /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit_Framework_MockObject_MockObject $node */ + $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() { + /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); + + $propFind = new PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + self::DATA_FINGERPRINT_PROPERTYNAME, + ), + 0 + ); + + $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('00000123instanceid', $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([self::DOWNLOADURL_PROPERTYNAME, self::DATA_FINGERPRINT_PROPERTYNAME], $propFind->get404Properties()); + } + + public function testGetPropertiesForRootDirectory() { + /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit_Framework_MockObject_MockObject $node */ + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $node->method('getPath')->willReturn('/'); + + $propFind = new PropFind( + '/', + [ + self::DATA_FINGERPRINT_PROPERTYNAME, + ], + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); + } + + 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 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]); + } + + public function testUpdatePropsForbidden() { + $propPatch = new PropPatch(array( + self::OWNER_ID_PROPERTYNAME => 'user2', + self::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two', + self::FILEID_PROPERTYNAME => 12345, + self::PERMISSIONS_PROPERTYNAME => 'C', + self::SIZE_PROPERTYNAME => 123, + self::DOWNLOADURL_PROPERTYNAME => 'http://example.com/', + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(403, $result[self::OWNER_ID_PROPERTYNAME]); + $this->assertEquals(403, $result[self::OWNER_DISPLAY_NAME_PROPERTYNAME]); + $this->assertEquals(403, $result[self::FILEID_PROPERTYNAME]); + $this->assertEquals(403, $result[self::PERMISSIONS_PROPERTYNAME]); + $this->assertEquals(403, $result[self::SIZE_PROPERTYNAME]); + $this->assertEquals(403, $result[self::DOWNLOADURL_PROPERTYNAME]); + } + + /** + * Testcase from https://github.com/owncloud/core/issues/5251 + * + * |-FolderA + * |-text.txt + * |-test.txt + * + * FolderA is an incoming 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'); + } + + /** + * @expectedException \Sabre\DAV\Exception\NotFound + * @expectedExceptionMessage FolderA/test.txt does not exist + */ + public function testMoveSrcNotExist() { + $this->view->expects($this->once()) + ->method('getFileInfo') + ->with('FolderA/test.txt') + ->willReturn(false); + + $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php new file mode 100644 index 00000000000..d5bbbbcc99d --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php @@ -0,0 +1,611 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\FilesReportPlugin as FilesReportPluginImplementation; +use Sabre\DAV\Exception\NotFound; +use OCP\SystemTag\ISystemTagObjectMapper; +use OC\Files\View; +use OCP\Files\Folder; +use OCP\IGroupManager; +use OCP\SystemTag\ISystemTagManager; + +class FilesReportPluginTest extends \Test\TestCase { + /** @var \Sabre\DAV\Server|\PHPUnit_Framework_MockObject_MockObject */ + private $server; + + /** @var \Sabre\DAV\Tree|\PHPUnit_Framework_MockObject_MockObject */ + private $tree; + + /** @var ISystemTagObjectMapper|\PHPUnit_Framework_MockObject_MockObject */ + private $tagMapper; + + /** @var ISystemTagManager|\PHPUnit_Framework_MockObject_MockObject */ + private $tagManager; + + /** @var \OCP\IUserSession */ + private $userSession; + + /** @var FilesReportPluginImplementation */ + private $plugin; + + /** @var View|\PHPUnit_Framework_MockObject_MockObject **/ + private $view; + + /** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject **/ + private $groupManager; + + /** @var Folder|\PHPUnit_Framework_MockObject_MockObject **/ + private $userFolder; + + public function setUp() { + parent::setUp(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + + $this->view = $this->getMockBuilder('\OC\Files\View') + ->disableOriginalConstructor() + ->getMock(); + + $this->server = $this->getMockBuilder('\Sabre\DAV\Server') + ->setConstructorArgs([$this->tree]) + ->setMethods(['getRequestUri']) + ->getMock(); + + $this->groupManager = $this->getMockBuilder('\OCP\IGroupManager') + ->disableOriginalConstructor() + ->getMock(); + + $this->userFolder = $this->getMockBuilder('\OCP\Files\Folder') + ->disableOriginalConstructor() + ->getMock(); + + $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager'); + $this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper'); + $this->userSession = $this->getMock('\OCP\IUserSession'); + + $user = $this->getMock('\OCP\IUser'); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('testuser')); + $this->userSession->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($user)); + + $this->plugin = new FilesReportPluginImplementation( + $this->tree, + $this->view, + $this->tagManager, + $this->tagMapper, + $this->userSession, + $this->groupManager, + $this->userFolder + ); + } + + /** + * @expectedException \Sabre\DAV\Exception\ReportNotSupported + */ + public function testOnReportInvalidNode() { + $path = 'totally/unrelated/13'; + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($this->getMock('\Sabre\DAV\INode'))); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->plugin->initialize($this->server); + + $this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, [], '/' . $path); + } + + /** + * @expectedException \Sabre\DAV\Exception\ReportNotSupported + */ + public function testOnReportInvalidReportName() { + $path = 'test'; + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($this->getMock('\Sabre\DAV\INode'))); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->plugin->initialize($this->server); + + $this->plugin->onReport('{whoever}whatever', [], '/' . $path); + } + + public function testOnReport() { + $path = 'test'; + + $parameters = [ + [ + 'name' => '{DAV:}prop', + 'value' => [ + ['name' => '{DAV:}getcontentlength', 'value' => ''], + ['name' => '{http://owncloud.org/ns}size', 'value' => ''], + ], + ], + [ + 'name' => '{http://owncloud.org/ns}filter-rules', + 'value' => [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + ], + ], + ]; + + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(true)); + + $this->tagMapper->expects($this->at(0)) + ->method('getObjectIdsForTags') + ->with('123', 'files') + ->will($this->returnValue(['111', '222'])); + $this->tagMapper->expects($this->at(1)) + ->method('getObjectIdsForTags') + ->with('456', 'files') + ->will($this->returnValue(['111', '222', '333'])); + + $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $response->expects($this->once()) + ->method('setHeader') + ->with('Content-Type', 'application/xml; charset=utf-8'); + + $response->expects($this->once()) + ->method('setStatus') + ->with(207); + + $response->expects($this->once()) + ->method('setBody'); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/' . $path) + ->will($this->returnValue($reportTargetNode)); + + $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder') + ->disableOriginalConstructor() + ->getMock(); + $filesNode2 = $this->getMockBuilder('\OCP\Files\File') + ->disableOriginalConstructor() + ->getMock(); + + $this->userFolder->expects($this->at(0)) + ->method('getById') + ->with('111') + ->will($this->returnValue([$filesNode1])); + $this->userFolder->expects($this->at(1)) + ->method('getById') + ->with('222') + ->will($this->returnValue([$filesNode2])); + + $this->server->expects($this->any()) + ->method('getRequestUri') + ->will($this->returnValue($path)); + $this->server->httpResponse = $response; + $this->plugin->initialize($this->server); + + $this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path); + } + + public function testFindNodesByFileIdsRoot() { + $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder') + ->disableOriginalConstructor() + ->getMock(); + $filesNode1->expects($this->once()) + ->method('getName') + ->will($this->returnValue('first node')); + + $filesNode2 = $this->getMockBuilder('\OCP\Files\File') + ->disableOriginalConstructor() + ->getMock(); + $filesNode2->expects($this->once()) + ->method('getName') + ->will($this->returnValue('second node')); + + $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $reportTargetNode->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/')); + + $this->userFolder->expects($this->at(0)) + ->method('getById') + ->with('111') + ->will($this->returnValue([$filesNode1])); + $this->userFolder->expects($this->at(1)) + ->method('getById') + ->with('222') + ->will($this->returnValue([$filesNode2])); + + /** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit_Framework_MockObject_MockObject $reportTargetNode */ + $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']); + + $this->assertCount(2, $result); + $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]); + $this->assertEquals('first node', $result[0]->getName()); + $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]); + $this->assertEquals('second node', $result[1]->getName()); + } + + public function testFindNodesByFileIdsSubDir() { + $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder') + ->disableOriginalConstructor() + ->getMock(); + $filesNode1->expects($this->once()) + ->method('getName') + ->will($this->returnValue('first node')); + + $filesNode2 = $this->getMockBuilder('\OCP\Files\File') + ->disableOriginalConstructor() + ->getMock(); + $filesNode2->expects($this->once()) + ->method('getName') + ->will($this->returnValue('second node')); + + $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $reportTargetNode->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/sub1/sub2')); + + + $subNode = $this->getMockBuilder('\OCP\Files\Folder') + ->disableOriginalConstructor() + ->getMock(); + + $this->userFolder->expects($this->at(0)) + ->method('get') + ->with('/sub1/sub2') + ->will($this->returnValue($subNode)); + + $subNode->expects($this->at(0)) + ->method('getById') + ->with('111') + ->will($this->returnValue([$filesNode1])); + $subNode->expects($this->at(1)) + ->method('getById') + ->with('222') + ->will($this->returnValue([$filesNode2])); + + /** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit_Framework_MockObject_MockObject $reportTargetNode */ + $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']); + + $this->assertCount(2, $result); + $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]); + $this->assertEquals('first node', $result[0]->getName()); + $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]); + $this->assertEquals('second node', $result[1]->getName()); + } + + public function testPrepareResponses() { + $requestedProps = ['{DAV:}getcontentlength', '{http://owncloud.org/ns}fileid', '{DAV:}resourcetype']; + + $node1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $node2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + + $node1->expects($this->once()) + ->method('getInternalFileId') + ->will($this->returnValue('111')); + $node2->expects($this->once()) + ->method('getInternalFileId') + ->will($this->returnValue('222')); + $node2->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(1024)); + + $config = $this->getMock('\OCP\IConfig'); + + $this->server->addPlugin( + new \OCA\DAV\Connector\Sabre\FilesPlugin( + $this->tree, + $this->view, + $config + ) + ); + $this->plugin->initialize($this->server); + $responses = $this->plugin->prepareResponses($requestedProps, [$node1, $node2]); + + $this->assertCount(2, $responses); + + $this->assertEquals(200, $responses[0]->getHttpStatus()); + $this->assertEquals(200, $responses[1]->getHttpStatus()); + + $props1 = $responses[0]->getResponseProperties(); + $this->assertEquals('111', $props1[200]['{http://owncloud.org/ns}fileid']); + $this->assertNull($props1[404]['{DAV:}getcontentlength']); + $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props1[200]['{DAV:}resourcetype']); + $resourceType1 = $props1[200]['{DAV:}resourcetype']->getValue(); + $this->assertEquals('{DAV:}collection', $resourceType1[0]); + + $props2 = $responses[1]->getResponseProperties(); + $this->assertEquals('1024', $props2[200]['{DAV:}getcontentlength']); + $this->assertEquals('222', $props2[200]['{http://owncloud.org/ns}fileid']); + $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props2[200]['{DAV:}resourcetype']); + $this->assertCount(0, $props2[200]['{DAV:}resourcetype']->getValue()); + } + + public function testProcessFilterRulesSingle() { + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(true)); + + $this->tagMapper->expects($this->exactly(1)) + ->method('getObjectIdsForTags') + ->withConsecutive( + ['123', 'files'] + ) + ->willReturnMap([ + ['123', 'files', 0, '', ['111', '222']], + ]); + + $rules = [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ]; + + $this->assertEquals(['111', '222'], $this->invokePrivate($this->plugin, 'processFilterRules', [$rules])); + } + + public function testProcessFilterRulesAndCondition() { + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(true)); + + $this->tagMapper->expects($this->exactly(2)) + ->method('getObjectIdsForTags') + ->withConsecutive( + ['123', 'files'], + ['456', 'files'] + ) + ->willReturnMap([ + ['123', 'files', 0, '', ['111', '222']], + ['456', 'files', 0, '', ['222', '333']], + ]); + + $rules = [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + ]; + + $this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + } + + public function testProcessFilterRulesAndConditionWithOneEmptyResult() { + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(true)); + + $this->tagMapper->expects($this->exactly(2)) + ->method('getObjectIdsForTags') + ->withConsecutive( + ['123', 'files'], + ['456', 'files'] + ) + ->willReturnMap([ + ['123', 'files', 0, '', ['111', '222']], + ['456', 'files', 0, '', []], + ]); + + $rules = [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + ]; + + $this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + } + + public function testProcessFilterRulesAndConditionWithFirstEmptyResult() { + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(true)); + + $this->tagMapper->expects($this->exactly(1)) + ->method('getObjectIdsForTags') + ->withConsecutive( + ['123', 'files'], + ['456', 'files'] + ) + ->willReturnMap([ + ['123', 'files', 0, '', []], + ['456', 'files', 0, '', ['111', '222']], + ]); + + $rules = [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + ]; + + $this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + } + + public function testProcessFilterRulesAndConditionWithEmptyMidResult() { + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(true)); + + $this->tagMapper->expects($this->exactly(2)) + ->method('getObjectIdsForTags') + ->withConsecutive( + ['123', 'files'], + ['456', 'files'], + ['789', 'files'] + ) + ->willReturnMap([ + ['123', 'files', 0, '', ['111', '222']], + ['456', 'files', 0, '', ['333']], + ['789', 'files', 0, '', ['111', '222']], + ]); + + $rules = [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '789'], + ]; + + $this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + } + + public function testProcessFilterRulesInvisibleTagAsAdmin() { + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(true)); + + $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag'); + $tag1->expects($this->any()) + ->method('getId') + ->will($this->returnValue('123')); + $tag1->expects($this->any()) + ->method('isUserVisible') + ->will($this->returnValue(true)); + + $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag'); + $tag2->expects($this->any()) + ->method('getId') + ->will($this->returnValue('123')); + $tag2->expects($this->any()) + ->method('isUserVisible') + ->will($this->returnValue(false)); + + // no need to fetch tags to check permissions + $this->tagManager->expects($this->never()) + ->method('getTagsByIds'); + + $this->tagMapper->expects($this->at(0)) + ->method('getObjectIdsForTags') + ->with('123') + ->will($this->returnValue(['111', '222'])); + $this->tagMapper->expects($this->at(1)) + ->method('getObjectIdsForTags') + ->with('456') + ->will($this->returnValue(['222', '333'])); + + $rules = [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + ]; + + $this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + } + + /** + * @expectedException \OCP\SystemTag\TagNotFoundException + */ + public function testProcessFilterRulesInvisibleTagAsUser() { + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(false)); + + $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag'); + $tag1->expects($this->any()) + ->method('getId') + ->will($this->returnValue('123')); + $tag1->expects($this->any()) + ->method('isUserVisible') + ->will($this->returnValue(true)); + + $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag'); + $tag2->expects($this->any()) + ->method('getId') + ->will($this->returnValue('123')); + $tag2->expects($this->any()) + ->method('isUserVisible') + ->will($this->returnValue(false)); // invisible + + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['123', '456']) + ->will($this->returnValue([$tag1, $tag2])); + + $rules = [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + ]; + + $this->invokePrivate($this->plugin, 'processFilterRules', [$rules]); + } + + public function testProcessFilterRulesVisibleTagAsUser() { + $this->groupManager->expects($this->any()) + ->method('isAdmin') + ->will($this->returnValue(false)); + + $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag'); + $tag1->expects($this->any()) + ->method('getId') + ->will($this->returnValue('123')); + $tag1->expects($this->any()) + ->method('isUserVisible') + ->will($this->returnValue(true)); + + $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag'); + $tag2->expects($this->any()) + ->method('getId') + ->will($this->returnValue('123')); + $tag2->expects($this->any()) + ->method('isUserVisible') + ->will($this->returnValue(true)); + + $this->tagManager->expects($this->once()) + ->method('getTagsByIds') + ->with(['123', '456']) + ->will($this->returnValue([$tag1, $tag2])); + + $this->tagMapper->expects($this->at(0)) + ->method('getObjectIdsForTags') + ->with('123') + ->will($this->returnValue(['111', '222'])); + $this->tagMapper->expects($this->at(1)) + ->method('getObjectIdsForTags') + ->with('456') + ->will($this->returnValue(['222', '333'])); + + $rules = [ + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], + ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], + ]; + + $this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + } +} 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..a95dcd69f44 --- /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) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\MaintenancePlugin; +use Test\TestCase; +use OCP\IConfig; + +/** + * Class MaintenancePluginTest + * + * @package OCA\DAV\Tests\unit\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/NodeTest.php b/apps/dav/tests/unit/Connector/Sabre/NodeTest.php new file mode 100644 index 00000000000..deb6ecf7f70 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/NodeTest.php @@ -0,0 +1,149 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +/** + * Class NodeTest + * + * @group DB + * @package OCA\DAV\Tests\unit\Connector\Sabre + */ +class NodeTest 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, 'RD'), + array(\OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_DELETE, 'file', false, false, 'RNVW'), + 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()); + } + + public function sharePermissionsProvider() { + return [ + [\OCP\Files\FileInfo::TYPE_FILE, null, 1, 1], + [\OCP\Files\FileInfo::TYPE_FILE, null, 3, 3], + [\OCP\Files\FileInfo::TYPE_FILE, null, 5, 1], + [\OCP\Files\FileInfo::TYPE_FILE, null, 7, 3], + [\OCP\Files\FileInfo::TYPE_FILE, null, 9, 1], + [\OCP\Files\FileInfo::TYPE_FILE, null, 11, 3], + [\OCP\Files\FileInfo::TYPE_FILE, null, 13, 1], + [\OCP\Files\FileInfo::TYPE_FILE, null, 15, 3], + [\OCP\Files\FileInfo::TYPE_FILE, null, 17, 17], + [\OCP\Files\FileInfo::TYPE_FILE, null, 19, 19], + [\OCP\Files\FileInfo::TYPE_FILE, null, 21, 17], + [\OCP\Files\FileInfo::TYPE_FILE, null, 23, 19], + [\OCP\Files\FileInfo::TYPE_FILE, null, 25, 17], + [\OCP\Files\FileInfo::TYPE_FILE, null, 27, 19], + [\OCP\Files\FileInfo::TYPE_FILE, null, 29, 17], + [\OCP\Files\FileInfo::TYPE_FILE, null, 30, 18], + [\OCP\Files\FileInfo::TYPE_FILE, null, 31, 19], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 1, 1], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 3, 3], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 5, 5], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 7, 7], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 9, 9], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 11, 11], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 13, 13], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 15, 15], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 17, 17], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 19, 19], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 21, 21], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 23, 23], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 25, 25], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 27, 27], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 29, 29], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 30, 30], + [\OCP\Files\FileInfo::TYPE_FOLDER, null, 31, 31], + [\OCP\Files\FileInfo::TYPE_FOLDER, 'shareToken', 7, 7], + ]; + } + + /** + * @dataProvider sharePermissionsProvider + */ + public function testSharePermissions($type, $user, $permissions, $expected) { + $storage = $this->getMock('\OCP\Files\Storage'); + $storage->method('getPermissions')->willReturn($permissions); + + $mountpoint = $this->getMock('\OCP\Files\Mount\IMountPoint'); + $mountpoint->method('getMountPoint')->willReturn('myPath'); + $shareManager = $this->getMockBuilder('OCP\Share\IManager')->disableOriginalConstructor()->getMock(); + $share = $this->getMockBuilder('OCP\Share\IShare')->disableOriginalConstructor()->getMock(); + + if ($user === null) { + $shareManager->expects($this->never())->method('getShareByToken'); + $share->expects($this->never())->method('getPermissions'); + } else { + $shareManager->expects($this->once())->method('getShareByToken')->with($user) + ->willReturn($share); + $share->expects($this->once())->method('getPermissions')->willReturn($permissions); + } + + $info = $this->getMockBuilder('\OC\Files\FileInfo') + ->disableOriginalConstructor() + ->setMethods(['getStorage', 'getType', 'getMountPoint']) + ->getMock(); + + $info->method('getStorage')->willReturn($storage); + $info->method('getType')->willReturn($type); + $info->method('getMountPoint')->willReturn($mountpoint); + + $view = $this->getMock('\OC\Files\View'); + + $node = new \OCA\DAV\Connector\Sabre\File($view, $info); + $this->invokePrivate($node, 'shareManager', [$shareManager]); + $this->assertEquals($expected, $node->getSharePermissions($user)); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php new file mode 100644 index 00000000000..4a5e43376c0 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php @@ -0,0 +1,355 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + + +use OC\Files\FileInfo; +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 ObjectTreeTest + * + * @group DB + * + * @package OCA\DAV\Tests\Unit\Connector\Sabre + */ +class ObjectTreeTest 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 \OCA\DAV\Connector\Sabre\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)); + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Could not copy directory nameOfSourceNode, target exists + */ + public function testFailingMove() { + $source = 'a/b'; + $destination = 'b/b'; + $updatables = array('a' => true, 'a/b' => true, 'b' => true, 'b/b' => false); + $deletables = array('a/b' => true); + + $view = new TestDoubleFileView($updatables, $deletables); + + $info = new FileInfo('', null, null, array(), null); + + $rootDir = new \OCA\DAV\Connector\Sabre\Directory($view, $info); + $objectTree = $this->getMock('\OCA\DAV\Connector\Sabre\ObjectTree', + array('nodeExists', 'getNodeForPath'), + array($rootDir, $view)); + + $sourceNode = $this->getMockBuilder('\Sabre\DAV\ICollection') + ->disableOriginalConstructor() + ->getMock(); + $sourceNode->expects($this->once()) + ->method('getName') + ->will($this->returnValue('nameOfSourceNode')); + + $objectTree->expects($this->once()) + ->method('nodeExists') + ->with($this->identicalTo($destination)) + ->will($this->returnValue(true)); + $objectTree->expects($this->once()) + ->method('getNodeForPath') + ->with($this->identicalTo($source)) + ->will($this->returnValue($sourceNode)); + + /** @var $objectTree \OCA\DAV\Connector\Sabre\ObjectTree */ + $mountManager = \OC\Files\Filesystem::getMountManager(); + $objectTree->init($rootDir, $view, $mountManager); + $objectTree->move($source, $destination); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php new file mode 100644 index 00000000000..63717713a7c --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php @@ -0,0 +1,273 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +use OCP\IGroupManager; +use \Sabre\DAV\PropPatch; +use OCP\IUserManager; +use Test\TestCase; + +class PrincipalTest extends TestCase { + /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */ + private $userManager; + /** @var \OCA\DAV\Connector\Sabre\Principal */ + private $connector; + /** @var IGroupManager | \PHPUnit_Framework_MockObject_MockObject */ + private $groupManager; + + public function setUp() { + $this->userManager = $this->getMockBuilder('\OCP\IUserManager') + ->disableOriginalConstructor()->getMock(); + $this->groupManager = $this->getMockBuilder('\OCP\IGroupManager') + ->disableOriginalConstructor()->getMock(); + + $this->connector = new \OCA\DAV\Connector\Sabre\Principal( + $this->userManager, + $this->groupManager); + 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(1)) + ->method('getUID') + ->will($this->returnValue('foo')); + $fooUser + ->expects($this->exactly(1)) + ->method('getDisplayName') + ->will($this->returnValue('Dr. Foo-Bar')); + $fooUser + ->expects($this->exactly(1)) + ->method('getEMailAddress') + ->will($this->returnValue('')); + $barUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $barUser + ->expects($this->exactly(1)) + ->method('getUID') + ->will($this->returnValue('bar')); + $barUser + ->expects($this->exactly(1)) + ->method('getEMailAddress') + ->will($this->returnValue('bar@owncloud.org')); + $this->userManager + ->expects($this->once()) + ->method('search') + ->with('') + ->will($this->returnValue([$fooUser, $barUser])); + + $expectedResponse = [ + 0 => [ + 'uri' => 'principals/users/foo', + '{DAV:}displayname' => 'Dr. Foo-Bar' + ], + 1 => [ + 'uri' => 'principals/users/bar', + '{DAV:}displayname' => 'bar', + '{http://sabredav.org/ns}email-address' => 'bar@owncloud.org' + ] + ]; + $response = $this->connector->getPrincipalsByPrefix('principals/users'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPrefixEmpty() { + $this->userManager + ->expects($this->once()) + ->method('search') + ->with('') + ->will($this->returnValue([])); + + $response = $this->connector->getPrincipalsByPrefix('principals/users'); + $this->assertSame([], $response); + } + + public function testGetPrincipalsByPathWithoutMail() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(1)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + + $expectedResponse = [ + 'uri' => 'principals/users/foo', + '{DAV:}displayname' => 'foo' + ]; + $response = $this->connector->getPrincipalByPath('principals/users/foo'); + $this->assertSame($expectedResponse, $response); + } + + public function testGetPrincipalsByPathWithMail() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(1)) + ->method('getEMailAddress') + ->will($this->returnValue('foo@owncloud.org')); + $fooUser + ->expects($this->exactly(1)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + + $expectedResponse = [ + 'uri' => 'principals/users/foo', + '{DAV:}displayname' => 'foo', + '{http://sabredav.org/ns}email-address' => 'foo@owncloud.org' + ]; + $response = $this->connector->getPrincipalByPath('principals/users/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/users/foo'); + $this->assertSame(null, $response); + } + + public function testGetGroupMemberSet() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(1)) + ->method('getUID') + ->will($this->returnValue('foo')); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($fooUser)); + + $response = $this->connector->getGroupMemberSet('principals/users/foo'); + $this->assertSame(['principals/users/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/users/foo'); + } + + public function testGetGroupMembership() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $group = $this->getMockBuilder('\OCP\IGroup') + ->disableOriginalConstructor()->getMock(); + $group->expects($this->once()) + ->method('getGID') + ->willReturn('group1'); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('foo') + ->willReturn($fooUser); + $this->groupManager + ->expects($this->once()) + ->method('getUserGroups') + ->willReturn([ + $group + ]); + + $expectedResponse = [ + 'principals/groups/group1' + ]; + $response = $this->connector->getGroupMembership('principals/users/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/users/foo'); + } + + /** + * @expectedException \Sabre\DAV\Exception + * @expectedExceptionMessage Setting members of the group is not supported yet + */ + public function testSetGroupMembership() { + $this->connector->setGroupMemberSet('principals/users/foo', ['foo']); + } + + public function testUpdatePrincipal() { + $this->assertSame(0, $this->connector->updatePrincipal('foo', new PropPatch(array()))); + } + + public function testSearchPrincipals() { + $this->assertSame([], $this->connector->searchPrincipals('principals/users', [])); + } + + public function testFindByUri() { + $fooUser = $this->getMockBuilder('\OC\User\User') + ->disableOriginalConstructor()->getMock(); + $fooUser + ->expects($this->exactly(1)) + ->method('getUID') + ->will($this->returnValue('foo')); + + $this->userManager->expects($this->once())->method('getByEmail')->willReturn([ + $fooUser + ]); + $ret = $this->connector->findByUri('mailto:foo@bar.net', 'principals/users'); + $this->assertSame('principals/users/foo', $ret); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php new file mode 100644 index 00000000000..45fb1743d15 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php @@ -0,0 +1,223 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; +/** + * 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 QuotaPluginTest extends \Test\TestCase { + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \OCA\DAV\Connector\Sabre\QuotaPlugin + */ + private $plugin; + + private function init($quota, $checkedPath = '') { + $view = $this->buildFileViewMock($quota, $checkedPath); + $this->server = new \Sabre\DAV\Server(); + $this->plugin = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\QuotaPlugin') + ->setConstructorArgs([$view]) + ->setMethods(['getFileChunking']) + ->getMock(); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider lengthProvider + */ + public function testLength($expected, $headers) { + $this->init(0); + $this->plugin->expects($this->never()) + ->method('getFileChunking'); + $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->plugin->expects($this->never()) + ->method('getFileChunking'); + + $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->plugin->expects($this->never()) + ->method('getFileChunking'); + + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $this->plugin->checkQuota(''); + } + + /** + * @dataProvider quotaOkayProvider + */ + public function testCheckQuotaOnPath($quota, $headers) { + $this->init($quota, 'sub/test.txt'); + $this->plugin->expects($this->never()) + ->method('getFileChunking'); + + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $result = $this->plugin->checkQuota('/sub/test.txt'); + $this->assertTrue($result); + } + + 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')), + ); + } + + public function quotaChunkedOkProvider() { + return array( + array(1024, 0, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(1024, 0, array('CONTENT-LENGTH' => '512')), + array(1024, 0, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + // with existing chunks (allowed size = total length - chunk total size) + array(400, 128, array('X-EXPECTED-ENTITY-LENGTH' => '512')), + array(400, 128, array('CONTENT-LENGTH' => '512')), + array(400, 128, array('OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500')), + // \OCP\Files\FileInfo::SPACE-UNKNOWN = -2 + array(-2, 0, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(-2, 0, array('CONTENT-LENGTH' => '512')), + array(-2, 0, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + array(-2, 128, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(-2, 128, array('CONTENT-LENGTH' => '512')), + array(-2, 128, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + ); + } + + /** + * @dataProvider quotaChunkedOkProvider + */ + public function testCheckQuotaChunkedOk($quota, $chunkTotalSize, $headers) { + $this->init($quota, 'sub/test.txt'); + + $mockChunking = $this->getMockBuilder('\OC_FileChunking') + ->disableOriginalConstructor() + ->getMock(); + $mockChunking->expects($this->once()) + ->method('getCurrentSize') + ->will($this->returnValue($chunkTotalSize)); + + $this->plugin->expects($this->once()) + ->method('getFileChunking') + ->will($this->returnValue($mockChunking)); + + $headers['OC-CHUNKED'] = 1; + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $result = $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1'); + $this->assertTrue($result); + } + + public function quotaChunkedFailProvider() { + return array( + array(400, 0, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(400, 0, array('CONTENT-LENGTH' => '512')), + array(400, 0, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + // with existing chunks (allowed size = total length - chunk total size) + array(380, 128, array('X-EXPECTED-ENTITY-LENGTH' => '512')), + array(380, 128, array('CONTENT-LENGTH' => '512')), + array(380, 128, array('OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500')), + ); + } + + /** + * @dataProvider quotaChunkedFailProvider + * @expectedException \Sabre\DAV\Exception\InsufficientStorage + */ + public function testCheckQuotaChunkedFail($quota, $chunkTotalSize, $headers) { + $this->init($quota, 'sub/test.txt'); + + $mockChunking = $this->getMockBuilder('\OC_FileChunking') + ->disableOriginalConstructor() + ->getMock(); + $mockChunking->expects($this->once()) + ->method('getCurrentSize') + ->will($this->returnValue($chunkTotalSize)); + + $this->plugin->expects($this->once()) + ->method('getFileChunking') + ->will($this->returnValue($mockChunking)); + + $headers['OC-CHUNKED'] = 1; + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); + $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1'); + } + + private function buildFileViewMock($quota, $checkedPath) { + // mock filesysten + $view = $this->getMock('\OC\Files\View', array('free_space'), array(), '', false); + $view->expects($this->any()) + ->method('free_space') + ->with($this->identicalTo($checkedPath)) + ->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..a8fcf552849 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php @@ -0,0 +1,117 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; + +use Sabre\DAV\Auth\Backend\BackendInterface; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +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; + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + $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 [true, "principals/$user"]; + } + return [false, "login failed"]; + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + // TODO: Implement challenge() method. + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/DownloadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/DownloadTest.php new file mode 100644 index 00000000000..1b296aaa243 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/DownloadTest.php @@ -0,0 +1,73 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; + +use OCP\AppFramework\Http; +use OCP\Lock\ILockingProvider; + +/** + * Class DownloadTest + * + * @group DB + * + * @package OCA\DAV\Tests\unit\Connector\Sabre\RequestTest + */ +class DownloadTest extends RequestTest { + public function testDownload() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $response = $this->request($view, $user, 'pass', 'GET', '/foo.txt'); + $this->assertEquals(Http::STATUS_OK, $response->getStatus()); + $this->assertEquals(stream_get_contents($response->getBody()), 'bar'); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testDownloadWriteLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE); + + $this->request($view, $user, 'pass', 'GET', '/foo.txt', 'asd'); + } + + public function testDownloadReadLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED); + + $response = $this->request($view, $user, 'pass', 'GET', '/foo.txt', 'asd'); + $this->assertEquals(Http::STATUS_OK, $response->getStatus()); + $this->assertEquals(stream_get_contents($response->getBody()), 'bar'); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php new file mode 100644 index 00000000000..1a593cb8d76 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php @@ -0,0 +1,46 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; + +use OC\Files\View; +use Test\Traits\EncryptionTrait; + +/** + * Class EncryptionUploadTest + * + * @group DB + * + * @package OCA\DAV\Tests\Unit\Connector\Sabre\RequestTest + */ +class EncryptionUploadTest extends UploadTest { + use EncryptionTrait; + + 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->setupForUser($name, $password); + $this->loginWithEncryption($name); + return new View('/' . $name . '/files'); + } +} 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..42b8ef927f0 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/ExceptionPlugin.php @@ -0,0 +1,46 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\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/PartFileInRootUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/PartFileInRootUploadTest.php new file mode 100644 index 00000000000..2986db4a7f1 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/PartFileInRootUploadTest.php @@ -0,0 +1,56 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; + +use OC\Files\View; +use Test\Traits\EncryptionTrait; + +/** + * Class PartFileInRootUploadTest + * + * @group DB + * + * @package OCA\DAV\Tests\unit\Connector\Sabre\RequestTest + */ +class PartFileInRootUploadTest extends UploadTest { + protected function setUp() { + $config = \OC::$server->getConfig(); + $mockConfig = $this->getMock('\OCP\IConfig'); + $mockConfig->expects($this->any()) + ->method('getSystemValue') + ->will($this->returnCallback(function ($key, $default) use ($config) { + if ($key === 'part_file_in_storage') { + return false; + } else { + return $config->getSystemValue($key, $default); + } + })); + $this->overwriteService('AllConfig', $mockConfig); + parent::setUp(); + } + + protected function tearDown() { + $this->restoreService('AllConfig'); + return parent::tearDown(); + } +} 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..1c951a2fbd0 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTest.php @@ -0,0 +1,146 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\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(), + $this->getMock('\OCP\IRequest') + ); + } + + 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 + * @throws \Exception + */ + 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..0f11ded89e0 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php @@ -0,0 +1,75 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\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..efb499b69c3 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php @@ -0,0 +1,211 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; + +use OC\Connector\Sabre\Exception\FileLocked; +use OCP\AppFramework\Http; +use OCP\Lock\ILockingProvider; + +/** + * Class UploadTest + * + * @group DB + * + * @package OCA\DAV\Tests\unit\Connector\Sabre\RequestTest + */ +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()); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testUploadOverWriteReadLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED); + + $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testUploadOverWriteWriteLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $view->file_put_contents('foo.txt', 'bar'); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE); + + $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); + } + + 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()); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testChunkedUploadOutOfOrderReadLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED); + + try { + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + $this->fail('Didn\'t expect locked error for the first chunk on read lock'); + return; + } + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + // last chunk should trigger the locked error since it tries to assemble + $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + } + + /** + * @expectedException \OCA\DAV\Connector\Sabre\Exception\FileLocked + */ + public function testChunkedUploadOutOfOrderWriteLocked() { + $user = $this->getUniqueID(); + $view = $this->setupUser($user, 'pass'); + + $this->assertFalse($view->file_exists('foo.txt')); + + $view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE); + + try { + $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); + } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + $this->fail('Didn\'t expect locked error for the first chunk on write lock'); // maybe forbid this in the future for write locks only? + return; + } + + $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); + $this->assertFalse($view->file_exists('foo.txt')); + + // last chunk should trigger the locked error since it tries to assemble + $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php new file mode 100644 index 00000000000..ff1f59e7851 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php @@ -0,0 +1,259 @@ +<?php +/** + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\Connector\Sabre; + +class SharesPluginTest extends \Test\TestCase { + + const SHARETYPES_PROPERTYNAME = \OCA\DAV\Connector\Sabre\SharesPlugin::SHARETYPES_PROPERTYNAME; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @var \OCP\Share\IManager + */ + private $shareManager; + + /** + * @var \OCP\Files\Folder + */ + private $userFolder; + + /** + * @var \OCA\DAV\Connector\Sabre\SharesPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + $this->shareManager = $this->getMock('\OCP\Share\IManager'); + $user = $this->getMock('\OCP\IUser'); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('user1')); + $userSession = $this->getMock('\OCP\IUserSession'); + $userSession->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + + $this->userFolder = $this->getMock('\OCP\Files\Folder'); + + $this->plugin = new \OCA\DAV\Connector\Sabre\SharesPlugin( + $this->tree, + $userSession, + $this->userFolder, + $this->shareManager + ); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider sharesGetPropertiesDataProvider + */ + public function testGetProperties($shareTypes) { + $sabreNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $sabreNode->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + $sabreNode->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/subdir')); + + // node API nodes + $node = $this->getMock('\OCP\Files\Folder'); + + $this->userFolder->expects($this->once()) + ->method('get') + ->with('/subdir') + ->will($this->returnValue($node)); + + $this->shareManager->expects($this->any()) + ->method('getSharesBy') + ->with( + $this->equalTo('user1'), + $this->anything(), + $this->anything(), + $this->equalTo(false), + $this->equalTo(1) + ) + ->will($this->returnCallback(function($userId, $requestedShareType, $node, $flag, $limit) use ($shareTypes){ + if (in_array($requestedShareType, $shareTypes)) { + return ['dummyshare']; + } + return []; + })); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + [self::SHARETYPES_PROPERTYNAME], + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $sabreNode + ); + + $result = $propFind->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($shareTypes, $result[200][self::SHARETYPES_PROPERTYNAME]->getShareTypes()); + } + + /** + * @dataProvider sharesGetPropertiesDataProvider + */ + public function testPreloadThenGetProperties($shareTypes) { + $sabreNode1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $sabreNode1->expects($this->any()) + ->method('getId') + ->will($this->returnValue(111)); + $sabreNode1->expects($this->never()) + ->method('getPath'); + $sabreNode2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $sabreNode2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(222)); + $sabreNode2->expects($this->never()) + ->method('getPath'); + + $sabreNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $sabreNode->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + // never, because we use getDirectoryListing from the Node API instead + $sabreNode->expects($this->never()) + ->method('getChildren'); + $sabreNode->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/subdir')); + + // node API nodes + $node = $this->getMock('\OCP\Files\Folder'); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + $node1 = $this->getMock('\OCP\Files\File'); + $node1->expects($this->any()) + ->method('getId') + ->will($this->returnValue(111)); + $node2 = $this->getMock('\OCP\Files\File'); + $node2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(222)); + $node->expects($this->once()) + ->method('getDirectoryListing') + ->will($this->returnValue([$node1, $node2])); + + $this->userFolder->expects($this->once()) + ->method('get') + ->with('/subdir') + ->will($this->returnValue($node)); + + $this->shareManager->expects($this->any()) + ->method('getSharesBy') + ->with( + $this->equalTo('user1'), + $this->anything(), + $this->anything(), + $this->equalTo(false), + $this->equalTo(1) + ) + ->will($this->returnCallback(function($userId, $requestedShareType, $node, $flag, $limit) use ($shareTypes){ + if ($node->getId() === 111 && in_array($requestedShareType, $shareTypes)) { + return ['dummyshare']; + } + + return []; + })); + + // simulate sabre recursive PROPFIND traversal + $propFindRoot = new \Sabre\DAV\PropFind( + '/subdir', + [self::SHARETYPES_PROPERTYNAME], + 1 + ); + $propFind1 = new \Sabre\DAV\PropFind( + '/subdir/test.txt', + [self::SHARETYPES_PROPERTYNAME], + 0 + ); + $propFind2 = new \Sabre\DAV\PropFind( + '/subdir/test2.txt', + [self::SHARETYPES_PROPERTYNAME], + 0 + ); + + $this->plugin->handleGetProperties( + $propFindRoot, + $sabreNode + ); + $this->plugin->handleGetProperties( + $propFind1, + $sabreNode1 + ); + $this->plugin->handleGetProperties( + $propFind2, + $sabreNode2 + ); + + $result = $propFind1->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($shareTypes, $result[200][self::SHARETYPES_PROPERTYNAME]->getShareTypes()); + } + + function sharesGetPropertiesDataProvider() { + return [ + [[]], + [[\OCP\Share::SHARE_TYPE_USER]], + [[\OCP\Share::SHARE_TYPE_GROUP]], + [[\OCP\Share::SHARE_TYPE_LINK]], + [[\OCP\Share::SHARE_TYPE_REMOTE]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK]], + [[\OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_REMOTE]], + ]; + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php new file mode 100644 index 00000000000..e48d9b3aa5f --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php @@ -0,0 +1,417 @@ +<?php +/** + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, 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 OCA\DAV\Tests\unit\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 TagsPluginTest 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\Tree + */ + 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])); + } + +} |