From b269594ddc18d355c131c188554b95b9609686cc Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Wed, 17 Feb 2021 21:35:42 +0100 Subject: [PATCH 1/3] feat(bridge): APIP bridge started --- .github/workflows/daily.yml | 0 README.md | 2 + composer.json | 4 + doc/api_platform.md | 3 + .../ApiPlatform/CollectionDataProvider.php | 66 ++++++ .../ApiPlatform/Filter/SearchFilter.php | 112 ++++++++++ src/Bridge/ApiPlatform/ItemDataProvider.php | 57 +++++ .../SchedulerBundleConfiguration.php | 4 + .../SchedulerBundleExtension.php | 44 ++++ .../CollectionDataProviderTest.php | 110 ++++++++++ .../ApiPlatform/Filter/SearchFilterTest.php | 194 ++++++++++++++++++ .../ApiPlatform/ItemDataProviderTest.php | 68 ++++++ .../SchedulerBundleExtensionTest.php | 42 ++++ .../WorkerLifecycleSubscriberTest.php | 77 +++++++ tests/Serializer/TaskNormalizerTest.php | 42 ++++ 15 files changed, 825 insertions(+) create mode 100644 .github/workflows/daily.yml create mode 100644 doc/api_platform.md create mode 100644 src/Bridge/ApiPlatform/CollectionDataProvider.php create mode 100644 src/Bridge/ApiPlatform/Filter/SearchFilter.php create mode 100644 src/Bridge/ApiPlatform/ItemDataProvider.php create mode 100644 tests/Bridge/ApiPlatform/CollectionDataProviderTest.php create mode 100644 tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php create mode 100644 tests/Bridge/ApiPlatform/ItemDataProviderTest.php diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml new file mode 100644 index 00000000..e69de29b diff --git a/README.md b/README.md index 0dbc2c40..ecdf3272 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ - Background worker - [Symfony/Messenger](https://symfony.com/doc/current/messenger.html) integration - [Mercure](https://www.mercure.rocks) integration +- [API-Platform](https://api-platform.com/) support ## Installation @@ -98,6 +99,7 @@ When a task is configured, time to execute it, two approaches can be used: * [Usage](doc/usage.md) * [Configuration](doc/configuration.md) * [Best practices](doc/best_practices.md) +* [API-Platform](doc/api_platform.md) * [Tasks](doc/tasks.md) * [Transports](doc/transport.md) * [Lock](doc/lock.md) diff --git a/composer.json b/composer.json index 52d8752c..f38448c6 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,8 @@ "psr-4": { "SchedulerBundle\\": "src/", "SchedulerBundle\\Bridge\\": "src/Bridge/", + "SchedulerBundle\\Bridge\\ApiPlatform\\": "src/Bridge/ApiPlatform/", + "SchedulerBundle\\Bridge\\ApiPlatform\\Filter\\": "src/Bridge/ApiPlatform/Filter/", "SchedulerBundle\\Bridge\\Doctrine\\": "src/Bridge/Doctrine/", "SchedulerBundle\\Bridge\\Doctrine\\SchemaListener\\": "src/Bridge/Doctrine/SchemaListener/", "SchedulerBundle\\Bridge\\Doctrine\\Transport\\": "src/Bridge/Doctrine/Transport/", @@ -56,6 +58,8 @@ "psr-4": { "Tests\\SchedulerBundle\\": "tests/", "Tests\\SchedulerBundle\\Bridge\\": "tests/Bridge/", + "Tests\\SchedulerBundle\\Bridge\\ApiPlatform\\": "tests/Bridge/ApiPlatform/", + "Tests\\SchedulerBundle\\Bridge\\ApiPlatform\\Filter\\": "tests/Bridge/ApiPlatform/Filter/", "Tests\\SchedulerBundle\\Bridge\\Doctrine\\": "tests/Bridge/Doctrine/", "Tests\\SchedulerBundle\\Bridge\\Doctrine\\SchemaListener\\": "tests/Bridge/Doctrine/SchemaListener/", "Tests\\SchedulerBundle\\Bridge\\Doctrine\\Transport\\": "tests/Bridge/Doctrine/Transport/", diff --git a/doc/api_platform.md b/doc/api_platform.md new file mode 100644 index 00000000..7696739c --- /dev/null +++ b/doc/api_platform.md @@ -0,0 +1,3 @@ +# API-Platform + +This bundle provides a bridge with API-Platform, diff --git a/src/Bridge/ApiPlatform/CollectionDataProvider.php b/src/Bridge/ApiPlatform/CollectionDataProvider.php new file mode 100644 index 00000000..e8ff1c42 --- /dev/null +++ b/src/Bridge/ApiPlatform/CollectionDataProvider.php @@ -0,0 +1,66 @@ + + */ +final class CollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface +{ + private SearchFilter $searchFilter; + private TransportInterface $transport; + private LoggerInterface $logger; + + public function __construct( + SearchFilter $searchFilter, + TransportInterface $transport, + ?LoggerInterface $logger = null + ) { + $this->searchFilter = $searchFilter; + $this->transport = $transport; + $this->logger = $logger ?: new NullLogger(); + } + + /** + * {@inheritdoc} + */ + public function supports(string $resourceClass, string $operationName = null, array $context = []): bool + { + return $resourceClass === TaskInterface::class; + } + + /** + * {@inheritdoc} + */ + public function getCollection(string $resourceClass, string $operationName = null, array $context = []): TaskListInterface + { + try { + $list = $this->transport->list(); + } catch (Throwable $throwable) { + $this->logger->critical('The list cannot be retrieved', [ + 'error' => $throwable->getMessage(), + ]); + + throw $throwable; + } + + if (array_key_exists('filters', $context) && [] !== $context['filters']) { + return $this->searchFilter->filter($list, $context['filters']); + } + + return $list; + } +} diff --git a/src/Bridge/ApiPlatform/Filter/SearchFilter.php b/src/Bridge/ApiPlatform/Filter/SearchFilter.php new file mode 100644 index 00000000..1cb3be20 --- /dev/null +++ b/src/Bridge/ApiPlatform/Filter/SearchFilter.php @@ -0,0 +1,112 @@ + + */ +final class SearchFilter implements FilterInterface +{ + /** + * {@inheritdoc} + */ + public function getDescription(string $resourceClass): array + { + if (TaskInterface::class !== $resourceClass) { + return []; + } + + return [ + 'expression' => [ + 'type' => 'string', + 'required' => false, + 'property' => 'expression', + 'swagger' => [ + 'description' => 'Filter tasks using the expression', + 'name' => 'expression', + 'type' => 'string', + ], + ], + 'queued' => [ + 'type' => 'bool', + 'required' => false, + 'property' => 'queued', + 'swagger' => [ + 'description' => 'Filter tasks that are queued', + 'name' => 'queued', + 'type' => 'bool', + ], + ], + 'state' => [ + 'type' => 'string', + 'required' => false, + 'property' => 'state', + 'swagger' => [ + 'description' => 'Filter tasks with a specific state', + 'name' => 'state', + 'type' => 'string', + ], + ], + 'timezone' => [ + 'type' => 'string', + 'required' => false, + 'property' => 'timezone', + 'swagger' => [ + 'description' => 'Filter tasks scheduled using a specific timezone', + 'name' => 'timezone', + 'type' => 'string', + ], + ], + 'type' => [ + 'type' => 'string', + 'required' => false, + 'property' => 'type', + 'swagger' => [ + 'description' => 'Filter tasks depending on internal type', + 'name' => 'timezone', + 'type' => 'string', + ], + ], + ]; + } + + public function filter(TaskListInterface $list, array $filters = []): TaskListInterface + { + if ([] === $filters) { + return $list; + } + + if (0 === $list->count()) { + return $list; + } + + foreach ($filters as $filter => $value) { + switch ($filter) { + case 'expression': + $list = $list->filter(fn (TaskInterface $task): bool => $value === $task->getExpression()); + break; + case 'queued': + $list = $list->filter(fn (TaskInterface $task): bool => $task->isQueued()); + break; + case 'state': + $list = $list->filter(fn (TaskInterface $task): bool => $value === $task->getState()); + break; + case 'timezone': + $list = $list->filter(fn (TaskInterface $task): bool => null !== $task->getTimezone() && $value === $task->getTimezone()->getName()); + break; + case 'type': + $list = $list->filter(fn (TaskInterface $task): bool => $value === get_class($task)); + break; + } + } + + return $list; + } +} diff --git a/src/Bridge/ApiPlatform/ItemDataProvider.php b/src/Bridge/ApiPlatform/ItemDataProvider.php new file mode 100644 index 00000000..f348d64d --- /dev/null +++ b/src/Bridge/ApiPlatform/ItemDataProvider.php @@ -0,0 +1,57 @@ + + */ +final class ItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface +{ + private TransportInterface $transport; + private LoggerInterface $logger; + + public function __construct( + TransportInterface $transport, + ?LoggerInterface $logger = null + ) { + $this->transport = $transport; + $this->logger = $logger ?: new NullLogger(); + } + + /** + * {@inheritdoc} + */ + public function supports(string $resourceClass, string $operationName = null, array $context = []): bool + { + return $resourceClass === TaskInterface::class; + } + + /** + * {@inheritdoc} + */ + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): TaskInterface + { + try { + $task = $this->transport->get($id); + } catch (Throwable $throwable) { + $this->logger->critical(sprintf('The task "%s" cannot be found', $id), [ + 'error' => $throwable->getMessage(), + ]); + + throw $throwable; + } + + return $task; + } +} diff --git a/src/DependencyInjection/SchedulerBundleConfiguration.php b/src/DependencyInjection/SchedulerBundleConfiguration.php index 11eed084..5c32bf54 100644 --- a/src/DependencyInjection/SchedulerBundleConfiguration.php +++ b/src/DependencyInjection/SchedulerBundleConfiguration.php @@ -225,6 +225,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() + ->scalarNode('api_platform') + ->info('Enable the API-Platform support') + ->defaultValue(false) + ->end() ->end() ->end() ; diff --git a/src/DependencyInjection/SchedulerBundleExtension.php b/src/DependencyInjection/SchedulerBundleExtension.php index 64ad914c..95c18fcc 100644 --- a/src/DependencyInjection/SchedulerBundleExtension.php +++ b/src/DependencyInjection/SchedulerBundleExtension.php @@ -7,6 +7,9 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; use Redis; +use SchedulerBundle\Bridge\ApiPlatform\CollectionDataProvider; +use SchedulerBundle\Bridge\ApiPlatform\Filter\SearchFilter; +use SchedulerBundle\Bridge\ApiPlatform\ItemDataProvider; use SchedulerBundle\Bridge\Doctrine\SchemaListener\SchedulerTransportDoctrineSchemaSubscriber; use SchedulerBundle\Bridge\Doctrine\Transport\DoctrineTransportFactory; use SchedulerBundle\Bridge\Redis\Transport\RedisTransportFactory; @@ -192,6 +195,7 @@ public function load(array $configs, ContainerBuilder $container): void $this->registerMiddlewareStacks($container, $config); $this->registerProbeContext($container, $config); $this->registerMercureSupport($container, $config); + $this->registerApiPlatformBridge($container, $config); $this->registerDataCollector($container); } @@ -1330,6 +1334,46 @@ private function registerMercureSupport(ContainerBuilder $container, array $conf ; } + private function registerApiPlatformBridge(ContainerBuilder $container, array $configuration): void + { + if (false === $configuration['api_platform']) { + return; + } + + $container->register(ItemDataProvider::class, ItemDataProvider::class) + ->setArguments([ + new Reference(TransportInterface::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), + new Reference(LoggerInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]) + ->setPublic(false) + ->addTag('api_platform.item_data_provider') + ->addTag('container.preload', [ + 'class' => ItemDataProvider::class, + ]) + ; + + $container->register(CollectionDataProvider::class, CollectionDataProvider::class) + ->setArguments([ + new Reference(SearchFilter::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), + new Reference(TransportInterface::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), + new Reference(LoggerInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE), + ]) + ->setPublic(false) + ->addTag('api_platform.collection_data_provider') + ->addTag('container.preload', [ + 'class' => CollectionDataProvider::class, + ]) + ; + + $container->register(SearchFilter::class, SearchFilter::class) + ->setPublic(false) + ->addTag('api_platform.filter') + ->addTag('container.preload', [ + 'class' => SearchFilter::class, + ]) + ; + } + private function registerDataCollector(ContainerBuilder $container): void { $container->register(SchedulerDataCollector::class, SchedulerDataCollector::class) diff --git a/tests/Bridge/ApiPlatform/CollectionDataProviderTest.php b/tests/Bridge/ApiPlatform/CollectionDataProviderTest.php new file mode 100644 index 00000000..99216ba5 --- /dev/null +++ b/tests/Bridge/ApiPlatform/CollectionDataProviderTest.php @@ -0,0 +1,110 @@ + + */ +final class CollectionDataProviderTest extends TestCase +{ + public function testProviderSupport(): void + { + $transport = $this->createMock(TransportInterface::class); + + $provider = new CollectionDataProvider(new SearchFilter(), $transport); + + self::assertInstanceOf(RestrictedDataProviderInterface::class, $provider); + self::assertFalse($provider->supports(stdClass::class)); + self::assertTrue($provider->supports(TaskInterface::class)); + } + + public function testProviderCannotReturnListWithError(): void + { + $transport = $this->createMock(TransportInterface::class); + $transport->expects(self::once())->method('list') + ->willThrowException(new RuntimeException('Random error')) + ; + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::once())->method('critical') + ->with( + self::equalTo('The list cannot be retrieved'), + self::equalTo([ + 'error' => 'Random error', + ]) + ) + ; + + $provider = new CollectionDataProvider(new SearchFilter(), $transport, $logger); + + self::expectException(RuntimeException::class); + self::expectExceptionMessage('Random error'); + self::expectExceptionCode(0); + $provider->getCollection(TaskInterface::class); + } + + public function testProviderCanReturnTaskList(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::never())->method('filter'); + + $transport = $this->createMock(TransportInterface::class); + $transport->expects(self::once())->method('list')->willReturn($list); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::never())->method('critical'); + + $provider = new CollectionDataProvider(new SearchFilter(), $transport, $logger); + + self::assertSame($list, $provider->getCollection(TaskInterface::class)); + } + + public function testProviderCannotReturnFilteredTaskListWithoutFilters(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::never())->method('filter')->willReturnSelf(); + + $transport = $this->createMock(TransportInterface::class); + $transport->expects(self::once())->method('list')->willReturn($list); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::never())->method('critical'); + + $provider = new CollectionDataProvider(new SearchFilter(), $transport, $logger); + $provider->getCollection(TaskInterface::class, 'GET', [ + 'filters' => [], + ]); + } + + public function testProviderCanReturnFilteredTaskList(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::once())->method('filter')->willReturnSelf(); + + $transport = $this->createMock(TransportInterface::class); + $transport->expects(self::once())->method('list')->willReturn($list); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::never())->method('critical'); + + $provider = new CollectionDataProvider(new SearchFilter(), $transport, $logger); + $provider->getCollection(TaskInterface::class, 'GET', [ + 'filters' => [ + 'expression' => '* * * * *', + ], + ]); + } +} diff --git a/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php b/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php new file mode 100644 index 00000000..af68c6b0 --- /dev/null +++ b/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php @@ -0,0 +1,194 @@ + + */ +final class SearchFilterTest extends TestCase +{ + public function testFilterCanDefineDescription(): void + { + $filter = new SearchFilter(); + + self::assertEmpty($filter->getDescription(stdClass::class)); + self::assertNotEmpty($filter->getDescription(TaskInterface::class)); + } + + public function testFilterDescriptionIsDefined(): void + { + $filter = new SearchFilter(); + + $description = $filter->getDescription(TaskInterface::class); + + self::assertCount(5, $description); + self::assertArrayHasKey('expression', $description); + self::assertArrayHasKey('queued', $description); + self::assertArrayHasKey('state', $description); + self::assertArrayHasKey('timezone', $description); + self::assertArrayHasKey('type', $description); + self::assertCount(4, $description['expression']); + self::assertCount(4, $description['queued']); + self::assertCount(4, $description['state']); + self::assertCount(4, $description['timezone']); + self::assertCount(4, $description['type']); + self::assertSame('string', $description['expression']['type']); + self::assertFalse($description['expression']['required']); + self::assertSame('expression', $description['expression']['property']); + self::assertSame([ + 'description' => 'Filter tasks using the expression', + 'name' => 'expression', + 'type' => 'string', + ], $description['expression']['swagger']); + self::assertSame('bool', $description['queued']['type']); + self::assertFalse($description['queued']['required']); + self::assertSame('queued', $description['queued']['property']); + self::assertSame([ + 'description' => 'Filter tasks that are queued', + 'name' => 'queued', + 'type' => 'bool', + ], $description['queued']['swagger']); + self::assertSame('string', $description['state']['type']); + self::assertFalse($description['state']['required']); + self::assertSame('state', $description['state']['property']); + self::assertSame([ + 'description' => 'Filter tasks with a specific state', + 'name' => 'state', + 'type' => 'string', + ], $description['state']['swagger']); + self::assertSame('string', $description['timezone']['type']); + self::assertFalse($description['timezone']['required']); + self::assertSame('timezone', $description['timezone']['property']); + self::assertSame([ + 'description' => 'Filter tasks scheduled using a specific timezone', + 'name' => 'timezone', + 'type' => 'string', + ], $description['timezone']['swagger']); + self::assertSame('string', $description['type']['type']); + self::assertFalse($description['type']['required']); + self::assertSame('type', $description['type']['property']); + self::assertSame([ + 'description' => 'Filter tasks depending on internal type', + 'name' => 'timezone', + 'type' => 'string', + ], $description['type']['swagger']); + } + + public function testFilterCannotFilterWithoutFilters(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::never())->method('count'); + $list->expects(self::never())->method('filter'); + + $filter = new SearchFilter(); + $filter->filter($list); + } + + public function testFilterCannotFilterEmptyList(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::once())->method('count')->willReturn(0); + $list->expects(self::never())->method('filter'); + + $filter = new SearchFilter(); + $filter->filter($list, [ + 'expression' => '* * * * *', + ]); + } + + public function testFilterCanFilterOnExpression(): void + { + $task = $this->createMock(TaskInterface::class); + $task->expects(self::once())->method('getExpression')->willReturn('* * * * *'); + $task->expects(self::never())->method('isQueued'); + $task->expects(self::never())->method('getState'); + $task->expects(self::never())->method('getTimezone'); + + $filter = new SearchFilter(); + $list = $filter->filter(new TaskList([$task]), [ + 'expression' => '* * * * *', + ]); + + self::assertNotEmpty($list); + self::assertCount(1, $list); + } + + public function testFilterCanFilterOnQueuedTask(): void + { + $task = $this->createMock(TaskInterface::class); + $task->expects(self::never())->method('getExpression'); + $task->expects(self::once())->method('isQueued')->willReturn(true); + $task->expects(self::never())->method('getState'); + $task->expects(self::never())->method('getTimezone'); + + $filter = new SearchFilter(); + $list = $filter->filter(new TaskList([$task]), [ + 'queued' => true, + ]); + + self::assertNotEmpty($list); + self::assertCount(1, $list); + } + + public function testFilterCanFilterOnTaskState(): void + { + $task = $this->createMock(TaskInterface::class); + $task->expects(self::never())->method('getExpression'); + $task->expects(self::never())->method('isQueued'); + $task->expects(self::once())->method('getState')->willReturn(TaskInterface::SUCCEED); + $task->expects(self::never())->method('getTimezone'); + + $filter = new SearchFilter(); + $list = $filter->filter(new TaskList([$task]), [ + 'state' => TaskInterface::SUCCEED, + ]); + + self::assertNotEmpty($list); + self::assertCount(1, $list); + } + + public function testFilterCanFilterOnTaskTimezone(): void + { + $task = $this->createMock(TaskInterface::class); + $task->expects(self::never())->method('getExpression'); + $task->expects(self::never())->method('isQueued'); + $task->expects(self::never())->method('getState'); + $task->expects(self::exactly(2))->method('getTimezone')->willReturn(new DateTimeZone('UTC')); + + $filter = new SearchFilter(); + $list = $filter->filter(new TaskList([$task]), [ + 'timezone' => 'UTC', + ]); + + self::assertNotEmpty($list); + self::assertCount(1, $list); + } + + public function testFilterCanFilterOnTaskType(): void + { + $task = $this->createMock(TaskInterface::class); + $task->expects(self::never())->method('getExpression'); + $task->expects(self::never())->method('isQueued'); + $task->expects(self::never())->method('getState'); + $task->expects(self::never())->method('getTimezone'); + + $filter = new SearchFilter(); + $list = $filter->filter(new TaskList([new NullTask('foo')]), [ + 'type' => NullTask::class, + ]); + + self::assertNotEmpty($list); + self::assertCount(1, $list); + } +} diff --git a/tests/Bridge/ApiPlatform/ItemDataProviderTest.php b/tests/Bridge/ApiPlatform/ItemDataProviderTest.php new file mode 100644 index 00000000..450a3c37 --- /dev/null +++ b/tests/Bridge/ApiPlatform/ItemDataProviderTest.php @@ -0,0 +1,68 @@ + + */ +final class ItemDataProviderTest extends TestCase +{ + public function testProviderSupport(): void + { + $transport = $this->createMock(TransportInterface::class); + + $provider = new ItemDataProvider($transport); + + self::assertInstanceOf(RestrictedDataProviderInterface::class, $provider); + self::assertFalse($provider->supports(stdClass::class)); + self::assertTrue($provider->supports(TaskInterface::class)); + } + + public function testProviderCannotReturnUndefinedTask(): void + { + $transport = $this->createMock(TransportInterface::class); + $transport->expects(self::once())->method('get')->with(self::equalTo('foo')) + ->willThrowException(new InvalidArgumentException('The task "foo" does not exist')) + ; + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::once())->method('critical') + ->with(self::equalTo('The task "foo" cannot be found'), self::equalTo([ + 'error' => 'The task "foo" does not exist', + ])) + ; + + $provider = new ItemDataProvider($transport, $logger); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('The task "foo" does not exist'); + self::expectExceptionCode(0); + $provider->getItem(TaskInterface::class, 'foo'); + } + + public function testProviderCanReturnTask(): void + { + $task = $this->createMock(TaskInterface::class); + + $transport = $this->createMock(TransportInterface::class); + $transport->expects(self::once())->method('get')->with(self::equalTo('foo'))->willReturn($task); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::never())->method('critical'); + + $provider = new ItemDataProvider($transport, $logger); + + self::assertSame($task, $provider->getItem(TaskInterface::class, 'foo')); + } +} diff --git a/tests/DependencyInjection/SchedulerBundleExtensionTest.php b/tests/DependencyInjection/SchedulerBundleExtensionTest.php index dec89549..4f9f4cf0 100644 --- a/tests/DependencyInjection/SchedulerBundleExtensionTest.php +++ b/tests/DependencyInjection/SchedulerBundleExtensionTest.php @@ -9,6 +9,9 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; +use SchedulerBundle\Bridge\ApiPlatform\CollectionDataProvider; +use SchedulerBundle\Bridge\ApiPlatform\Filter\SearchFilter; +use SchedulerBundle\Bridge\ApiPlatform\ItemDataProvider; use SchedulerBundle\Bridge\Doctrine\SchemaListener\SchedulerTransportDoctrineSchemaSubscriber; use SchedulerBundle\Bridge\Doctrine\Transport\DoctrineTransportFactory; use SchedulerBundle\Bridge\Redis\Transport\RedisTransportFactory; @@ -1631,6 +1634,45 @@ public function testProbeTasksCanBeConfigured(): void self::assertInstanceOf(Definition::class, $container->getDefinition(Scheduler::class)->getMethodCalls()[0][1][0]); } + public function testApiPlatformBridgeCanBeConfigured(): void + { + $container = $this->getContainer([ + 'path' => '/_foo', + 'timezone' => 'Europe/Paris', + 'transport' => [ + 'dsn' => 'memory://first_in_first_out', + ], + 'tasks' => [], + 'lock_store' => null, + 'api_platform' => true, + ]); + + self::assertTrue($container->hasDefinition(ItemDataProvider::class)); + self::assertFalse($container->getDefinition(ItemDataProvider::class)->isPublic()); + self::assertCount(2, $container->getDefinition(ItemDataProvider::class)->getArguments()); + self::assertInstanceOf(Reference::class, $container->getDefinition(ItemDataProvider::class)->getArgument(0)); + self::assertInstanceOf(Reference::class, $container->getDefinition(ItemDataProvider::class)->getArgument(1)); + self::assertTrue($container->getDefinition(ItemDataProvider::class)->hasTag('api_platform.item_data_provider')); + self::assertTrue($container->getDefinition(ItemDataProvider::class)->hasTag('container.preload')); + self::assertSame(ItemDataProvider::class, $container->getDefinition(ItemDataProvider::class)->getTag('container.preload')[0]['class']); + + self::assertTrue($container->hasDefinition(CollectionDataProvider::class)); + self::assertFalse($container->getDefinition(CollectionDataProvider::class)->isPublic()); + self::assertCount(3, $container->getDefinition(CollectionDataProvider::class)->getArguments()); + self::assertInstanceOf(Reference::class, $container->getDefinition(CollectionDataProvider::class)->getArgument(0)); + self::assertInstanceOf(Reference::class, $container->getDefinition(CollectionDataProvider::class)->getArgument(1)); + self::assertInstanceOf(Reference::class, $container->getDefinition(CollectionDataProvider::class)->getArgument(2)); + self::assertTrue($container->getDefinition(CollectionDataProvider::class)->hasTag('api_platform.collection_data_provider')); + self::assertTrue($container->getDefinition(CollectionDataProvider::class)->hasTag('container.preload')); + self::assertSame(CollectionDataProvider::class, $container->getDefinition(CollectionDataProvider::class)->getTag('container.preload')[0]['class']); + + self::assertTrue($container->hasDefinition(SearchFilter::class)); + self::assertFalse($container->getDefinition(SearchFilter::class)->isPublic()); + self::assertTrue($container->getDefinition(SearchFilter::class)->hasTag('api_platform.filter')); + self::assertTrue($container->getDefinition(SearchFilter::class)->hasTag('container.preload')); + self::assertSame(SearchFilter::class, $container->getDefinition(SearchFilter::class)->getTag('container.preload')[0]['class']); + } + public function testDataCollectorIsConfigured(): void { $container = $this->getContainer([ diff --git a/tests/EventListener/WorkerLifecycleSubscriberTest.php b/tests/EventListener/WorkerLifecycleSubscriberTest.php index 6741d265..18421bf3 100644 --- a/tests/EventListener/WorkerLifecycleSubscriberTest.php +++ b/tests/EventListener/WorkerLifecycleSubscriberTest.php @@ -88,6 +88,25 @@ public function testSubscriberLogOnWorkerRestartedWithoutExecutedTask(): void $workerLifecycleSubscriber->onWorkerRestarted(new WorkerRestartedEvent($worker)); } + public function testSubscriberLogOnWorkerRestartedWithoutExecutedTask(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::once())->method('count')->willReturn(0); + + $worker = $this->createMock(WorkerInterface::class); + $worker->expects(self::once())->method('getFailedTasks')->willReturn($list); + $worker->expects(self::once())->method('getLastExecutedTask')->willReturn(null); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::once())->method('info')->with(self::equalTo('The worker has been restarted'), self::equalTo([ + 'failedTasks' => 0, + 'lastExecutedTask' => null, + ])); + + $subscriber = new WorkerLifecycleSubscriber($logger); + $subscriber->onWorkerRestarted(new WorkerRestartedEvent($worker)); + } + public function testSubscriberLogOnWorkerRestarted(): void { $task = $this->createMock(TaskInterface::class); @@ -124,6 +143,26 @@ public function testSubscriberLogOnWorkerRunningWithoutExecutedTask(): void $workerLifecycleSubscriber->onWorkerRunning(new WorkerRunningEvent($worker)); } + public function testSubscriberLogOnWorkerRunningWithoutExecutedTask(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::once())->method('count')->willReturn(0); + + $worker = $this->createMock(WorkerInterface::class); + $worker->expects(self::once())->method('getFailedTasks')->willReturn($list); + $worker->expects(self::once())->method('getLastExecutedTask')->willReturn(null); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::once())->method('info')->with(self::equalTo('The worker is currently running'), self::equalTo([ + 'failedTasks' => 0, + 'lastExecutedTask' => null, + 'idle' => false, + ])); + + $subscriber = new WorkerLifecycleSubscriber($logger); + $subscriber->onWorkerRunning(new WorkerRunningEvent($worker)); + } + public function testSubscriberLogOnWorkerRunning(): void { $task = $this->createMock(TaskInterface::class); @@ -160,6 +199,25 @@ public function testSubscriberLogOnWorkerStartedWithoutExecutedTask(): void $workerLifecycleSubscriber->onWorkerStarted(new WorkerStartedEvent($worker)); } + public function testSubscriberLogOnWorkerStartedWithoutExecutedTask(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::once())->method('count')->willReturn(0); + + $worker = $this->createMock(WorkerInterface::class); + $worker->expects(self::once())->method('getFailedTasks')->willReturn($list); + $worker->expects(self::once())->method('getLastExecutedTask')->willReturn(null); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::once())->method('info')->with(self::equalTo('The worker has been started'), self::equalTo([ + 'failedTasks' => 0, + 'lastExecutedTask' => null, + ])); + + $subscriber = new WorkerLifecycleSubscriber($logger); + $subscriber->onWorkerStarted(new WorkerStartedEvent($worker)); + } + public function testSubscriberLogOnWorkerStarted(): void { $task = $this->createMock(TaskInterface::class); @@ -195,6 +253,25 @@ public function testSubscriberLogOnWorkerStoppedWithoutExecutedTask(): void $workerLifecycleSubscriber->onWorkerStopped(new WorkerStoppedEvent($worker)); } + public function testSubscriberLogOnWorkerStoppedWithoutExecutedTask(): void + { + $list = $this->createMock(TaskListInterface::class); + $list->expects(self::once())->method('count')->willReturn(0); + + $worker = $this->createMock(WorkerInterface::class); + $worker->expects(self::once())->method('getFailedTasks')->willReturn($list); + $worker->expects(self::once())->method('getLastExecutedTask')->willReturn(null); + + $logger = $this->createMock(LoggerInterface::class); + $logger->expects(self::once())->method('info')->with(self::equalTo('The worker has been stopped'), self::equalTo([ + 'failedTasks' => 0, + 'lastExecutedTask' => null, + ])); + + $subscriber = new WorkerLifecycleSubscriber($logger); + $subscriber->onWorkerStopped(new WorkerStoppedEvent($worker)); + } + public function testSubscriberLogOnWorkerStopped(): void { $task = $this->createMock(TaskInterface::class); diff --git a/tests/Serializer/TaskNormalizerTest.php b/tests/Serializer/TaskNormalizerTest.php index df09afc6..961b7f06 100644 --- a/tests/Serializer/TaskNormalizerTest.php +++ b/tests/Serializer/TaskNormalizerTest.php @@ -952,6 +952,48 @@ public function testChainedTaskWithCommandTaskCanBeDenormalized(): void self::assertContains('-vvv', $fooThirdTask->getOptions()); } + public function testChainedTaskWithCommandTaskCanBeDenormalized(): void + { + $objectNormalizer = new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()])); + $notificationTaskBagNormalizer = new NotificationTaskBagNormalizer($objectNormalizer); + + $serializer = new Serializer([ + $notificationTaskBagNormalizer, + new TaskNormalizer(new DateTimeNormalizer(), new DateTimeZoneNormalizer(), new DateIntervalNormalizer(), $objectNormalizer, $notificationTaskBagNormalizer), + new DateTimeNormalizer(), + new DateIntervalNormalizer(), + new JsonSerializableNormalizer(), + $objectNormalizer, + ], [new JsonEncoder()]); + $objectNormalizer->setSerializer($serializer); + + $data = $serializer->serialize(new ChainedTask( + 'foo', + new ShellTask('bar', ['echo', 'Symfony']), + new CommandTask('foo_second', 'cache:clear', [], ['--no-warmup']), + new CommandTask('foo_third', 'cache:clear', [], ['--no-warmup']) + ), 'json'); + $task = $serializer->deserialize($data, TaskInterface::class, 'json'); + + self::assertInstanceOf(ChainedTask::class, $task); + self::assertNotEmpty($task->getTasks()); + self::assertCount(3, $task->getTasks()); + self::assertInstanceOf(ShellTask::class, $task->getTask(0)); + self::assertSame('bar', $task->getTask(0)->getName()); + + self::assertInstanceOf(CommandTask::class, $task->getTask(1)); + self::assertSame('foo_second', $task->getTask(1)->getName()); + self::assertSame('cache:clear', $task->getTask(1)->getCommand()); + self::assertEmpty($task->getTask(1)->getArguments()); + self::assertNotEmpty($task->getTask(1)->getOptions()); + + self::assertInstanceOf(CommandTask::class, $task->getTask(2)); + self::assertSame('foo_third', $task->getTask(2)->getName()); + self::assertSame('cache:clear', $task->getTask(2)->getCommand()); + self::assertEmpty($task->getTask(2)->getArguments()); + self::assertNotEmpty($task->getTask(2)->getOptions()); + } + public function testShellTaskWithBeforeSchedulingNotificationTaskBagCanBeNormalized(): void { $objectNormalizer = new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()])); From d72fa003fc1910402cd1623f0c217341e3c1ae65 Mon Sep 17 00:00:00 2001 From: Loulier Guillaume Date: Fri, 13 Aug 2021 18:16:20 +0200 Subject: [PATCH 2/3] tests(core): improvements --- .../ApiPlatform/Filter/SearchFilter.php | 6 +- .../ApiPlatform/Filter/SearchFilterTest.php | 2 +- .../WorkerLifecycleSubscriberTest.php | 77 ------------------- tests/Serializer/TaskNormalizerTest.php | 42 ---------- 4 files changed, 6 insertions(+), 121 deletions(-) diff --git a/src/Bridge/ApiPlatform/Filter/SearchFilter.php b/src/Bridge/ApiPlatform/Filter/SearchFilter.php index 1cb3be20..467b9ddb 100644 --- a/src/Bridge/ApiPlatform/Filter/SearchFilter.php +++ b/src/Bridge/ApiPlatform/Filter/SearchFilter.php @@ -99,7 +99,11 @@ public function filter(TaskListInterface $list, array $filters = []): TaskListIn $list = $list->filter(fn (TaskInterface $task): bool => $value === $task->getState()); break; case 'timezone': - $list = $list->filter(fn (TaskInterface $task): bool => null !== $task->getTimezone() && $value === $task->getTimezone()->getName()); + $list = $list->filter(function (TaskInterface $task) use ($value): bool { + $timezone = $task->getTimezone(); + + return null !== $timezone && $value === $timezone->getName(); + }); break; case 'type': $list = $list->filter(fn (TaskInterface $task): bool => $value === get_class($task)); diff --git a/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php b/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php index af68c6b0..69968211 100644 --- a/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php +++ b/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php @@ -164,7 +164,7 @@ public function testFilterCanFilterOnTaskTimezone(): void $task->expects(self::never())->method('getExpression'); $task->expects(self::never())->method('isQueued'); $task->expects(self::never())->method('getState'); - $task->expects(self::exactly(2))->method('getTimezone')->willReturn(new DateTimeZone('UTC')); + $task->expects(self::once())->method('getTimezone')->willReturn(new DateTimeZone('UTC')); $filter = new SearchFilter(); $list = $filter->filter(new TaskList([$task]), [ diff --git a/tests/EventListener/WorkerLifecycleSubscriberTest.php b/tests/EventListener/WorkerLifecycleSubscriberTest.php index 18421bf3..6741d265 100644 --- a/tests/EventListener/WorkerLifecycleSubscriberTest.php +++ b/tests/EventListener/WorkerLifecycleSubscriberTest.php @@ -88,25 +88,6 @@ public function testSubscriberLogOnWorkerRestartedWithoutExecutedTask(): void $workerLifecycleSubscriber->onWorkerRestarted(new WorkerRestartedEvent($worker)); } - public function testSubscriberLogOnWorkerRestartedWithoutExecutedTask(): void - { - $list = $this->createMock(TaskListInterface::class); - $list->expects(self::once())->method('count')->willReturn(0); - - $worker = $this->createMock(WorkerInterface::class); - $worker->expects(self::once())->method('getFailedTasks')->willReturn($list); - $worker->expects(self::once())->method('getLastExecutedTask')->willReturn(null); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects(self::once())->method('info')->with(self::equalTo('The worker has been restarted'), self::equalTo([ - 'failedTasks' => 0, - 'lastExecutedTask' => null, - ])); - - $subscriber = new WorkerLifecycleSubscriber($logger); - $subscriber->onWorkerRestarted(new WorkerRestartedEvent($worker)); - } - public function testSubscriberLogOnWorkerRestarted(): void { $task = $this->createMock(TaskInterface::class); @@ -143,26 +124,6 @@ public function testSubscriberLogOnWorkerRunningWithoutExecutedTask(): void $workerLifecycleSubscriber->onWorkerRunning(new WorkerRunningEvent($worker)); } - public function testSubscriberLogOnWorkerRunningWithoutExecutedTask(): void - { - $list = $this->createMock(TaskListInterface::class); - $list->expects(self::once())->method('count')->willReturn(0); - - $worker = $this->createMock(WorkerInterface::class); - $worker->expects(self::once())->method('getFailedTasks')->willReturn($list); - $worker->expects(self::once())->method('getLastExecutedTask')->willReturn(null); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects(self::once())->method('info')->with(self::equalTo('The worker is currently running'), self::equalTo([ - 'failedTasks' => 0, - 'lastExecutedTask' => null, - 'idle' => false, - ])); - - $subscriber = new WorkerLifecycleSubscriber($logger); - $subscriber->onWorkerRunning(new WorkerRunningEvent($worker)); - } - public function testSubscriberLogOnWorkerRunning(): void { $task = $this->createMock(TaskInterface::class); @@ -199,25 +160,6 @@ public function testSubscriberLogOnWorkerStartedWithoutExecutedTask(): void $workerLifecycleSubscriber->onWorkerStarted(new WorkerStartedEvent($worker)); } - public function testSubscriberLogOnWorkerStartedWithoutExecutedTask(): void - { - $list = $this->createMock(TaskListInterface::class); - $list->expects(self::once())->method('count')->willReturn(0); - - $worker = $this->createMock(WorkerInterface::class); - $worker->expects(self::once())->method('getFailedTasks')->willReturn($list); - $worker->expects(self::once())->method('getLastExecutedTask')->willReturn(null); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects(self::once())->method('info')->with(self::equalTo('The worker has been started'), self::equalTo([ - 'failedTasks' => 0, - 'lastExecutedTask' => null, - ])); - - $subscriber = new WorkerLifecycleSubscriber($logger); - $subscriber->onWorkerStarted(new WorkerStartedEvent($worker)); - } - public function testSubscriberLogOnWorkerStarted(): void { $task = $this->createMock(TaskInterface::class); @@ -253,25 +195,6 @@ public function testSubscriberLogOnWorkerStoppedWithoutExecutedTask(): void $workerLifecycleSubscriber->onWorkerStopped(new WorkerStoppedEvent($worker)); } - public function testSubscriberLogOnWorkerStoppedWithoutExecutedTask(): void - { - $list = $this->createMock(TaskListInterface::class); - $list->expects(self::once())->method('count')->willReturn(0); - - $worker = $this->createMock(WorkerInterface::class); - $worker->expects(self::once())->method('getFailedTasks')->willReturn($list); - $worker->expects(self::once())->method('getLastExecutedTask')->willReturn(null); - - $logger = $this->createMock(LoggerInterface::class); - $logger->expects(self::once())->method('info')->with(self::equalTo('The worker has been stopped'), self::equalTo([ - 'failedTasks' => 0, - 'lastExecutedTask' => null, - ])); - - $subscriber = new WorkerLifecycleSubscriber($logger); - $subscriber->onWorkerStopped(new WorkerStoppedEvent($worker)); - } - public function testSubscriberLogOnWorkerStopped(): void { $task = $this->createMock(TaskInterface::class); diff --git a/tests/Serializer/TaskNormalizerTest.php b/tests/Serializer/TaskNormalizerTest.php index 961b7f06..df09afc6 100644 --- a/tests/Serializer/TaskNormalizerTest.php +++ b/tests/Serializer/TaskNormalizerTest.php @@ -952,48 +952,6 @@ public function testChainedTaskWithCommandTaskCanBeDenormalized(): void self::assertContains('-vvv', $fooThirdTask->getOptions()); } - public function testChainedTaskWithCommandTaskCanBeDenormalized(): void - { - $objectNormalizer = new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()])); - $notificationTaskBagNormalizer = new NotificationTaskBagNormalizer($objectNormalizer); - - $serializer = new Serializer([ - $notificationTaskBagNormalizer, - new TaskNormalizer(new DateTimeNormalizer(), new DateTimeZoneNormalizer(), new DateIntervalNormalizer(), $objectNormalizer, $notificationTaskBagNormalizer), - new DateTimeNormalizer(), - new DateIntervalNormalizer(), - new JsonSerializableNormalizer(), - $objectNormalizer, - ], [new JsonEncoder()]); - $objectNormalizer->setSerializer($serializer); - - $data = $serializer->serialize(new ChainedTask( - 'foo', - new ShellTask('bar', ['echo', 'Symfony']), - new CommandTask('foo_second', 'cache:clear', [], ['--no-warmup']), - new CommandTask('foo_third', 'cache:clear', [], ['--no-warmup']) - ), 'json'); - $task = $serializer->deserialize($data, TaskInterface::class, 'json'); - - self::assertInstanceOf(ChainedTask::class, $task); - self::assertNotEmpty($task->getTasks()); - self::assertCount(3, $task->getTasks()); - self::assertInstanceOf(ShellTask::class, $task->getTask(0)); - self::assertSame('bar', $task->getTask(0)->getName()); - - self::assertInstanceOf(CommandTask::class, $task->getTask(1)); - self::assertSame('foo_second', $task->getTask(1)->getName()); - self::assertSame('cache:clear', $task->getTask(1)->getCommand()); - self::assertEmpty($task->getTask(1)->getArguments()); - self::assertNotEmpty($task->getTask(1)->getOptions()); - - self::assertInstanceOf(CommandTask::class, $task->getTask(2)); - self::assertSame('foo_third', $task->getTask(2)->getName()); - self::assertSame('cache:clear', $task->getTask(2)->getCommand()); - self::assertEmpty($task->getTask(2)->getArguments()); - self::assertNotEmpty($task->getTask(2)->getOptions()); - } - public function testShellTaskWithBeforeSchedulingNotificationTaskBagCanBeNormalized(): void { $objectNormalizer = new ObjectNormalizer(null, null, null, new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()])); From febf992ec24ed10cbfa9151903e4a63c2ecdad41 Mon Sep 17 00:00:00 2001 From: Guillaume Loulier Date: Sat, 9 Apr 2022 12:45:11 +0200 Subject: [PATCH 3/3] refactor(core): improvements --- composer.json | 1 + .../ApiPlatform/Filter/SearchFilter.php | 11 ++- src/Bridge/ApiPlatform/Model/Task.php | 31 +++++++++ ...mDataProvider.php => TaskDataProvider.php} | 2 +- ...aProvider.php => TaskListDataProvider.php} | 2 +- .../SchedulerBundleExtension.php | 18 +++-- .../ApiPlatform/Filter/SearchFilterTest.php | 46 +++---------- ...viderTest.php => TaskDataProviderTest.php} | 31 ++++----- ...rTest.php => TaskListDataProviderTest.php} | 48 +++++-------- .../SchedulerBundleExtensionTest.php | 40 +++++------ tests/SchedulerTest.php | 69 ++++++++----------- 11 files changed, 142 insertions(+), 157 deletions(-) create mode 100644 src/Bridge/ApiPlatform/Model/Task.php rename src/Bridge/ApiPlatform/{ItemDataProvider.php => TaskDataProvider.php} (95%) rename src/Bridge/ApiPlatform/{CollectionDataProvider.php => TaskListDataProvider.php} (93%) rename tests/Bridge/ApiPlatform/{ItemDataProviderTest.php => TaskDataProviderTest.php} (60%) rename tests/Bridge/ApiPlatform/{CollectionDataProviderTest.php => TaskListDataProviderTest.php} (61%) diff --git a/composer.json b/composer.json index f38448c6..af693b94 100644 --- a/composer.json +++ b/composer.json @@ -122,6 +122,7 @@ "ext-pcntl": "*", "ext-pdo": "*", "ext-redis": "*", + "api-platform/core": "^2.6", "doctrine/dbal": "^3.1.4", "doctrine/orm": "^2.8", "friendsofphp/php-cs-fixer": "^3.5", diff --git a/src/Bridge/ApiPlatform/Filter/SearchFilter.php b/src/Bridge/ApiPlatform/Filter/SearchFilter.php index 467b9ddb..93d847e4 100644 --- a/src/Bridge/ApiPlatform/Filter/SearchFilter.php +++ b/src/Bridge/ApiPlatform/Filter/SearchFilter.php @@ -7,7 +7,6 @@ use ApiPlatform\Core\Api\FilterInterface; use SchedulerBundle\Task\TaskInterface; use SchedulerBundle\Task\TaskListInterface; -use function get_class; /** * @author Guillaume Loulier @@ -90,23 +89,23 @@ public function filter(TaskListInterface $list, array $filters = []): TaskListIn foreach ($filters as $filter => $value) { switch ($filter) { case 'expression': - $list = $list->filter(fn (TaskInterface $task): bool => $value === $task->getExpression()); + $list = $list->filter(static fn (TaskInterface $task): bool => $value === $task->getExpression()); break; case 'queued': - $list = $list->filter(fn (TaskInterface $task): bool => $task->isQueued()); + $list = $list->filter(static fn (TaskInterface $task): bool => $task->isQueued()); break; case 'state': - $list = $list->filter(fn (TaskInterface $task): bool => $value === $task->getState()); + $list = $list->filter(static fn (TaskInterface $task): bool => $value === $task->getState()); break; case 'timezone': - $list = $list->filter(function (TaskInterface $task) use ($value): bool { + $list = $list->filter(static function (TaskInterface $task) use ($value): bool { $timezone = $task->getTimezone(); return null !== $timezone && $value === $timezone->getName(); }); break; case 'type': - $list = $list->filter(fn (TaskInterface $task): bool => $value === get_class($task)); + $list = $list->filter(static fn (TaskInterface $task): bool => $value === $task::class); break; } } diff --git a/src/Bridge/ApiPlatform/Model/Task.php b/src/Bridge/ApiPlatform/Model/Task.php new file mode 100644 index 00000000..605c8176 --- /dev/null +++ b/src/Bridge/ApiPlatform/Model/Task.php @@ -0,0 +1,31 @@ +getName())); + } + + public function getWrappedTask(): TaskInterface + { + return $this->wrappedTask; + } +} diff --git a/src/Bridge/ApiPlatform/ItemDataProvider.php b/src/Bridge/ApiPlatform/TaskDataProvider.php similarity index 95% rename from src/Bridge/ApiPlatform/ItemDataProvider.php rename to src/Bridge/ApiPlatform/TaskDataProvider.php index f348d64d..04397a11 100644 --- a/src/Bridge/ApiPlatform/ItemDataProvider.php +++ b/src/Bridge/ApiPlatform/TaskDataProvider.php @@ -16,7 +16,7 @@ /** * @author Guillaume Loulier */ -final class ItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface +final class TaskDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface { private TransportInterface $transport; private LoggerInterface $logger; diff --git a/src/Bridge/ApiPlatform/CollectionDataProvider.php b/src/Bridge/ApiPlatform/TaskListDataProvider.php similarity index 93% rename from src/Bridge/ApiPlatform/CollectionDataProvider.php rename to src/Bridge/ApiPlatform/TaskListDataProvider.php index e8ff1c42..be241fe8 100644 --- a/src/Bridge/ApiPlatform/CollectionDataProvider.php +++ b/src/Bridge/ApiPlatform/TaskListDataProvider.php @@ -18,7 +18,7 @@ /** * @author Guillaume Loulier */ -final class CollectionDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface +final class TaskListDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface { private SearchFilter $searchFilter; private TransportInterface $transport; diff --git a/src/DependencyInjection/SchedulerBundleExtension.php b/src/DependencyInjection/SchedulerBundleExtension.php index 95c18fcc..eca89278 100644 --- a/src/DependencyInjection/SchedulerBundleExtension.php +++ b/src/DependencyInjection/SchedulerBundleExtension.php @@ -7,9 +7,9 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; use Redis; -use SchedulerBundle\Bridge\ApiPlatform\CollectionDataProvider; +use SchedulerBundle\Bridge\ApiPlatform\TaskListDataProvider; use SchedulerBundle\Bridge\ApiPlatform\Filter\SearchFilter; -use SchedulerBundle\Bridge\ApiPlatform\ItemDataProvider; +use SchedulerBundle\Bridge\ApiPlatform\TaskDataProvider; use SchedulerBundle\Bridge\Doctrine\SchemaListener\SchedulerTransportDoctrineSchemaSubscriber; use SchedulerBundle\Bridge\Doctrine\Transport\DoctrineTransportFactory; use SchedulerBundle\Bridge\Redis\Transport\RedisTransportFactory; @@ -1340,19 +1340,22 @@ private function registerApiPlatformBridge(ContainerBuilder $container, array $c return; } - $container->register(ItemDataProvider::class, ItemDataProvider::class) + $container->register(TaskDataProvider::class, TaskDataProvider::class) ->setArguments([ new Reference(TransportInterface::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), new Reference(LoggerInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE), ]) ->setPublic(false) ->addTag('api_platform.item_data_provider') + ->addTag('monolog.logger', [ + 'channel' => 'scheduler', + ]) ->addTag('container.preload', [ - 'class' => ItemDataProvider::class, + 'class' => TaskDataProvider::class, ]) ; - $container->register(CollectionDataProvider::class, CollectionDataProvider::class) + $container->register(TaskListDataProvider::class, TaskListDataProvider::class) ->setArguments([ new Reference(SearchFilter::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), new Reference(TransportInterface::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE), @@ -1360,8 +1363,11 @@ private function registerApiPlatformBridge(ContainerBuilder $container, array $c ]) ->setPublic(false) ->addTag('api_platform.collection_data_provider') + ->addTag('monolog.logger', [ + 'channel' => 'scheduler', + ]) ->addTag('container.preload', [ - 'class' => CollectionDataProvider::class, + 'class' => TaskListDataProvider::class, ]) ; diff --git a/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php b/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php index 69968211..68a2457b 100644 --- a/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php +++ b/tests/Bridge/ApiPlatform/Filter/SearchFilterTest.php @@ -109,14 +109,8 @@ public function testFilterCannotFilterEmptyList(): void public function testFilterCanFilterOnExpression(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::once())->method('getExpression')->willReturn('* * * * *'); - $task->expects(self::never())->method('isQueued'); - $task->expects(self::never())->method('getState'); - $task->expects(self::never())->method('getTimezone'); - $filter = new SearchFilter(); - $list = $filter->filter(new TaskList([$task]), [ + $list = $filter->filter(new TaskList([new NullTask('foo')]), [ 'expression' => '* * * * *', ]); @@ -126,14 +120,10 @@ public function testFilterCanFilterOnExpression(): void public function testFilterCanFilterOnQueuedTask(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::never())->method('getExpression'); - $task->expects(self::once())->method('isQueued')->willReturn(true); - $task->expects(self::never())->method('getState'); - $task->expects(self::never())->method('getTimezone'); - $filter = new SearchFilter(); - $list = $filter->filter(new TaskList([$task]), [ + $list = $filter->filter(new TaskList([new NullTask('foo', [ + 'queued' => true, + ])]), [ 'queued' => true, ]); @@ -143,15 +133,11 @@ public function testFilterCanFilterOnQueuedTask(): void public function testFilterCanFilterOnTaskState(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::never())->method('getExpression'); - $task->expects(self::never())->method('isQueued'); - $task->expects(self::once())->method('getState')->willReturn(TaskInterface::SUCCEED); - $task->expects(self::never())->method('getTimezone'); - $filter = new SearchFilter(); - $list = $filter->filter(new TaskList([$task]), [ - 'state' => TaskInterface::SUCCEED, + $list = $filter->filter(new TaskList([new NullTask('foo', [ + 'execution_state' => TaskInterface::SUCCEED, + ])]), [ + 'execution_state' => TaskInterface::SUCCEED, ]); self::assertNotEmpty($list); @@ -160,14 +146,10 @@ public function testFilterCanFilterOnTaskState(): void public function testFilterCanFilterOnTaskTimezone(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::never())->method('getExpression'); - $task->expects(self::never())->method('isQueued'); - $task->expects(self::never())->method('getState'); - $task->expects(self::once())->method('getTimezone')->willReturn(new DateTimeZone('UTC')); - $filter = new SearchFilter(); - $list = $filter->filter(new TaskList([$task]), [ + $list = $filter->filter(new TaskList([new NullTask('foo', [ + 'timezone' => new DateTimeZone('UTC'), + ])]), [ 'timezone' => 'UTC', ]); @@ -177,12 +159,6 @@ public function testFilterCanFilterOnTaskTimezone(): void public function testFilterCanFilterOnTaskType(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::never())->method('getExpression'); - $task->expects(self::never())->method('isQueued'); - $task->expects(self::never())->method('getState'); - $task->expects(self::never())->method('getTimezone'); - $filter = new SearchFilter(); $list = $filter->filter(new TaskList([new NullTask('foo')]), [ 'type' => NullTask::class, diff --git a/tests/Bridge/ApiPlatform/ItemDataProviderTest.php b/tests/Bridge/ApiPlatform/TaskDataProviderTest.php similarity index 60% rename from tests/Bridge/ApiPlatform/ItemDataProviderTest.php rename to tests/Bridge/ApiPlatform/TaskDataProviderTest.php index 450a3c37..4fd27041 100644 --- a/tests/Bridge/ApiPlatform/ItemDataProviderTest.php +++ b/tests/Bridge/ApiPlatform/TaskDataProviderTest.php @@ -7,22 +7,22 @@ use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; -use SchedulerBundle\Bridge\ApiPlatform\ItemDataProvider; +use SchedulerBundle\Bridge\ApiPlatform\TaskDataProvider; use SchedulerBundle\Exception\InvalidArgumentException; +use SchedulerBundle\SchedulePolicy\SchedulePolicyOrchestrator; +use SchedulerBundle\Task\NullTask; use SchedulerBundle\Task\TaskInterface; -use SchedulerBundle\Transport\TransportInterface; +use SchedulerBundle\Transport\InMemoryTransport; use stdClass; /** * @author Guillaume Loulier */ -final class ItemDataProviderTest extends TestCase +final class TaskDataProviderTest extends TestCase { public function testProviderSupport(): void { - $transport = $this->createMock(TransportInterface::class); - - $provider = new ItemDataProvider($transport); + $provider = new TaskDataProvider(new InMemoryTransport([], new SchedulePolicyOrchestrator([]))); self::assertInstanceOf(RestrictedDataProviderInterface::class, $provider); self::assertFalse($provider->supports(stdClass::class)); @@ -31,37 +31,32 @@ public function testProviderSupport(): void public function testProviderCannotReturnUndefinedTask(): void { - $transport = $this->createMock(TransportInterface::class); - $transport->expects(self::once())->method('get')->with(self::equalTo('foo')) - ->willThrowException(new InvalidArgumentException('The task "foo" does not exist')) - ; - $logger = $this->createMock(LoggerInterface::class); $logger->expects(self::once())->method('critical') ->with(self::equalTo('The task "foo" cannot be found'), self::equalTo([ - 'error' => 'The task "foo" does not exist', + 'error' => 'The task "foo" does not exist or is invalid', ])) ; - $provider = new ItemDataProvider($transport, $logger); + $provider = new TaskDataProvider(new InMemoryTransport([], new SchedulePolicyOrchestrator([])), $logger); self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('The task "foo" does not exist'); + self::expectExceptionMessage('The task "foo" does not exist or is invalid'); self::expectExceptionCode(0); $provider->getItem(TaskInterface::class, 'foo'); } public function testProviderCanReturnTask(): void { - $task = $this->createMock(TaskInterface::class); + $task = new NullTask('foo'); - $transport = $this->createMock(TransportInterface::class); - $transport->expects(self::once())->method('get')->with(self::equalTo('foo'))->willReturn($task); + $transport = new InMemoryTransport([], new SchedulePolicyOrchestrator([])); + $transport->create($task); $logger = $this->createMock(LoggerInterface::class); $logger->expects(self::never())->method('critical'); - $provider = new ItemDataProvider($transport, $logger); + $provider = new TaskDataProvider($transport, $logger); self::assertSame($task, $provider->getItem(TaskInterface::class, 'foo')); } diff --git a/tests/Bridge/ApiPlatform/CollectionDataProviderTest.php b/tests/Bridge/ApiPlatform/TaskListDataProviderTest.php similarity index 61% rename from tests/Bridge/ApiPlatform/CollectionDataProviderTest.php rename to tests/Bridge/ApiPlatform/TaskListDataProviderTest.php index 99216ba5..b02dd604 100644 --- a/tests/Bridge/ApiPlatform/CollectionDataProviderTest.php +++ b/tests/Bridge/ApiPlatform/TaskListDataProviderTest.php @@ -7,24 +7,26 @@ use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; -use SchedulerBundle\Bridge\ApiPlatform\CollectionDataProvider; +use SchedulerBundle\Bridge\ApiPlatform\TaskListDataProvider; use SchedulerBundle\Bridge\ApiPlatform\Filter\SearchFilter; use SchedulerBundle\Exception\RuntimeException; +use SchedulerBundle\SchedulePolicy\FirstInFirstOutPolicy; +use SchedulerBundle\SchedulePolicy\SchedulePolicyOrchestrator; use SchedulerBundle\Task\TaskInterface; -use SchedulerBundle\Task\TaskListInterface; +use SchedulerBundle\Transport\InMemoryTransport; use SchedulerBundle\Transport\TransportInterface; use stdClass; /** * @author Guillaume Loulier */ -final class CollectionDataProviderTest extends TestCase +final class TaskListDataProviderTest extends TestCase { public function testProviderSupport(): void { - $transport = $this->createMock(TransportInterface::class); - - $provider = new CollectionDataProvider(new SearchFilter(), $transport); + $provider = new TaskListDataProvider(new SearchFilter(), new InMemoryTransport([], new SchedulePolicyOrchestrator([ + new FirstInFirstOutPolicy(), + ]))); self::assertInstanceOf(RestrictedDataProviderInterface::class, $provider); self::assertFalse($provider->supports(stdClass::class)); @@ -48,7 +50,7 @@ public function testProviderCannotReturnListWithError(): void ) ; - $provider = new CollectionDataProvider(new SearchFilter(), $transport, $logger); + $provider = new TaskListDataProvider(new SearchFilter(), $transport, $logger); self::expectException(RuntimeException::class); self::expectExceptionMessage('Random error'); @@ -58,32 +60,24 @@ public function testProviderCannotReturnListWithError(): void public function testProviderCanReturnTaskList(): void { - $list = $this->createMock(TaskListInterface::class); - $list->expects(self::never())->method('filter'); - - $transport = $this->createMock(TransportInterface::class); - $transport->expects(self::once())->method('list')->willReturn($list); - $logger = $this->createMock(LoggerInterface::class); $logger->expects(self::never())->method('critical'); - $provider = new CollectionDataProvider(new SearchFilter(), $transport, $logger); + $provider = new TaskListDataProvider(new SearchFilter(), new InMemoryTransport([], new SchedulePolicyOrchestrator([ + new FirstInFirstOutPolicy(), + ])), $logger); - self::assertSame($list, $provider->getCollection(TaskInterface::class)); + self::assertCount(0, $provider->getCollection(TaskInterface::class)); } public function testProviderCannotReturnFilteredTaskListWithoutFilters(): void { - $list = $this->createMock(TaskListInterface::class); - $list->expects(self::never())->method('filter')->willReturnSelf(); - - $transport = $this->createMock(TransportInterface::class); - $transport->expects(self::once())->method('list')->willReturn($list); - $logger = $this->createMock(LoggerInterface::class); $logger->expects(self::never())->method('critical'); - $provider = new CollectionDataProvider(new SearchFilter(), $transport, $logger); + $provider = new TaskListDataProvider(new SearchFilter(), new InMemoryTransport([], new SchedulePolicyOrchestrator([ + new FirstInFirstOutPolicy(), + ])), $logger); $provider->getCollection(TaskInterface::class, 'GET', [ 'filters' => [], ]); @@ -91,16 +85,12 @@ public function testProviderCannotReturnFilteredTaskListWithoutFilters(): void public function testProviderCanReturnFilteredTaskList(): void { - $list = $this->createMock(TaskListInterface::class); - $list->expects(self::once())->method('filter')->willReturnSelf(); - - $transport = $this->createMock(TransportInterface::class); - $transport->expects(self::once())->method('list')->willReturn($list); - $logger = $this->createMock(LoggerInterface::class); $logger->expects(self::never())->method('critical'); - $provider = new CollectionDataProvider(new SearchFilter(), $transport, $logger); + $provider = new TaskListDataProvider(new SearchFilter(), new InMemoryTransport([], new SchedulePolicyOrchestrator([ + new FirstInFirstOutPolicy(), + ])), $logger); $provider->getCollection(TaskInterface::class, 'GET', [ 'filters' => [ 'expression' => '* * * * *', diff --git a/tests/DependencyInjection/SchedulerBundleExtensionTest.php b/tests/DependencyInjection/SchedulerBundleExtensionTest.php index 4f9f4cf0..82efcd22 100644 --- a/tests/DependencyInjection/SchedulerBundleExtensionTest.php +++ b/tests/DependencyInjection/SchedulerBundleExtensionTest.php @@ -9,9 +9,9 @@ use PHPUnit\Framework\TestCase; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; -use SchedulerBundle\Bridge\ApiPlatform\CollectionDataProvider; +use SchedulerBundle\Bridge\ApiPlatform\TaskListDataProvider; use SchedulerBundle\Bridge\ApiPlatform\Filter\SearchFilter; -use SchedulerBundle\Bridge\ApiPlatform\ItemDataProvider; +use SchedulerBundle\Bridge\ApiPlatform\TaskDataProvider; use SchedulerBundle\Bridge\Doctrine\SchemaListener\SchedulerTransportDoctrineSchemaSubscriber; use SchedulerBundle\Bridge\Doctrine\Transport\DoctrineTransportFactory; use SchedulerBundle\Bridge\Redis\Transport\RedisTransportFactory; @@ -1647,24 +1647,24 @@ public function testApiPlatformBridgeCanBeConfigured(): void 'api_platform' => true, ]); - self::assertTrue($container->hasDefinition(ItemDataProvider::class)); - self::assertFalse($container->getDefinition(ItemDataProvider::class)->isPublic()); - self::assertCount(2, $container->getDefinition(ItemDataProvider::class)->getArguments()); - self::assertInstanceOf(Reference::class, $container->getDefinition(ItemDataProvider::class)->getArgument(0)); - self::assertInstanceOf(Reference::class, $container->getDefinition(ItemDataProvider::class)->getArgument(1)); - self::assertTrue($container->getDefinition(ItemDataProvider::class)->hasTag('api_platform.item_data_provider')); - self::assertTrue($container->getDefinition(ItemDataProvider::class)->hasTag('container.preload')); - self::assertSame(ItemDataProvider::class, $container->getDefinition(ItemDataProvider::class)->getTag('container.preload')[0]['class']); - - self::assertTrue($container->hasDefinition(CollectionDataProvider::class)); - self::assertFalse($container->getDefinition(CollectionDataProvider::class)->isPublic()); - self::assertCount(3, $container->getDefinition(CollectionDataProvider::class)->getArguments()); - self::assertInstanceOf(Reference::class, $container->getDefinition(CollectionDataProvider::class)->getArgument(0)); - self::assertInstanceOf(Reference::class, $container->getDefinition(CollectionDataProvider::class)->getArgument(1)); - self::assertInstanceOf(Reference::class, $container->getDefinition(CollectionDataProvider::class)->getArgument(2)); - self::assertTrue($container->getDefinition(CollectionDataProvider::class)->hasTag('api_platform.collection_data_provider')); - self::assertTrue($container->getDefinition(CollectionDataProvider::class)->hasTag('container.preload')); - self::assertSame(CollectionDataProvider::class, $container->getDefinition(CollectionDataProvider::class)->getTag('container.preload')[0]['class']); + self::assertTrue($container->hasDefinition(TaskDataProvider::class)); + self::assertFalse($container->getDefinition(TaskDataProvider::class)->isPublic()); + self::assertCount(2, $container->getDefinition(TaskDataProvider::class)->getArguments()); + self::assertInstanceOf(Reference::class, $container->getDefinition(TaskDataProvider::class)->getArgument(0)); + self::assertInstanceOf(Reference::class, $container->getDefinition(TaskDataProvider::class)->getArgument(1)); + self::assertTrue($container->getDefinition(TaskDataProvider::class)->hasTag('api_platform.item_data_provider')); + self::assertTrue($container->getDefinition(TaskDataProvider::class)->hasTag('container.preload')); + self::assertSame(TaskDataProvider::class, $container->getDefinition(TaskDataProvider::class)->getTag('container.preload')[0]['class']); + + self::assertTrue($container->hasDefinition(TaskListDataProvider::class)); + self::assertFalse($container->getDefinition(TaskListDataProvider::class)->isPublic()); + self::assertCount(3, $container->getDefinition(TaskListDataProvider::class)->getArguments()); + self::assertInstanceOf(Reference::class, $container->getDefinition(TaskListDataProvider::class)->getArgument(0)); + self::assertInstanceOf(Reference::class, $container->getDefinition(TaskListDataProvider::class)->getArgument(1)); + self::assertInstanceOf(Reference::class, $container->getDefinition(TaskListDataProvider::class)->getArgument(2)); + self::assertTrue($container->getDefinition(TaskListDataProvider::class)->hasTag('api_platform.collection_data_provider')); + self::assertTrue($container->getDefinition(TaskListDataProvider::class)->hasTag('container.preload')); + self::assertSame(TaskListDataProvider::class, $container->getDefinition(TaskListDataProvider::class)->getTag('container.preload')[0]['class']); self::assertTrue($container->hasDefinition(SearchFilter::class)); self::assertFalse($container->getDefinition(SearchFilter::class)->isPublic()); diff --git a/tests/SchedulerTest.php b/tests/SchedulerTest.php index 484bcd37..bf6aa06e 100644 --- a/tests/SchedulerTest.php +++ b/tests/SchedulerTest.php @@ -85,18 +85,14 @@ final class SchedulerTest extends TestCase */ public function testSchedulerCanScheduleTasks(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::once())->method('setScheduledAt'); - $task->expects(self::once())->method('setTimezone'); - $task->expects(self::never())->method('isQueued'); - $scheduler = new Scheduler('UTC', new InMemoryTransport([ 'execution_mode' => 'first_in_first_out', ], new SchedulePolicyOrchestrator([ new FirstInFirstOutPolicy(), ])), new SchedulerMiddlewareStack(), new EventDispatcher()); - $scheduler->schedule($task); + $scheduler->schedule(new NullTask('foo')); + self::assertCount(1, $scheduler->getTasks()); } /** @@ -105,11 +101,9 @@ public function testSchedulerCanScheduleTasks(): void */ public function testSchedulerCanScheduleTasksWithCustomTimezone(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::once())->method('setScheduledAt'); - $task->expects(self::once())->method('setTimezone')->with(new DateTimeZone('Europe/Paris')); - $task->expects(self::once())->method('getTimezone')->willReturn(new DateTimeZone('Europe/Paris')); - $task->expects(self::never())->method('isQueued'); + $task = new NullTask('foo', [ + 'timezone' => new DateTimeZone('Europe/Paris'), + ]); $scheduler = new Scheduler('UTC', new InMemoryTransport([ 'execution_mode' => 'first_in_first_out', @@ -118,6 +112,10 @@ public function testSchedulerCanScheduleTasksWithCustomTimezone(): void ])), new SchedulerMiddlewareStack(), new EventDispatcher()); $scheduler->schedule($task); + self::assertCount(1, $scheduler->getTasks()); + + $task = $scheduler->getTasks()->last(); + self::assertSame('Europe/Paris', $task->getTimezone()->getName()); } /** @@ -126,12 +124,6 @@ public function testSchedulerCanScheduleTasksWithCustomTimezone(): void */ public function testSchedulerCannotScheduleTasksWithErroredBeforeCallback(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::never())->method('setScheduledAt'); - $task->expects(self::never())->method('setTimezone'); - $task->expects(self::never())->method('isQueued'); - $task->expects(self::once())->method('getBeforeScheduling')->willReturn(fn (): bool => false); - $scheduler = new Scheduler('UTC', new InMemoryTransport([ 'execution_mode' => 'first_in_first_out', ], new SchedulePolicyOrchestrator([ @@ -143,7 +135,9 @@ public function testSchedulerCannotScheduleTasksWithErroredBeforeCallback(): voi self::expectException(RuntimeException::class); self::expectExceptionMessage('The task cannot be scheduled'); self::expectExceptionCode(0); - $scheduler->schedule($task); + $scheduler->schedule(new NullTask('foo', [ + 'before_scheduling' => static fn (): bool => false, + ])); } /** @@ -152,12 +146,6 @@ public function testSchedulerCannotScheduleTasksWithErroredBeforeCallback(): voi */ public function testSchedulerCanScheduleTasksWithBeforeCallback(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::once())->method('setScheduledAt'); - $task->expects(self::once())->method('setTimezone'); - $task->expects(self::never())->method('isQueued'); - $task->expects(self::once())->method('getBeforeScheduling')->willReturn(fn (): int => 1 + 1); - $scheduler = new Scheduler('UTC', new InMemoryTransport([ 'execution_mode' => 'first_in_first_out', ], new SchedulePolicyOrchestrator([ @@ -166,7 +154,10 @@ public function testSchedulerCanScheduleTasksWithBeforeCallback(): void new TaskCallbackMiddleware(), ]), new EventDispatcher()); - $scheduler->schedule($task); + $scheduler->schedule(new NullTask('foo', [ + 'before_scheduling' => static fn (): int => 1 + 1, + ])); + self::assertCount(1, $scheduler->getTasks()); } /** @@ -345,14 +336,6 @@ public function testSchedulerCannotScheduleTasksWithErroredAfterCallback(): void */ public function testSchedulerCanScheduleTasksWithAfterCallback(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::exactly(2))->method('getName')->willReturn('foo'); - $task->expects(self::once())->method('setScheduledAt'); - $task->expects(self::once())->method('setTimezone'); - $task->expects(self::never())->method('isQueued'); - $task->expects(self::once())->method('getBeforeScheduling')->willReturn(null); - $task->expects(self::once())->method('getAfterScheduling')->willReturn(fn (): bool => true); - $scheduler = new Scheduler('UTC', new InMemoryTransport([ 'execution_mode' => 'first_in_first_out', ], new SchedulePolicyOrchestrator([ @@ -361,19 +344,21 @@ public function testSchedulerCanScheduleTasksWithAfterCallback(): void new TaskCallbackMiddleware(), ]), new EventDispatcher()); - $scheduler->schedule($task); + $scheduler->schedule(new NullTask('foo', [ + 'after_scheduling' => static fn (): bool => true, + ])); + self::assertCount(1, $scheduler->getTasks()); } /** * @throws Throwable {@see Scheduler::__construct()} * @throws Throwable {@see Scheduler::getSynchronizedCurrentDate()} */ - public function testSchedulerCanScheduleTasksWithMessageBus(): void + public function testSchedulerCanScheduleQueuedTasksWithMessageBus(): void { - $task = $this->createMock(TaskInterface::class); - $task->expects(self::once())->method('setScheduledAt'); - $task->expects(self::once())->method('setTimezone'); - $task->expects(self::once())->method('isQueued')->willReturn(true); + $task = new NullTask('foo', [ + 'queued' => true, + ]); $bus = $this->createMock(MessageBusInterface::class); $bus->expects(self::once())->method('dispatch')->with(new TaskToExecuteMessage($task))->willReturn(new Envelope(new stdClass())); @@ -383,7 +368,9 @@ public function testSchedulerCanScheduleTasksWithMessageBus(): void ], new SchedulePolicyOrchestrator([ new FirstInFirstOutPolicy(), ])), new SchedulerMiddlewareStack(), new EventDispatcher(), $bus); + $scheduler->schedule($task); + self::assertCount(0, $scheduler->getTasks()); } /** @@ -1628,7 +1615,7 @@ public function testSchedulerCannotPreemptEmptyDueTasks(): void new FirstInFirstOutPolicy(), ])), new SchedulerMiddlewareStack(), new EventDispatcher()); - $scheduler->preempt('foo', fn (TaskInterface $task): bool => $task->getName() === 'bar'); + $scheduler->preempt('foo', static fn (TaskInterface $task): bool => $task->getName() === 'bar'); self::assertNotSame(TaskInterface::READY_TO_EXECUTE, $task->getState()); } @@ -1646,7 +1633,7 @@ public function testSchedulerCannotPreemptEmptyToPreemptTasks(): void ])), new SchedulerMiddlewareStack(), $eventDispatcher); $scheduler->schedule(new NullTask('foo')); - $scheduler->preempt('foo', fn (TaskInterface $task): bool => $task->getName() === 'bar'); + $scheduler->preempt('foo', static fn (TaskInterface $task): bool => $task->getName() === 'bar'); } /**