From c7f4b02c9857a45bb3936024c00b8e31d3072b6f Mon Sep 17 00:00:00 2001 From: Stefano Rosanelli Date: Fri, 21 Jul 2023 15:50:00 +0200 Subject: [PATCH] Handle files index via async job (#9) * #54 feat: handle files index via async job * chore: add annotations --- src/Index/CollectionHandler.php | 31 +++++++- src/Job/Service/IndexFileService.php | 52 +++++++++++++ .../TestCase/Index/CollectionHandlerTest.php | 30 +++++++- .../Job/Service/IndexFileServiceTest.php | 73 +++++++++++++++++++ tests/TestMockTrait.php | 24 ++++++ 5 files changed, 204 insertions(+), 6 deletions(-) create mode 100644 src/Job/Service/IndexFileService.php create mode 100644 tests/TestCase/Job/Service/IndexFileServiceTest.php diff --git a/src/Index/CollectionHandler.php b/src/Index/CollectionHandler.php index 1d2fbd1..4738f9f 100644 --- a/src/Index/CollectionHandler.php +++ b/src/Index/CollectionHandler.php @@ -10,9 +10,11 @@ use BEdita\Chatlas\Client\ChatlasClient; use BEdita\Core\Filesystem\FilesystemRegistry; +use BEdita\Core\Model\Entity\AsyncJob; use BEdita\Core\Model\Entity\ObjectEntity; use Cake\Http\Client\FormData; use Cake\Log\LogTrait; +use Cake\ORM\Locator\LocatorAwareTrait; use Cake\Utility\Hash; use Laminas\Diactoros\UploadedFile; @@ -21,6 +23,7 @@ */ class CollectionHandler { + use LocatorAwareTrait; use LogTrait; /** @@ -152,7 +155,7 @@ protected function addDocument(ObjectEntity $collection, ObjectEntity $entity): return; } if ($entity->get('type') === 'files') { - $this->uploadDocument($collection, $entity); + $this->uploadDocumentJob($collection, $entity); return; } @@ -175,7 +178,7 @@ protected function addDocument(ObjectEntity $collection, ObjectEntity $entity): * @param \BEdita\Core\Model\Entity\ObjectEntity $entity Document entity * @return void */ - protected function uploadDocument(ObjectEntity $collection, ObjectEntity $entity): void + public function uploadDocument(ObjectEntity $collection, ObjectEntity $entity): void { $form = new FormData(); if (empty($entity->get('streams'))) { @@ -208,6 +211,30 @@ protected function uploadDocument(ObjectEntity $collection, ObjectEntity $entity $form ); $entity->set('index_updated', date('c')); + $entity->set('index_status', 'done'); + $entity->getTable()->saveOrFail($entity, ['_skipAfterSave' => true]); + } + + /** + * Create async job to upload file to index + * + * @param \BEdita\Core\Model\Entity\ObjectEntity $collection Collection entity + * @param \BEdita\Core\Model\Entity\ObjectEntity $entity File entity + * @return void + */ + protected function uploadDocumentJob(ObjectEntity $collection, ObjectEntity $entity): void + { + $asyncJob = new AsyncJob([ + 'service' => 'BEdita/Chatlas.IndexFile', + 'max_attempts' => 3, + 'priority' => 5, + 'payload' => [ + 'collection_id' => $collection->id, + 'file_id' => $entity->id, + ], + ]); + $this->fetchTable('AsyncJobs')->saveOrFail($asyncJob); + $entity->set('index_status', 'processing'); $entity->getTable()->saveOrFail($entity, ['_skipAfterSave' => true]); } diff --git a/src/Job/Service/IndexFileService.php b/src/Job/Service/IndexFileService.php new file mode 100644 index 0000000..3b28098 --- /dev/null +++ b/src/Job/Service/IndexFileService.php @@ -0,0 +1,52 @@ + `true` on success, `false` on failure + * - 'messages' (optional) => array of messages + * + * @param array $payload Input data for running this job. + * @param array $options Options for running this job. + * @return bool + */ + public function run(array $payload, array $options = []): bool + { + $handler = new CollectionHandler(); + try { + /** @var \BEdita\Core\Model\Entity\ObjectEntity $collection */ + $collection = $this->fetchTable('Collections')->get($payload['collection_id']); + /** @var \BEdita\Core\Model\Entity\ObjectEntity $file */ + $file = $this->fetchTable('Files')->get($payload['file_id']); + $handler->uploadDocument($collection, $file); + + return true; + } catch (Throwable $th) { + $this->log(sprintf('IndexFile async job error - %s', $th->getMessage()), 'error'); + } + + return false; + } +} diff --git a/tests/TestCase/Index/CollectionHandlerTest.php b/tests/TestCase/Index/CollectionHandlerTest.php index 68435cc..dc4d00e 100644 --- a/tests/TestCase/Index/CollectionHandlerTest.php +++ b/tests/TestCase/Index/CollectionHandlerTest.php @@ -6,6 +6,7 @@ use BEdita\Chatlas\Index\CollectionHandler; use BEdita\Chatlas\Test\TestMockTrait; use BEdita\Core\Filesystem\FilesystemRegistry; +use BEdita\Core\Model\Entity\AsyncJob; use BEdita\Core\Model\Entity\ObjectEntity; use BEdita\Core\Model\Entity\Stream; use Cake\TestSuite\TestCase; @@ -69,14 +70,14 @@ public function testRemoveCollection(): void } /** - * Test `uploadDocument()` method. + * Test `uploadDocumentJob()` method. * * @return void - * @covers ::uploadDocument() + * @covers ::uploadDocumentJob() * @covers ::updateDocument() * @covers ::addDocument() */ - public function testUploadEmptyStream(): void + public function testUploadDocumentJob(): void { $entity = $this->mockEntity('files', [ 'index_updated' => null, @@ -85,8 +86,29 @@ public function testUploadEmptyStream(): void $entity->setNew(false); $handler = new CollectionHandler(); $collection = new ObjectEntity(); + $this->mockTable('AsyncJobs', new AsyncJob()); $handler->updateDocument($collection, $entity); static::assertNull($entity->get('index_updated')); + static::assertEquals('processing', $entity->get('index_status')); + } + + /** + * Test `uploadDocument()` method. + * + * @return void + * @covers ::uploadDocument() + */ + public function testUploadEmptyStream(): void + { + $entity = $this->mockEntity('files', [ + 'index_updated' => null, + 'status' => 'on', + ]); + $entity->setNew(false); + $handler = new CollectionHandler(); + $collection = new ObjectEntity(); + $handler->uploadDocument($collection, $entity); + static::assertNull($entity->get('index_updated')); } /** @@ -118,7 +140,7 @@ public function testUploadStream(): void $handler = new CollectionHandler(); $collection = new ObjectEntity(); - $handler->updateDocument($collection, $entity); + $handler->uploadDocument($collection, $entity); static::assertNotEmpty($entity->get('index_updated')); } diff --git a/tests/TestCase/Job/Service/IndexFileServiceTest.php b/tests/TestCase/Job/Service/IndexFileServiceTest.php new file mode 100644 index 0000000..9186858 --- /dev/null +++ b/tests/TestCase/Job/Service/IndexFileServiceTest.php @@ -0,0 +1,73 @@ + [ + 'className' => 'BEdita/Core.Local', + 'path' => './tests' . DS . 'uploads', + ], + ]); + + $stream = new Stream(); + $stream->set('uri', 'default://test.txt', ['guard' => false]); + $stream->set('file_size', 1, ['guard' => false]); + $file = $this->mockEntity('files', [ + 'index_updated' => null, + 'status' => 'on', + 'streams' => [$stream], + ]); + $this->mockClientResponse(); + $this->mockTable('Collections', new ObjectEntity()); + $this->mockTable('Files', $file); + + $service = new IndexFileService(); + $success = $service->run([ + 'collection_id' => 1, + 'file_id' => 2, + ]); + static::assertTrue($success); + static::assertNotEmpty($file->get('index_updated')); + static::assertEquals('done', $file->get('index_status')); + } + + /** + * Test `run()` method with failure. + * + * @return void + * @covers ::run() + */ + public function testFailure(): void + { + $service = new IndexFileService(); + $success = $service->run([ + 'collection_id' => 1, + 'file_id' => 2, + ]); + static::assertFalse($success); + } +} diff --git a/tests/TestMockTrait.php b/tests/TestMockTrait.php index d5366dd..8e03ffd 100644 --- a/tests/TestMockTrait.php +++ b/tests/TestMockTrait.php @@ -8,7 +8,9 @@ use Cake\Core\Configure; use Cake\Http\Client\Adapter\Stream; use Cake\Http\Client\Response; +use Cake\ORM\Entity; use Cake\ORM\Table; +use Cake\ORM\TableRegistry; trait TestMockTrait { @@ -78,4 +80,26 @@ protected function mockEntity(?string $type = null, array $data = [], array $ass return $mockEntity; } + + /** + * Create mock table + * + * @param string $alias Table alias + * @param Entity $entity Entity + * @return void + */ + protected function mockTable(string $alias, Entity $entity): void + { + $mockTable = $this->getMockBuilder(Table::class) + ->onlyMethods(['saveOrFail', 'get']) + ->getMock(); + $mockTable->method('saveOrFail') + ->willReturn($entity); + $mockTable->method('get') + ->willReturn($entity); + + $locator = TableRegistry::getTableLocator(); + $locator->remove($alias); + $locator->set($alias, $mockTable); + } }