Update DropFile to handle object versioning

This commit is contained in:
2024-09-02 16:24:23 +02:00
parent b6edbb3eed
commit 3d49c959e0
42 changed files with 857 additions and 539 deletions

View File

@@ -14,19 +14,37 @@ namespace AsyncUpload\Driver\OpenstackObjectStore;
use Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\TempUrlOpenstackGenerator;
use Chill\DocStoreBundle\AsyncUpload\SignedUrl;
use Chill\DocStoreBundle\AsyncUpload\SignedUrlPost;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* @internal
*
* @coversNothing
*/
class TempUrlOpenstackGeneratorTest extends TestCase
class TempUrlOpenstackGeneratorTest extends KernelTestCase
{
private ParameterBagInterface $parameterBag;
private HttpClientInterface $client;
private const TESTING_OBJECT_NAME_PREFIX = 'test-prefix-o0o008wk404gcos40k8s4s4c44cgwwos4k4o8k/';
private const TESTING_OBJECT_NAME = 'object-name-4fI0iAtq';
private function setUpIntegration(): void
{
self::bootKernel();
$this->parameterBag = self::getContainer()->get(ParameterBagInterface::class);
$this->client = self::getContainer()->get(HttpClientInterface::class);
}
/**
* @dataProvider dataProviderGenerate
*/
@@ -175,4 +193,62 @@ class TempUrlOpenstackGeneratorTest extends TestCase
];
}
}
/**
* @group openstack-integration
*/
public function testGeneratePostIntegration(): void
{
$this->setUpIntegration();
$generator = new TempUrlOpenstackGenerator(new NullLogger(), new EventDispatcher(), new MockClock(), $this->parameterBag);
$signedUrl = $generator->generatePost(object_name: self::TESTING_OBJECT_NAME_PREFIX);
$formData = new FormDataPart([
'redirect', $signedUrl->redirect,
'max_file_size' => (string) $signedUrl->max_file_size,
'max_file_count' => (string) $signedUrl->max_file_count,
'expires' => (string) $signedUrl->expires->getTimestamp(),
'signature' => $signedUrl->signature,
self::TESTING_OBJECT_NAME => DataPart::fromPath(
__DIR__.'/file-to-upload.txt',
self::TESTING_OBJECT_NAME
),
]);
$response = $this->client
->request(
'POST',
$signedUrl->url,
[
'body' => $formData->bodyToString(),
'headers' => $formData->getPreparedHeaders()->toArray(),
]
);
self::assertEquals(201, $response->getStatusCode());
}
/**
* @group openstack-integration
*
* @depends testGeneratePostIntegration
*/
public function testGenerateGetIntegration(): void
{
$this->setUpIntegration();
$generator = new TempUrlOpenstackGenerator(new NullLogger(), new EventDispatcher(), new MockClock(), $this->parameterBag);
$signedUrl = $generator->generate('GET', self::TESTING_OBJECT_NAME_PREFIX.self::TESTING_OBJECT_NAME);
$response = $this->client->request('GET', $signedUrl->url);
self::assertEquals(200, $response->getStatusCode());
try {
$content = $response->getContent();
self::assertEquals(file_get_contents(__DIR__.'/file-to-upload.txt'), $content);
} catch (HttpExceptionInterface $exception) {
$this->fail('could not retrieve file content: '.$exception->getMessage());
}
}
}

View File

@@ -13,6 +13,7 @@ namespace Chill\DocStoreBundle\Tests\Controller;
use Chill\DocStoreBundle\Controller\WebdavController;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
@@ -417,30 +418,32 @@ class WebdavControllerTest extends KernelTestCase
class MockedStoredObjectManager implements StoredObjectManagerInterface
{
public function getLastModified(StoredObject|\Chill\DocStoreBundle\Entity\StoredObjectVersion $document): \DateTimeInterface
public function getLastModified(StoredObject|StoredObjectVersion $document): \DateTimeInterface
{
return new \DateTimeImmutable('2023-09-13T14:15', new \DateTimeZone('+02:00'));
}
public function getContentLength(StoredObject|\Chill\DocStoreBundle\Entity\StoredObjectVersion $document): int
public function getContentLength(StoredObject|StoredObjectVersion $document): int
{
return 5;
}
public function read(StoredObject|\Chill\DocStoreBundle\Entity\StoredObjectVersion $document): string
public function read(StoredObject|StoredObjectVersion $document): string
{
return 'abcde';
}
public function write(StoredObject $document, string $clearContent, ?string $contentType = null): \Chill\DocStoreBundle\Entity\StoredObjectVersion
public function write(StoredObject $document, string $clearContent, ?string $contentType = null): StoredObjectVersion
{
return $document->registerVersion();
}
public function etag(StoredObject|\Chill\DocStoreBundle\Entity\StoredObjectVersion $document): string
public function etag(StoredObject|StoredObjectVersion $document): string
{
return 'ab56b4d92b40713acc5af89985d4b786';
}
public function clearCache(): void {}
public function delete(StoredObjectVersion $storedObjectVersion): void {}
}

View File

@@ -21,40 +21,6 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
*/
class StoredObjectTest extends KernelTestCase
{
public function testSaveHistory(): void
{
$storedObject = new StoredObject();
$storedObject
->registerVersion(
[2, 4, 6, 8],
['key' => ['data0' => 'data0']],
'text/html',
'test_0',
);
$storedObject->saveHistory();
$storedObject
->registerVersion(
[8, 10, 12],
['key' => ['data1' => 'data1']],
'text/text',
'test_1',
);
$storedObject->saveHistory();
self::assertEquals('test_0', $storedObject->getDatas()['history'][0]['filename']);
self::assertEquals([2, 4, 6, 8], $storedObject->getDatas()['history'][0]['iv']);
self::assertEquals(['key' => ['data0' => 'data0']], $storedObject->getDatas()['history'][0]['key_infos']);
self::assertEquals('text/html', $storedObject->getDatas()['history'][0]['type']);
self::assertEquals('test_1', $storedObject->getDatas()['history'][1]['filename']);
self::assertEquals([8, 10, 12], $storedObject->getDatas()['history'][1]['iv']);
self::assertEquals(['key' => ['data1' => 'data1']], $storedObject->getDatas()['history'][1]['key_infos']);
self::assertEquals('text/text', $storedObject->getDatas()['history'][1]['type']);
}
public function testRegisterVersion(): void
{
$object = new StoredObject();
@@ -63,6 +29,9 @@ class StoredObjectTest extends KernelTestCase
['key' => ['some key']],
'text/html',
);
self::assertSame($firstVersion, $object->getCurrentVersion());
$version = $object->registerVersion(
[1, 2, 3, 4],
$k = ['key' => ['data0' => 'data0']],
@@ -70,6 +39,8 @@ class StoredObjectTest extends KernelTestCase
'abcde',
);
self::assertSame($version, $object->getCurrentVersion());
self::assertCount(2, $object->getVersions());
self::assertEquals('abcde', $object->getFilename());
self::assertEquals([1, 2, 3, 4], $object->getIv());

View File

@@ -15,11 +15,17 @@ use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Form\DataMapper\StoredObjectDataMapper;
use Chill\DocStoreBundle\Form\DataTransformer\StoredObjectDataTransformer;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Security\Guard\JWTDavTokenProviderInterface;
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectDenormalizer;
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer;
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectVersionNormalizer;
use Chill\MainBundle\Serializer\Normalizer\UserNormalizer;
use Chill\MainBundle\Templating\Entity\UserRender;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -36,33 +42,41 @@ class StoredObjectTypeTest extends TypeTestCase
{
use ProphecyTrait;
private StoredObject $model;
protected function setUp(): void
{
$this->model = new StoredObject();
$this->model->registerVersion();
parent::setUp();
}
public function testChangeTitleValue(): void
{
$formData = ['title' => $newTitle = 'new title', 'stored_object' => <<<'JSON'
{"datas":[],"filename":"","id":null,"iv":[],"keyInfos":[],"title":"","type":"","uuid":"3c6a28fe-f913-40b9-a201-5eccc4f2d312","status":"ready","createdAt":null,"createdBy":null,"creationDate":null,"_links":{"dav_link":{"href":"http:\/\/url\/fake","expiration":"1716889578"}}}
{"uuid":"9855d676-690b-11ef-88d3-9f5a4129a7b7"}
JSON];
$model = new StoredObject();
$form = $this->factory->create(StoredObjectType::class, $model, ['has_title' => true]);
$form = $this->factory->create(StoredObjectType::class, $this->model, ['has_title' => true]);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals($newTitle, $model->getTitle());
$this->assertEquals($newTitle, $this->model->getTitle());
}
public function testReplaceByAnotherObject(): void
{
$formData = ['title' => $newTitle = 'new title', 'stored_object' => <<<'JSON'
{"filename":"abcdef","iv":[10, 15, 20, 30],"keyInfos":[],"type":"text/html","status":"object_store_created"}
{"uuid":"9855d676-690b-11ef-88d3-9f5a4129a7b7","currentVersion":{"filename":"abcdef","iv":[10, 15, 20, 30],"keyInfos":[],"type":"text/html","persisted": false}}
JSON];
$model = new StoredObject();
$originalObjectId = spl_object_hash($model);
$form = $this->factory->create(StoredObjectType::class, $model, ['has_title' => true]);
$originalObjectId = spl_object_hash($this->model);
$form = $this->factory->create(StoredObjectType::class, $this->model, ['has_title' => true]);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$model = $form->getData();
$this->assertEquals($originalObjectId, spl_object_hash($model));
$this->assertEquals('abcdef', $model->getFilename());
@@ -71,6 +85,29 @@ class StoredObjectTypeTest extends TypeTestCase
$this->assertEquals($newTitle, $model->getTitle());
}
public function testNothingIsChanged(): void
{
$formData = ['title' => $newTitle = 'new title', 'stored_object' => <<<'JSON'
{"uuid":"9855d676-690b-11ef-88d3-9f5a4129a7b7","currentVersion":{"filename":"abcdef","iv":[10, 15, 20, 30],"keyInfos":[],"type":"text/html"}}
JSON];
$originalObjectId = spl_object_hash($this->model);
$originalVersion = $this->model->getCurrentVersion();
$originalFilename = $originalVersion->getFilename();
$originalKeyInfos = $originalVersion->getKeyInfos();
$form = $this->factory->create(StoredObjectType::class, $this->model, ['has_title' => true]);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$model = $form->getData();
$this->assertEquals($originalObjectId, spl_object_hash($model));
$this->assertSame($originalVersion, $model->getCurrentVersion());
$this->assertEquals($originalFilename, $model->getCurrentVersion()->getFilename());
$this->assertEquals($originalKeyInfos, $model->getCurrentVersion()->getKeyInfos());
}
protected function getExtensions()
{
$jwtTokenProvider = $this->prophesize(JWTDavTokenProviderInterface::class);
@@ -84,6 +121,12 @@ class StoredObjectTypeTest extends TypeTestCase
$security = $this->prophesize(Security::class);
$security->isGranted(Argument::cetera())->willReturn(true);
$storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class);
$storedObjectRepository->findOneByUUID(Argument::type('string'))
->willReturn($this->model);
$userRender = $this->prophesize(UserRender::class);
$serializer = new Serializer(
[
new StoredObjectNormalizer(
@@ -91,6 +134,9 @@ class StoredObjectTypeTest extends TypeTestCase
$urlGenerator->reveal(),
$security->reveal()
),
new StoredObjectDenormalizer($storedObjectRepository->reveal()),
new StoredObjectVersionNormalizer(),
new UserNormalizer($userRender->reveal(), new MockClock()),
],
[
new JsonEncoder(),

View File

@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Tests\Serializer\Normalizer;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface;
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectDenormalizer;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
/**
* @internal
*
* @coversNothing
*/
class StoredObjectDenormalizerTest extends TestCase
{
use ProphecyTrait;
public function testDenormalizeWithoutObjectToPopulateWithUUID(): void
{
$storedObject = new StoredObject();
$storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class);
$storedObjectRepository->findOneByUUID($uuid = $storedObject->getUUID()->toString())
->shouldBeCalledOnce()
->willReturn($storedObject);
$denormalizer = new StoredObjectDenormalizer($storedObjectRepository->reveal());
$actual = $denormalizer->denormalize(['uuid' => $uuid], 'json');
self::assertSame($storedObject, $actual);
}
public function testDenormalizeWithoutObjectToPopulateWithId(): void
{
$storedObject = new StoredObject();
$storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class);
$storedObjectRepository->find($id = 1)
->shouldBeCalledOnce()
->willReturn($storedObject);
$denormalizer = new StoredObjectDenormalizer($storedObjectRepository->reveal());
$actual = $denormalizer->denormalize(['id' => $id], 'json');
self::assertSame($storedObject, $actual);
}
public function testDenormalizeTitle(): void
{
$storedObject = new StoredObject();
$storedObject->setTitle('foo');
$storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class);
$denormalizer = new StoredObjectDenormalizer($storedObjectRepository->reveal());
$actual = $denormalizer->denormalize([], StoredObject::class, 'json', [AbstractNormalizer::OBJECT_TO_POPULATE => $storedObject]);
self::assertEquals('foo', $actual->getTitle(), 'the title should remains the same');
$actual = $denormalizer->denormalize(['title' => 'bar'], StoredObject::class, 'json', [AbstractNormalizer::OBJECT_TO_POPULATE => $storedObject]);
self::assertEquals('bar', $actual->getTitle(), 'the title should have been updated');
}
public function testDenormalizeNoNewVersion(): void
{
$storedObject = new StoredObject();
$version = $storedObject->registerVersion();
$iv = $version->getIv();
$keyInfos = $version->getKeyInfos();
$type = $version->getType();
$filename = $version->getFilename();
$storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class);
$denormalizer = new StoredObjectDenormalizer($storedObjectRepository->reveal());
$actual = $denormalizer->denormalize([
'currentVersion' => [
'iv' => $iv,
'keyInfos' => $keyInfos,
'type' => $type,
'filename' => $filename,
],
], StoredObject::class, 'json', [AbstractNormalizer::OBJECT_TO_POPULATE => $storedObject]);
self::assertSame($storedObject, $actual);
self::assertSame($version, $storedObject->getCurrentVersion());
self::assertEquals($iv, $version->getIv());
self::assertEquals($keyInfos, $version->getKeyInfos());
self::assertEquals($type, $version->getType());
self::assertEquals($filename, $version->getFilename());
}
public function testDenormalizeNewVersion(): void
{
$storedObject = new StoredObject();
$version = $storedObject->registerVersion();
$iv = ['1, 2, 3'];
$keyInfos = ['some-key' => 'some'];
$type = 'text/html';
$filename = 'Foo-Bar';
$storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class);
$denormalizer = new StoredObjectDenormalizer($storedObjectRepository->reveal());
$actual = $denormalizer->denormalize([
'currentVersion' => [
'iv' => $iv,
'keyInfos' => $keyInfos,
'type' => $type,
'filename' => $filename,
// this is the required key for new versions
'persisted' => false,
],
], StoredObject::class, 'json', [AbstractNormalizer::OBJECT_TO_POPULATE => $storedObject]);
self::assertSame($storedObject, $actual);
self::assertNotSame($version, $storedObject->getCurrentVersion());
$version = $storedObject->getCurrentVersion();
self::assertEquals($iv, $version->getIv());
self::assertEquals($keyInfos, $version->getKeyInfos());
self::assertEquals($type, $version->getType());
self::assertEquals($filename, $version->getFilename());
}
}

View File

@@ -9,25 +9,32 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocStoreBundle\Tests\Serializer\Normalizer;
namespace ChillDocStoreBundle\Tests\Serializer\Normalizer;
use Chill\DocStoreBundle\Entity\StoredObject;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectVersionNormalizer;
use Chill\MainBundle\Serializer\Normalizer\UserNormalizer;
use Chill\MainBundle\Templating\Entity\UserRender;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Serializer;
/**
* @internal
*
* @coversNothing
*/
class StoredObjectVersionNormalizerTest extends KernelTestCase
class StoredObjectVersionNormalizerTest extends TestCase
{
private NormalizerInterface $normalizer;
protected function setUp(): void
{
self::bootKernel();
$this->normalizer = self::getContainer()->get(NormalizerInterface::class);
$userRender = $this->createMock(UserRender::class);
$userRender->method('renderString')->willReturn('user');
$this->normalizer = new StoredObjectVersionNormalizer();
$this->normalizer->setNormalizer(new Serializer([new UserNormalizer($userRender, new MockClock())]));
}
public function testNormalize(): void
@@ -58,4 +65,14 @@ class StoredObjectVersionNormalizerTest extends KernelTestCase
$actual
);
}
public function testNormalizeUnsupportedObject(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The object must be an instance of Chill\DocStoreBundle\Entity\StoredObjectVersion');
$unsupportedObject = new \stdClass();
$this->normalizer->normalize($unsupportedObject, 'json', ['groups' => ['read']]);
}
}