diff --git a/Storage/src/Bucket.php b/Storage/src/Bucket.php index 887cffd4f84d..c15b50d07b32 100644 --- a/Storage/src/Bucket.php +++ b/Storage/src/Bucket.php @@ -1145,6 +1145,8 @@ public function update(array $options = []) * matches the given value. * @type string $ifMetagenerationMatch Makes the operation conditional on whether the object's current * metageneration matches the given value. + * @type bool $deleteSourceObjects If true, the source objects will be + * deleted after a successful compose operation. * } * @return StorageObject * @throws \InvalidArgumentException diff --git a/Storage/src/Connection/ServiceDefinition/storage-v1.json b/Storage/src/Connection/ServiceDefinition/storage-v1.json index dcce6fbecb50..55ebe63e52ba 100644 --- a/Storage/src/Connection/ServiceDefinition/storage-v1.json +++ b/Storage/src/Connection/ServiceDefinition/storage-v1.json @@ -897,6 +897,10 @@ "$ref": "Object", "description": "Properties of the resulting object." }, + "deleteSourceObjects": { + "type": "boolean", + "description": "If true, the source objects will be deleted after a successful compose operation." + }, "kind": { "type": "string", "description": "The kind of item this is.", diff --git a/Storage/tests/System/ManageObjectsTest.php b/Storage/tests/System/ManageObjectsTest.php index 847ad64cc0be..abdca16cfbe3 100644 --- a/Storage/tests/System/ManageObjectsTest.php +++ b/Storage/tests/System/ManageObjectsTest.php @@ -597,6 +597,84 @@ public function testComposeObjects($object) return $composedObject; } + public function testComposeObjectsWithDeleteSourceObjects() + { + $source1 = self::$bucket->upload('content1', ['name' => uniqid(self::TESTING_PREFIX) . '-s1.txt']); + $source2 = self::$bucket->upload('content2', ['name' => uniqid(self::TESTING_PREFIX) . '-s2.txt']); + + $this->assertTrue($source1->exists()); + $this->assertTrue($source2->exists()); + + $name = uniqid(self::TESTING_PREFIX) . '-composed.txt'; + $composedObject = self::$bucket->compose( + [$source1, $source2], + $name, + ['deleteSourceObjects' => true] + ); + + $this->assertEquals($name, $composedObject->name()); + $this->assertEquals('content1content2', $composedObject->downloadAsString()); + + $this->assertFalse($source1->exists()); + $this->assertFalse($source2->exists()); + + $composedObject->delete(); + } + + public function testComposeObjectsWithDeleteSourceObjectsFalse() + { + $source1 = self::$bucket->upload('content1', ['name' => uniqid(self::TESTING_PREFIX) . '-s1.txt']); + $source2 = self::$bucket->upload('content2', ['name' => uniqid(self::TESTING_PREFIX) . '-s2.txt']); + + $this->assertTrue($source1->exists()); + $this->assertTrue($source2->exists()); + + $name = uniqid(self::TESTING_PREFIX) . '-composed.txt'; + $composedObject = self::$bucket->compose( + [$source1, $source2], + $name, + ['deleteSourceObjects' => false] + ); + + $this->assertEquals($name, $composedObject->name()); + $this->assertEquals('content1content2', $composedObject->downloadAsString()); + + // Source objects should still exist because deleteSourceObjects is false + $this->assertTrue($source1->exists()); + $this->assertTrue($source2->exists()); + + $source1->delete(); + $source2->delete(); + $composedObject->delete(); + } + + public function testComposeObjectsWithDeleteSourceObjectsNull() + { + $source1 = self::$bucket->upload('content1', ['name' => uniqid(self::TESTING_PREFIX) . '-s1.txt']); + $source2 = self::$bucket->upload('content2', ['name' => uniqid(self::TESTING_PREFIX) . '-s2.txt']); + + $this->assertTrue($source1->exists()); + $this->assertTrue($source2->exists()); + + $name = uniqid(self::TESTING_PREFIX) . '-composed.txt'; + $composedObject = self::$bucket->compose( + [$source1, $source2], + $name, + ['deleteSourceObjects' => null] + ); + + $this->assertEquals($name, $composedObject->name()); + $this->assertEquals('content1content2', $composedObject->downloadAsString()); + + // Source objects should still exist because deleteSourceObjects is null + $this->assertTrue($source1->exists()); + $this->assertTrue($source2->exists()); + + $source1->delete(); + $source2->delete(); + $composedObject->delete(); + } + public function testSoftDeleteObject() { $softDeleteBucketName = "soft-delete-bucket-" . uniqid(); diff --git a/Storage/tests/Unit/BucketTest.php b/Storage/tests/Unit/BucketTest.php index 23618a7c68d8..4f70471fdb21 100644 --- a/Storage/tests/Unit/BucketTest.php +++ b/Storage/tests/Unit/BucketTest.php @@ -419,6 +419,86 @@ public function testComposesObjects( $this->assertEquals($destinationObject, $object->name()); } + public function testComposeWithDeleteSourceObjects() + { + $acl = 'private'; + $destinationObject = 'combined-files.txt'; + $this->connection->composeObject([ + 'destinationBucket' => self::BUCKET_NAME, + 'destinationObject' => $destinationObject, + 'destinationPredefinedAcl' => $acl, + 'destination' => ['contentType' => 'text/plain'], + 'sourceObjects' => [['name' => 'file1.txt'], ['name' => 'file2.txt']], + 'deleteSourceObjects' => true, + ]) + ->willReturn([ + 'name' => $destinationObject, + 'generation' => 1 + ]) + ->shouldBeCalledTimes(1); + + $bucket = $this->getBucket(); + + $object = $bucket->compose(['file1.txt', 'file2.txt'], $destinationObject, [ + 'predefinedAcl' => $acl, + 'deleteSourceObjects' => true + ]); + + $this->assertEquals($destinationObject, $object->name()); + } + + public function testComposeWithDeleteSourceObjectsFalse() + { + $acl = 'private'; + $destinationObject = 'combined-files.txt'; + $this->connection->composeObject([ + 'destinationBucket' => self::BUCKET_NAME, + 'destinationObject' => $destinationObject, + 'destinationPredefinedAcl' => $acl, + 'destination' => ['contentType' => 'text/plain'], + 'sourceObjects' => [['name' => 'file1.txt'], ['name' => 'file2.txt']], + ]) + ->willReturn([ + 'name' => $destinationObject, + 'generation' => 1 + ]) + ->shouldBeCalledTimes(1); + $bucket = $this->getBucket(); + + $object = $bucket->compose(['file1.txt', 'file2.txt'], $destinationObject, [ + 'predefinedAcl' => $acl, + 'deleteSourceObjects' => false + ]); + + $this->assertEquals($destinationObject, $object->name()); + } + + public function testComposeWithDeleteSourceObjectsNull() + { + $acl = 'private'; + $destinationObject = 'combined-files.txt'; + $this->connection->composeObject([ + 'destinationBucket' => self::BUCKET_NAME, + 'destinationObject' => $destinationObject, + 'destinationPredefinedAcl' => $acl, + 'destination' => ['contentType' => 'text/plain'], + 'sourceObjects' => [['name' => 'file1.txt'], ['name' => 'file2.txt']], + ]) + ->willReturn([ + 'name' => $destinationObject, + 'generation' => 1 + ]) + ->shouldBeCalledTimes(1); + $bucket = $this->getBucket(); + + $object = $bucket->compose(['file1.txt', 'file2.txt'], $destinationObject, [ + 'predefinedAcl' => $acl, + 'deleteSourceObjects' => null + ]); + + $this->assertEquals($destinationObject, $object->name()); + } + public function composeProvider() { $object1 = $this->prophesize(StorageObject::class);