summaryrefslogtreecommitdiffstats
path: root/apps/dav/tests/unit/Connector/Sabre
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/tests/unit/Connector/Sabre')
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/FileTest.php221
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php60
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php91
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php2
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php110
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());
+ }
}