diff options
Diffstat (limited to 'apps/dav/tests')
5 files changed, 470 insertions, 14 deletions
diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php index fd75508398f..8d72fb13b78 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FileTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -73,6 +73,7 @@ class FileTest extends TestCase { protected function setUp(): void { parent::setUp(); + unset($_SERVER['HTTP_OC_CHUNKED']); unset($_SERVER['CONTENT_LENGTH']); unset($_SERVER['REQUEST_METHOD']); @@ -90,6 +91,7 @@ class FileTest extends TestCase { protected function tearDown(): void { $userManager = \OC::$server->getUserManager(); $userManager->get($this->user)->delete(); + unset($_SERVER['HTTP_OC_CHUNKED']); parent::tearDown(); } @@ -233,6 +235,81 @@ class FileTest extends TestCase { } /** + * Test putting a file using chunking + * + * @dataProvider fopenFailuresProvider + */ + public function testChunkedPutFails($thrownException, $expectedException, $checkPreviousClass = false): void { + // setup + $storage = $this->getMockBuilder(Local::class) + ->setMethods(['fopen']) + ->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]]) + ->getMock(); + \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); + $view = $this->getMockBuilder(View::class) + ->setMethods(['getRelativePath', 'resolvePath']) + ->getMock(); + $view->expects($this->atLeastOnce()) + ->method('resolvePath') + ->willReturnCallback( + 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') + ->willReturn(false); + } + + $view->expects($this->any()) + ->method('getRelativePath') + ->willReturnArgument(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, + 'type' => FileInfo::TYPE_FOLDER, + ], 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, + 'type' => FileInfo::TYPE_FOLDER, + ], 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 @@ -361,6 +438,41 @@ class FileTest extends TestCase { } /** + * Test putting a file with string Mtime using chunking + * @dataProvider legalMtimeProvider + */ + public function testChunkedPutLegalMtime($requestMtime, $resultMtime): void { + $request = new Request([ + 'server' => [ + 'HTTP_X_OC_MTIME' => $requestMtime, + ] + ], $this->requestId, $this->config, null); + + $_SERVER['HTTP_OC_CHUNKED'] = true; + $file = 'foo.txt'; + + if ($resultMtime === null) { + $this->expectException(\Sabre\DAV\Exception::class); + } + + $this->doPut($file.'-chunking-12345-2-0', null, $request); + $this->doPut($file.'-chunking-12345-2-1', null, $request); + + if ($resultMtime !== null) { + $this->assertEquals($resultMtime, $this->getFileInfos($file)['mtime']); + } + } + + /** + * Test putting a file using chunking + */ + public function testChunkedPut(): void { + $_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(): void { @@ -462,6 +574,75 @@ class FileTest extends TestCase { ); } + /** + * Test that putting a file with chunks triggers create hooks + */ + public function testPutChunkedFileTriggersHooks(): void { + 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(): void { + $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): void { self::$hookCalls[] = [ 'signal' => Filesystem::signal_post_create, @@ -575,6 +756,46 @@ class FileTest extends TestCase { } /** + * Test exception during final rename in chunk upload mode + */ + public function testChunkedPutFailsFinalRename(): void { + $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, + 'type' => FileInfo::TYPE_FOLDER, + ], 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, + 'type' => FileInfo::TYPE_FOLDER, + ], 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(): void { diff --git a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php index 86de3d81334..d219888ef15 100644 --- a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php @@ -148,8 +148,13 @@ class ObjectTreeTest extends \Test\TestCase { $inputFileName, $fileInfoQueryPath, $outputFileName, - $type + $type, + $enableChunkingHeader ): void { + if ($enableChunkingHeader) { + $_SERVER['HTTP_OC_CHUNKED'] = true; + } + $rootNode = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() ->getMock(); @@ -186,6 +191,8 @@ class ObjectTreeTest extends \Test\TestCase { } else { $this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\Directory); } + + unset($_SERVER['HTTP_OC_CHUNKED']); } public function nodeForPathProvider() { @@ -195,67 +202,94 @@ class ObjectTreeTest extends \Test\TestCase { 'regularfile.txt', 'regularfile.txt', 'regularfile.txt', - 'file' + 'file', + false ], // regular directory [ 'regulardir', 'regulardir', 'regulardir', - 'dir' + 'dir', + false ], // regular file with chunking [ 'regularfile.txt', 'regularfile.txt', 'regularfile.txt', - 'file' + 'file', + true ], // regular directory with chunking [ 'regulardir', 'regulardir', 'regulardir', - 'dir' + 'dir', + true + ], + // file with chunky file name + [ + 'regularfile.txt-chunking-123566789-10-1', + 'regularfile.txt', + 'regularfile.txt', + 'file', + true ], // regular file in subdir [ 'subdir/regularfile.txt', 'subdir/regularfile.txt', 'regularfile.txt', - 'file' + 'file', + false ], // regular directory in subdir [ 'subdir/regulardir', 'subdir/regulardir', 'regulardir', - 'dir' + 'dir', + false + ], + // file with chunky file name in subdir + [ + 'subdir/regularfile.txt-chunking-123566789-10-1', + 'subdir/regularfile.txt', + 'regularfile.txt', + 'file', + true ], ]; } public function testGetNodeForPathInvalidPath(): void { + $this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class); + $path = '/foo\bar'; + + $storage = new Temporary([]); - $rootNode = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); - $mountManager = $this->createMock(IMountManager::class); + $view = $this->getMockBuilder(View::class) ->setMethods(['resolvePath']) ->getMock(); - - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class); $view->expects($this->once()) ->method('resolvePath') ->willReturnCallback(function ($path) use ($storage) { return [$storage, ltrim($path, '/')]; }); + $rootNode = $this->getMockBuilder(Directory::class) + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->createMock(IMountManager::class); + $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); $tree->init($rootNode, $view, $mountManager); + $tree->getNodeForPath($path); } diff --git a/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php index 75f799ada71..4a9ca159bbd 100644 --- a/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php @@ -52,7 +52,10 @@ class QuotaPluginTest extends TestCase { private function init($quota, $checkedPath = ''): void { $view = $this->buildFileViewMock($quota, $checkedPath); $this->server = new \Sabre\DAV\Server(); - $this->plugin = new QuotaPlugin($view); + $this->plugin = $this->getMockBuilder(QuotaPlugin::class) + ->setConstructorArgs([$view]) + ->setMethods(['getFileChunking']) + ->getMock(); $this->plugin->initialize($this->server); } @@ -61,6 +64,8 @@ class QuotaPluginTest extends TestCase { */ public function testLength($expected, $headers): void { $this->init(0); + $this->plugin->expects($this->never()) + ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $length = $this->plugin->getLength(); $this->assertEquals($expected, $length); @@ -71,6 +76,8 @@ class QuotaPluginTest extends TestCase { */ public function testCheckQuota($quota, $headers): void { $this->init($quota); + $this->plugin->expects($this->never()) + ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $result = $this->plugin->checkQuota(''); @@ -84,6 +91,8 @@ class QuotaPluginTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\InsufficientStorage::class); $this->init($quota); + $this->plugin->expects($this->never()) + ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $this->plugin->checkQuota(''); @@ -94,6 +103,8 @@ class QuotaPluginTest extends TestCase { */ public function testCheckQuotaOnPath($quota, $headers): void { $this->init($quota, 'sub/test.txt'); + $this->plugin->expects($this->never()) + ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $result = $this->plugin->checkQuota('/sub/test.txt'); @@ -143,6 +154,84 @@ class QuotaPluginTest extends TestCase { ]; } + public function quotaChunkedOkProvider() { + return [ + [1024, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], + [1024, 0, ['CONTENT-LENGTH' => '512']], + [1024, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']], + // with existing chunks (allowed size = total length - chunk total size) + [400, 128, ['X-EXPECTED-ENTITY-LENGTH' => '512']], + [400, 128, ['CONTENT-LENGTH' => '512']], + [400, 128, ['OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500']], + // \OCP\Files\FileInfo::SPACE-UNKNOWN = -2 + [-2, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], + [-2, 0, ['CONTENT-LENGTH' => '512']], + [-2, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']], + [-2, 128, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], + [-2, 128, ['CONTENT-LENGTH' => '512']], + [-2, 128, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']], + ]; + } + + /** + * @dataProvider quotaChunkedOkProvider + */ + public function testCheckQuotaChunkedOk($quota, $chunkTotalSize, $headers): void { + $this->init($quota, 'sub/test.txt'); + + $mockChunking = $this->getMockBuilder(\OC_FileChunking::class) + ->disableOriginalConstructor() + ->getMock(); + $mockChunking->expects($this->once()) + ->method('getCurrentSize') + ->willReturn($chunkTotalSize); + + $this->plugin->expects($this->once()) + ->method('getFileChunking') + ->willReturn($mockChunking); + + $headers['OC-CHUNKED'] = 1; + $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); + $result = $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1'); + $this->assertTrue($result); + } + + public function quotaChunkedFailProvider() { + return [ + [400, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], + [400, 0, ['CONTENT-LENGTH' => '512']], + [400, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']], + // with existing chunks (allowed size = total length - chunk total size) + [380, 128, ['X-EXPECTED-ENTITY-LENGTH' => '512']], + [380, 128, ['CONTENT-LENGTH' => '512']], + [380, 128, ['OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500']], + ]; + } + + /** + * @dataProvider quotaChunkedFailProvider + */ + public function testCheckQuotaChunkedFail($quota, $chunkTotalSize, $headers): void { + $this->expectException(\Sabre\DAV\Exception\InsufficientStorage::class); + + $this->init($quota, 'sub/test.txt'); + + $mockChunking = $this->getMockBuilder(\OC_FileChunking::class) + ->disableOriginalConstructor() + ->getMock(); + $mockChunking->expects($this->once()) + ->method('getCurrentSize') + ->willReturn($chunkTotalSize); + + $this->plugin->expects($this->once()) + ->method('getFileChunking') + ->willReturn($mockChunking); + + $headers['OC-CHUNKED'] = 1; + $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); + $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1'); + } + private function buildFileViewMock($quota, $checkedPath) { // mock filesysten $view = $this->getMockBuilder(View::class) diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php index 564d8c5938c..f6aa79eb6c4 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php @@ -57,6 +57,8 @@ abstract class RequestTestCase extends TestCase { protected function setUp(): void { parent::setUp(); + unset($_SERVER['HTTP_OC_CHUNKED']); + $this->serverFactory = new ServerFactory( \OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class), diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php index f8d5ad40b49..16953d9b598 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php @@ -92,4 +92,114 @@ class UploadTest extends RequestTestCase { $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); } + public function testChunkedUpload(): void { + $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(): void { + $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(): void { + $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()); + } + + public function testChunkedUploadOutOfOrderReadLocked(): void { + $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 + $result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); + } + + public function testChunkedUploadOutOfOrderWriteLocked(): void { + $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 + $result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); + $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); + } } |