From fa2ccc938ec68ba415a528c5a7048d909c343b54 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 18 Jan 2024 21:55:27 +0100 Subject: [PATCH 1/6] Drop support for Laravel 6, 7, 8 and 9 --- .github/workflows/run-tests.yml | 11 ------- README.md | 2 +- composer.json | 18 +----------- src/CloudTasksJob.php | 11 ------- src/CloudTasksQueue.php | 33 --------------------- src/Events/JobReleasedAfterException.php | 37 ------------------------ src/TaskHandler.php | 6 ++-- tests/CloudTasksDashboardTest.php | 16 ++++------ tests/QueueTest.php | 28 +++++------------- tests/TaskHandlerTest.php | 34 +++++++--------------- tests/TestCase.php | 12 +------- 11 files changed, 29 insertions(+), 179 deletions(-) delete mode 100644 src/Events/JobReleasedAfterException.php diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 40f2d20..d3ab8aa 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -67,17 +67,6 @@ jobs: - { queue: 'github-actions-laravel10-php83', laravel: '10.*', php: '8.3', 'testbench': '8.*'} - { queue: 'github-actions-laravel10-php82', laravel: '10.*', php: '8.2', 'testbench': '8.*'} - { queue: 'github-actions-laravel10-php81', laravel: '10.*', php: '8.1', 'testbench': '8.*'} - - { queue: 'github-actions-laravel9-php83', laravel: '9.*', php: '8.3', 'testbench': '7.*'} - - { queue: 'github-actions-laravel9-php82', laravel: '9.*', php: '8.2', 'testbench': '7.*'} - - { queue: 'github-actions-laravel9-php81', laravel: '9.*', php: '8.1', 'testbench': '7.*'} - - { queue: 'github-actions-laravel9-php80', laravel: '9.*', php: '8.0', 'testbench': '7.*'} - - { queue: 'github-actions-laravel8-php81', laravel: '8.*', php: '8.1', 'testbench': '6.*'} - - { queue: 'github-actions-laravel8-php80', laravel: '8.*', php: '8.0', 'testbench': '6.*'} - - { queue: 'github-actions-laravel8-php74', laravel: '8.*', php: '7.4', 'testbench': '6.*'} - - { queue: 'github-actions-laravel7-php80', laravel: '7.*', php: '8.0', 'testbench': '5.*' } - - { queue: 'github-actions-laravel7-php74', laravel: '7.*', php: '7.4', 'testbench': '5.*' } - - { queue: 'github-actions-laravel6-php80', laravel: '6.*', php: '8.0', 'testbench': '4.*' } - - { queue: 'github-actions-laravel6-php74', laravel: '6.*', php: '7.4', 'testbench': '4.*' } name: PHP ${{ matrix.payload.php }} - Laravel ${{ matrix.payload.laravel }} - DB ${{ matrix.db }} diff --git a/README.md b/README.md index 23b072e..24bf4e8 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This package allows Google Cloud Tasks to be used as the queue driver.
- This package requires Laravel 6 or higher and supports MySQL 8 and PostgreSQL 14. Might support older database versions too, but package hasn't been tested for it. + This package requires Laravel 10 or higher and supports MySQL 8 and PostgreSQL 15. Might support older database versions too, but package hasn't been tested for it. Please check the [Laravel support policy](https://laravel.com/docs/master/releases#support-policy) table for supported Laravel and PHP versions. diff --git a/composer.json b/composer.json index cf4f0ba..f4d259e 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "thecodingmachine/safe": "^1.0|^2.0" }, "require-dev": { - "orchestra/testbench": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "orchestra/testbench": "^8.0", "nunomaduro/larastan": "^1.0 || ^2.0", "thecodingmachine/phpstan-safe-rule": "^1.2", "laravel/legacy-factories": "^1.3" @@ -45,21 +45,5 @@ "composer require laravel/framework:10.* orchestra/testbench:8.* --no-interaction --no-update", "composer update --prefer-stable --prefer-dist --no-interaction" ], - "l9": [ - "composer require laravel/framework:9.* orchestra/testbench:7.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" - ], - "l8": [ - "composer require laravel/framework:8.* orchestra/testbench:6.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" - ], - "l7": [ - "composer require laravel/framework:7.* orchestra/testbench:5.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" - ], - "l6": [ - "composer require laravel/framework:6.* orchestra/testbench:4.* --no-interaction --no-update", - "composer update --prefer-stable --prefer-dist --no-interaction" - ] } } diff --git a/src/CloudTasksJob.php b/src/CloudTasksJob.php index 9e804cf..3cd63bc 100644 --- a/src/CloudTasksJob.php +++ b/src/CloudTasksJob.php @@ -6,7 +6,6 @@ use Illuminate\Contracts\Queue\Job as JobContract; use Illuminate\Queue\Jobs\Job as LaravelJob; use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleased; -use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleasedAfterException; use function Safe\json_encode; class CloudTasksJob extends LaravelJob implements JobContract @@ -137,16 +136,6 @@ public function release($delay = 0) $properties = TaskHandler::getCommandProperties($this->job['data']['command']); $connection = $properties['connection'] ?? config('queue.default'); - // The package uses the JobReleasedAfterException provided by Laravel to grab - // the payload of the released job in tests to easily run and test a released - // job. Because the event is only accessible in Laravel 9.x, we create an - // identical event to hook into for Laravel versions older than 9.x - if (version_compare(app()->version(), '9.0.0', '<')) { - if (data_get($this->job, 'internal.errored')) { - app('events')->dispatch(new JobReleasedAfterException($connection, $this)); - } - } - if (! data_get($this->job, 'internal.errored')) { app('events')->dispatch(new JobReleased($connection, $this, $delay)); } diff --git a/src/CloudTasksQueue.php b/src/CloudTasksQueue.php index b07242b..4828b6f 100644 --- a/src/CloudTasksQueue.php +++ b/src/CloudTasksQueue.php @@ -45,25 +45,6 @@ public function size($queue = null) return 0; } - /** - * Fallback method for Laravel 6x and 7x - * - * @param \Closure|string|object $job - * @param string $payload - * @param string $queue - * @param \DateTimeInterface|\DateInterval|int|null $delay - * @param callable $callback - * @return mixed - */ - protected function enqueueUsing($job, $payload, $queue, $delay, $callback) - { - if (method_exists(parent::class, 'enqueueUsing')) { - return parent::enqueueUsing($job, $payload, $queue, $delay, $callback); - } - - return $callback($payload, $queue, $delay); - } - /** * Push a new job onto the queue. * @@ -142,11 +123,6 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0) $payload = json_decode($payload, true); - // Laravel 7+ jobs have a uuid, but Laravel 6 doesn't have it. - // Since we are using and expecting the uuid in some places - // we will add it manually here if it's not present yet. - $payload = $this->withUuid($payload); - // Since 3.x tasks are released back onto the queue after an exception has // been thrown. This means we lose the native [X-CloudTasks-TaskRetryCount] header // value and need to manually set and update the number of times a task has been attempted. @@ -183,15 +159,6 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0) return $payload['uuid']; } - private function withUuid(array $payload): array - { - if (!isset($payload['uuid'])) { - $payload['uuid'] = (string) Str::uuid(); - } - - return $payload; - } - private function taskName(string $queueName, array $payload): string { $displayName = $this->sanitizeTaskName($payload['displayName']); diff --git a/src/Events/JobReleasedAfterException.php b/src/Events/JobReleasedAfterException.php deleted file mode 100644 index 603fbe3..0000000 --- a/src/Events/JobReleasedAfterException.php +++ /dev/null @@ -1,37 +0,0 @@ -job = $job; - $this->connectionName = $connectionName; - } -} diff --git a/src/TaskHandler.php b/src/TaskHandler.php index 434e8f8..9b38442 100644 --- a/src/TaskHandler.php +++ b/src/TaskHandler.php @@ -187,9 +187,9 @@ public function getWorkerOptions(): WorkerOptions { $options = new WorkerOptions(); - $prop = version_compare(app()->version(), '8.0.0', '<') ? 'delay' : 'backoff'; - - $options->$prop = $this->config['backoff'] ?? 0; + if (isset($this->config['backoff'])) { + $options->backoff = $this->config['backoff']; + } return $options; } diff --git a/tests/CloudTasksDashboardTest.php b/tests/CloudTasksDashboardTest.php index 6cb7b99..e01f863 100644 --- a/tests/CloudTasksDashboardTest.php +++ b/tests/CloudTasksDashboardTest.php @@ -487,17 +487,11 @@ public function test_publish() // Act & Assert $expectedPublishBase = dirname(__DIR__); - if (version_compare(app()->version(), '9.0.0', '>=')) { - $this->artisan('vendor:publish --tag=cloud-tasks --force') - ->expectsOutputToContain('Publishing [cloud-tasks] assets.') - ->expectsOutputToContain('Copying file [' . $expectedPublishBase . '/config/cloud-tasks.php] to [config/cloud-tasks.php]') - ->expectsOutputToContain('Copying directory [' . $expectedPublishBase . '/dashboard/dist] to [public/vendor/cloud-tasks]'); - } else { - $this->artisan('vendor:publish --tag=cloud-tasks --force') - ->expectsOutput('Copied File [' . $expectedPublishBase . '/config/cloud-tasks.php] To [/config/cloud-tasks.php]') - ->expectsOutput('Copied Directory [' . $expectedPublishBase . '/dashboard/dist] To [/public/vendor/cloud-tasks]') - ->expectsOutput('Publishing complete.'); - } + $this->artisan('vendor:publish --tag=cloud-tasks --force') + ->expectsOutputToContain('Publishing [cloud-tasks] assets.') + ->expectsOutputToContain('Copying file [' . $expectedPublishBase . '/config/cloud-tasks.php] to [config/cloud-tasks.php]') + ->expectsOutputToContain('Copying directory [' . $expectedPublishBase . '/dashboard/dist] to [public/vendor/cloud-tasks]'); + } /** diff --git a/tests/QueueTest.php b/tests/QueueTest.php index 5338afe..c094f0a 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -10,6 +10,7 @@ use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessing; use Illuminate\Queue\Events\JobQueued; +use Illuminate\Queue\Events\JobReleasedAfterException; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; @@ -176,10 +177,6 @@ public function it_posts_the_task_the_correct_queue() */ public function it_can_dispatch_after_commit_inline() { - if (version_compare(app()->version(), '8.0.0', '<')) { - $this->markTestSkipped('Not supported by Laravel 7.x and below.'); - } - // Arrange CloudTasksApi::fake(); Event::fake(); @@ -202,10 +199,6 @@ public function it_can_dispatch_after_commit_inline() */ public function it_can_dispatch_after_commit_through_config() { - if (version_compare(app()->version(), '8.0.0', '<')) { - $this->markTestSkipped('Not supported by Laravel 7.x and below.'); - } - // Arrange CloudTasksApi::fake(); Event::fake(); @@ -233,7 +226,7 @@ public function jobs_can_be_released() CloudTasksApi::fake(); OpenIdVerificator::fake(); Event::fake([ - $this->getJobReleasedAfterExceptionEvent(), + JobReleasedAfterException::class, JobReleased::class, ]); @@ -241,7 +234,7 @@ public function jobs_can_be_released() $this->dispatch(new JobThatWillBeReleased())->run(); // Assert - Event::assertNotDispatched($this->getJobReleasedAfterExceptionEvent()); + Event::assertNotDispatched(JobReleasedAfterException::class); CloudTasksApi::assertDeletedTaskCount(0); // it returned 200 OK so we dont delete it, but Google does $releasedJob = null; Event::assertDispatched(JobReleased::class, function (JobReleased $event) use (&$releasedJob) { @@ -275,7 +268,7 @@ public function jobs_can_be_released_with_a_delay() CloudTasksApi::fake(); OpenIdVerificator::fake(); Event::fake([ - $this->getJobReleasedAfterExceptionEvent(), + JobReleasedAfterException::class, JobReleased::class, ]); Carbon::setTestNow(now()->addDay()); @@ -302,7 +295,7 @@ public function test_default_backoff() // Arrange CloudTasksApi::fake(); OpenIdVerificator::fake(); - Event::fake($this->getJobReleasedAfterExceptionEvent()); + Event::fake(JobReleasedAfterException::class); // Act $this->dispatch(new FailingJob())->run(); @@ -321,7 +314,7 @@ public function test_backoff_from_queue_config() $this->setConfigValue('backoff', 123); CloudTasksApi::fake(); OpenIdVerificator::fake(); - Event::fake($this->getJobReleasedAfterExceptionEvent()); + Event::fake(JobReleasedAfterException::class); // Act $this->dispatch(new FailingJob())->run(); @@ -340,12 +333,11 @@ public function test_backoff_from_job() Carbon::setTestNow(now()->addDay()); CloudTasksApi::fake(); OpenIdVerificator::fake(); - Event::fake($this->getJobReleasedAfterExceptionEvent()); + Event::fake(JobReleasedAfterException::class); // Act $failingJob = new FailingJob(); - $prop = version_compare(app()->version(), '8.0.0', '<') ? 'delay' : 'backoff'; - $failingJob->$prop = 123; + $failingJob->backoff = 123; $this->dispatch($failingJob)->run(); // Assert @@ -358,10 +350,6 @@ public function test_backoff_from_job() /** @test */ public function test_exponential_backoff_from_job_method() { - if (version_compare(app()->version(), '8.0.0', '<')) { - $this->markTestSkipped('Not supported by Laravel 7.x and below.'); - } - // Arrange Carbon::setTestNow(now()->addDay()); CloudTasksApi::fake(); diff --git a/tests/TaskHandlerTest.php b/tests/TaskHandlerTest.php index b100da6..31a3b20 100644 --- a/tests/TaskHandlerTest.php +++ b/tests/TaskHandlerTest.php @@ -8,6 +8,7 @@ use Google\Protobuf\Duration; use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessing; +use Illuminate\Queue\Events\JobReleasedAfterException; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Log; @@ -356,9 +357,6 @@ public function test_unlimited_max_attempts() */ public function test_max_attempts_in_combination_with_retry_until() { - // Laravel 5, 6, 7: check both max_attempts and retry_until before failing a job. - // Laravel 8+: if retry_until, only check that - // Arrange OpenIdVerificator::fake(); CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( @@ -382,15 +380,7 @@ public function test_max_attempts_in_combination_with_retry_until() $releasedJob->run(); # Max attempts was reached - # Laravel 5, 6, 7: fail because max attempts was reached - # Laravel 8+: don't fail because retryUntil has not yet passed. - - if (version_compare(app()->version(), '8.0.0', '<')) { - $this->assertEquals('failed', $task->fresh()->status); - return; - } else { - $this->assertEquals('error', $task->fresh()->status); - } + $this->assertEquals('error', $task->fresh()->status); CloudTasksApi::shouldReceive('getRetryUntilTimestamp')->andReturn(time() - 1); $releasedJob->run(); @@ -403,10 +393,6 @@ public function test_max_attempts_in_combination_with_retry_until() */ public function it_can_handle_encrypted_jobs() { - if (version_compare(app()->version(), '8.0.0', '<')) { - $this->markTestSkipped('Not supported by Laravel 7.x and below.'); - } - // Arrange OpenIdVerificator::fake(); Log::swap(new LogFake()); @@ -434,7 +420,7 @@ public function failing_jobs_are_released() CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( (new RetryConfig())->setMaxAttempts(3) ); - Event::fake($this->getJobReleasedAfterExceptionEvent()); + Event::fake(JobReleasedAfterException::class); // Act $job = $this->dispatch(new FailingJob()); @@ -448,7 +434,7 @@ public function failing_jobs_are_released() CloudTasksApi::assertDeletedTaskCount(1); CloudTasksApi::assertCreatedTaskCount(2); CloudTasksApi::assertTaskDeleted($job->task->getName()); - Event::assertDispatched($this->getJobReleasedAfterExceptionEvent(), function ($event) { + Event::assertDispatched(JobReleasedAfterException::class, function ($event) { return $event->job->attempts() === 1; }); } @@ -460,21 +446,21 @@ public function attempts_are_tracked_internally() { // Arrange OpenIdVerificator::fake(); - Event::fake($this->getJobReleasedAfterExceptionEvent()); + Event::fake(JobReleasedAfterException::class); // Act & Assert $job = $this->dispatch(new FailingJob()); $job->run(); $releasedJob = null; - Event::assertDispatched($this->getJobReleasedAfterExceptionEvent(), function ($event) use (&$releasedJob) { + Event::assertDispatched(JobReleasedAfterException::class, function ($event) use (&$releasedJob) { $releasedJob = $event->job->getRawBody(); return $event->job->attempts() === 1; }); $this->runFromPayload($releasedJob); - Event::assertDispatched($this->getJobReleasedAfterExceptionEvent(), function ($event) { + Event::assertDispatched(JobReleasedAfterException::class, function ($event) { return $event->job->attempts() === 2; }); } @@ -486,14 +472,14 @@ public function attempts_are_copied_from_x_header() { // Arrange OpenIdVerificator::fake(); - Event::fake($this->getJobReleasedAfterExceptionEvent()); + Event::fake(JobReleasedAfterException::class); // Act & Assert $job = $this->dispatch(new FailingJob()); request()->headers->set('X-CloudTasks-TaskRetryCount', 6); $job->run(); - Event::assertDispatched($this->getJobReleasedAfterExceptionEvent(), function ($event) { + Event::assertDispatched(JobReleasedAfterException::class, function ($event) { return $event->job->attempts() === 7; }); } @@ -505,7 +491,7 @@ public function retried_jobs_get_a_new_name() { // Arrange OpenIdVerificator::fake(); - Event::fake($this->getJobReleasedAfterExceptionEvent()); + Event::fake(JobReleasedAfterException::class); CloudTasksApi::fake(); // Act & Assert diff --git a/tests/TestCase.php b/tests/TestCase.php index bbdd1e1..b2c709e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,7 +11,6 @@ use Illuminate\Queue\Events\JobReleasedAfterException; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; -use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleasedAfterException as PackageJobReleasedAfterException; use Stackkit\LaravelGoogleCloudTasksQueue\Events\TaskCreated; use Stackkit\LaravelGoogleCloudTasksQueue\TaskHandler; @@ -35,7 +34,7 @@ protected function setUp(): void $this->defaultHeaders['Authorization'] = 'Bearer ' . encrypt(time() + 10); Event::listen( - $this->getJobReleasedAfterExceptionEvent(), + JobReleasedAfterException::class, function ($event) { $this->releasedJobPayload = $event->job->getRawBody(); } @@ -239,13 +238,4 @@ protected function assertDatabaseCount($table, int $count, $connection = null) $this->assertEquals($count, DB::connection($connection)->table($table)->count()); } - public function getJobReleasedAfterExceptionEvent(): string - { - // The JobReleasedAfterException event is not available in Laravel versions - // below 9.x so instead for those versions we throw our own event which - // is identical to the Laravel one. - return version_compare(app()->version(), '9.0.0', '<') - ? PackageJobReleasedAfterException::class - : JobReleasedAfterException::class; - } } From 0bcff3c34457d963afd050e4923903534e46f019 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Thu, 18 Jan 2024 22:04:05 +0100 Subject: [PATCH 2/6] Remove trailing comma --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f4d259e..1eec0a3 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,6 @@ "l10": [ "composer require laravel/framework:10.* orchestra/testbench:8.* --no-interaction --no-update", "composer update --prefer-stable --prefer-dist --no-interaction" - ], + ] } } From 0e6d0ae8ed53f596b5c834fd1e12bf1cdf3df0b7 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Mon, 12 Feb 2024 22:05:46 +0100 Subject: [PATCH 3/6] Move $tries and retryUntil() to Laravel --- src/CloudTasksApi.php | 2 - src/CloudTasksApiConcrete.php | 39 -------- src/CloudTasksApiContract.php | 3 - src/CloudTasksApiFake.php | 17 ---- src/CloudTasksJob.php | 33 ------- src/TaskHandler.php | 40 -------- tests/CloudTasksApiTest.php | 61 ------------ tests/CloudTasksDashboardTest.php | 12 +-- tests/QueueTest.php | 9 +- tests/Support/FailingJob.php | 2 + .../FailingJobWithExponentialBackoff.php | 2 + tests/Support/FailingJobWithMaxTries.php | 8 ++ .../FailingJobWithMaxTriesAndRetryUntil.php | 15 +++ tests/Support/FailingJobWithRetryUntil.php | 13 +++ tests/Support/JobThatWillBeReleased.php | 2 + tests/Support/SimpleJob.php | 2 + tests/TaskHandlerTest.php | 95 ++++++------------- tests/TestCase.php | 2 +- 18 files changed, 81 insertions(+), 276 deletions(-) create mode 100644 tests/Support/FailingJobWithMaxTries.php create mode 100644 tests/Support/FailingJobWithMaxTriesAndRetryUntil.php create mode 100644 tests/Support/FailingJobWithRetryUntil.php diff --git a/src/CloudTasksApi.php b/src/CloudTasksApi.php index c113bf4..f18cc4e 100644 --- a/src/CloudTasksApi.php +++ b/src/CloudTasksApi.php @@ -9,11 +9,9 @@ use Illuminate\Support\Facades\Facade; /** - * @method static RetryConfig getRetryConfig(string $queueName) * @method static Task createTask(string $queueName, Task $task) * @method static void deleteTask(string $taskName) * @method static Task getTask(string $taskName) - * @method static int|null getRetryUntilTimestamp(Task $task) */ class CloudTasksApi extends Facade { diff --git a/src/CloudTasksApiConcrete.php b/src/CloudTasksApiConcrete.php index d63b8ed..8b3b1ab 100644 --- a/src/CloudTasksApiConcrete.php +++ b/src/CloudTasksApiConcrete.php @@ -4,10 +4,8 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; -use Exception; use Google\Cloud\Tasks\V2\Attempt; use Google\Cloud\Tasks\V2\CloudTasksClient; -use Google\Cloud\Tasks\V2\RetryConfig; use Google\Cloud\Tasks\V2\Task; use Google\Protobuf\Duration; use Google\Protobuf\Timestamp; @@ -24,17 +22,6 @@ public function __construct(CloudTasksClient $client) $this->client = $client; } - public function getRetryConfig(string $queueName): RetryConfig - { - $retryConfig = $this->client->getQueue($queueName)->getRetryConfig(); - - if (! $retryConfig instanceof RetryConfig) { - throw new Exception('Queue does not have a retry config.'); - } - - return $retryConfig; - } - public function createTask(string $queueName, Task $task): Task { return $this->client->createTask($queueName, $task); @@ -49,30 +36,4 @@ public function getTask(string $taskName): Task { return $this->client->getTask($taskName); } - - public function getRetryUntilTimestamp(Task $task): ?int - { - $attempt = $task->getFirstAttempt(); - - if (!$attempt instanceof Attempt) { - return null; - } - - $queueName = implode('/', array_slice(explode('/', $task->getName()), 0, 6)); - - $retryConfig = $this->getRetryConfig($queueName); - - $maxRetryDuration = $retryConfig->getMaxRetryDuration(); - $dispatchTime = $attempt->getDispatchTime(); - - if (! $maxRetryDuration instanceof Duration || ! $dispatchTime instanceof Timestamp) { - return null; - } - - $maxDurationInSeconds = (int) $maxRetryDuration->getSeconds(); - - $firstAttemptTimestamp = $dispatchTime->toDateTime()->getTimestamp(); - - return $firstAttemptTimestamp + $maxDurationInSeconds; - } } diff --git a/src/CloudTasksApiContract.php b/src/CloudTasksApiContract.php index aa0880b..6eab9bc 100644 --- a/src/CloudTasksApiContract.php +++ b/src/CloudTasksApiContract.php @@ -4,14 +4,11 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; -use Google\Cloud\Tasks\V2\RetryConfig; use Google\Cloud\Tasks\V2\Task; interface CloudTasksApiContract { - public function getRetryConfig(string $queueName): RetryConfig; public function createTask(string $queueName, Task $task): Task; public function deleteTask(string $taskName): void; public function getTask(string $taskName): Task; - public function getRetryUntilTimestamp(Task $task): ?int; } diff --git a/src/CloudTasksApiFake.php b/src/CloudTasksApiFake.php index f1af5da..6c3cb58 100644 --- a/src/CloudTasksApiFake.php +++ b/src/CloudTasksApiFake.php @@ -17,17 +17,6 @@ class CloudTasksApiFake implements CloudTasksApiContract public array $createdTasks = []; public array $deletedTasks = []; - public function getRetryConfig(string $queueName): RetryConfig - { - $retryConfig = new RetryConfig(); - - $retryConfig - ->setMinBackoff((new Duration(['seconds' => 0]))) - ->setMaxBackoff((new Duration(['seconds' => 0]))); - - return $retryConfig; - } - public function createTask(string $queueName, Task $task): Task { $this->createdTasks[] = compact('queueName', 'task'); @@ -46,12 +35,6 @@ public function getTask(string $taskName): Task ->setName($taskName); } - - public function getRetryUntilTimestamp(Task $task): ?int - { - return null; - } - public function assertTaskDeleted(string $taskName): void { Assert::assertTrue( diff --git a/src/CloudTasksJob.php b/src/CloudTasksJob.php index 3cd63bc..5953bf7 100644 --- a/src/CloudTasksJob.php +++ b/src/CloudTasksJob.php @@ -17,9 +17,6 @@ class CloudTasksJob extends LaravelJob implements JobContract */ public array $job; - private ?int $maxTries; - public ?int $retryUntil = null; - /** * @var CloudTasksQueue */ @@ -65,41 +62,11 @@ public function setAttempts(int $attempts): void $this->job['internal']['attempts'] = $attempts; } - public function setMaxTries(int $maxTries): void - { - if ($maxTries === -1) { - $maxTries = 0; - } - - $this->maxTries = $maxTries; - } - - public function maxTries(): ?int - { - return $this->maxTries; - } - public function setQueue(string $queue): void { $this->queue = $queue; } - public function setRetryUntil(?int $retryUntil): void - { - $this->retryUntil = $retryUntil; - } - - public function retryUntil(): ?int - { - return $this->retryUntil; - } - - // timeoutAt was renamed to retryUntil in 8.x but we still support this. - public function timeoutAt(): ?int - { - return $this->retryUntil; - } - public function delete(): void { // Laravel automatically calls delete() after a job is processed successfully. However, this is diff --git a/src/TaskHandler.php b/src/TaskHandler.php index 9b38442..5262a3f 100644 --- a/src/TaskHandler.php +++ b/src/TaskHandler.php @@ -4,16 +4,11 @@ use Google\ApiCore\ApiException; use Google\Cloud\Tasks\V2\CloudTasksClient; -use Google\Cloud\Tasks\V2\RetryConfig; -use Illuminate\Bus\Queueable; use Illuminate\Contracts\Encryption\Encrypter; -use Illuminate\Queue\Jobs\Job; -use Illuminate\Queue\QueueManager; use Illuminate\Queue\WorkerOptions; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; use Safe\Exceptions\JsonException; -use UnexpectedValueException; use function Safe\json_decode; class TaskHandler @@ -33,11 +28,6 @@ class TaskHandler */ private $queue; - /** - * @var RetryConfig - */ - private $retryConfig = null; - public function __construct(CloudTasksClient $client) { $this->client = $client; @@ -117,8 +107,6 @@ private function handleTask(array $task): void { $job = new CloudTasksJob($task, $this->queue); - $this->loadQueueRetryConfig($job); - $taskName = request()->header('X-Cloudtasks-Taskname'); $fullTaskName = $this->client->taskName( $this->config['project'], @@ -137,39 +125,11 @@ private function handleTask(array $task): void throw $e; } - // If the task has a [X-CloudTasks-TaskRetryCount] header higher than 0, then - // we know the job was created using an earlier version of the package. This - // job does not have the attempts tracked internally yet. - $taskRetryCountHeader = request()->header('X-CloudTasks-TaskRetryCount'); - if ($taskRetryCountHeader && (int) $taskRetryCountHeader > 0) { - $job->setAttempts((int) $taskRetryCountHeader); - } else { - $job->setAttempts($task['internal']['attempts']); - } - - $job->setMaxTries($this->retryConfig->getMaxAttempts()); - - // If the job is being attempted again we also check if a - // max retry duration has been set. If that duration - // has passed, it should stop trying altogether. - if ($job->attempts() > 0) { - $job->setRetryUntil(CloudTasksApi::getRetryUntilTimestamp($apiTask)); - } - $job->setAttempts($job->attempts() + 1); app('queue.worker')->process($this->config['connection'], $job, $this->getWorkerOptions()); } - private function loadQueueRetryConfig(CloudTasksJob $job): void - { - $queue = $job->getQueue() ?: $this->config['queue']; - - $queueName = $this->client->queueName($this->config['project'], $this->config['location'], $queue); - - $this->retryConfig = CloudTasksApi::getRetryConfig($queueName); - } - public static function getCommandProperties(string $command): array { if (Str::startsWith($command, 'O:')) { diff --git a/tests/CloudTasksApiTest.php b/tests/CloudTasksApiTest.php index 5b5a1c2..e8769e5 100644 --- a/tests/CloudTasksApiTest.php +++ b/tests/CloudTasksApiTest.php @@ -8,7 +8,6 @@ use Google\Cloud\Tasks\V2\CloudTasksClient; use Google\Cloud\Tasks\V2\HttpMethod; use Google\Cloud\Tasks\V2\HttpRequest; -use Google\Cloud\Tasks\V2\RetryConfig; use Google\Cloud\Tasks\V2\Task; use Google\Protobuf\Timestamp; use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksApi; @@ -42,26 +41,6 @@ protected function setUp(): void } - /** - * @test - */ - public function test_get_retry_config() - { - // Act - $retryConfig = CloudTasksApi::getRetryConfig( - $this->client->queueName( - env('CI_CLOUD_TASKS_PROJECT_ID'), - env('CI_CLOUD_TASKS_LOCATION'), - env('CI_CLOUD_TASKS_QUEUE') - ) - ); - - // Assert - $this->assertInstanceOf(RetryConfig::class, $retryConfig); - $this->assertEquals(2, $retryConfig->getMaxAttempts()); - $this->assertEquals(5, $retryConfig->getMaxRetryDuration()->getSeconds()); - } - /** * @test */ @@ -147,44 +126,4 @@ public function test_delete_task() $this->expectExceptionMessage('NOT_FOUND'); CloudTasksApi::getTask($task->getName()); } - - /** - * @test - */ - public function test_get_retry_until_timestamp() - { - // Arrange - $httpRequest = new HttpRequest(); - $httpRequest->setHttpMethod(HttpMethod::GET); - $httpRequest->setUrl('https://httpstat.us/500'); - - $cloudTask = new Task(); - $cloudTask->setHttpRequest($httpRequest); - - $createdTask = CloudTasksApi::createTask( - $this->client->queueName( - env('CI_CLOUD_TASKS_PROJECT_ID'), - env('CI_CLOUD_TASKS_LOCATION'), - env('CI_CLOUD_TASKS_CUSTOM_QUEUE', env('CI_CLOUD_TASKS_QUEUE')) - ), - $cloudTask, - ); - - $secondsSlept = 0; - while ($createdTask->getFirstAttempt() === null) { - $createdTask = CloudTasksApi::getTask($createdTask->getName()); - sleep(1); - $secondsSlept += 1; - - if ($secondsSlept >= 180) { - $this->fail('Task took too long to get executed.'); - } - } - - // The queue max retry duration is 5 seconds. The max retry until timestamp is calculated from the - // first attempt, so we expect it to be [timestamp first attempt] + 5 seconds. - $expected = $createdTask->getFirstAttempt()->getDispatchTime()->getSeconds() + 5; - $actual = CloudTasksApi::getRetryUntilTimestamp($createdTask); - $this->assertSame($expected, $actual); - } } diff --git a/tests/CloudTasksDashboardTest.php b/tests/CloudTasksDashboardTest.php index e01f863..7dd759e 100644 --- a/tests/CloudTasksDashboardTest.php +++ b/tests/CloudTasksDashboardTest.php @@ -10,6 +10,7 @@ use Stackkit\LaravelGoogleCloudTasksQueue\OpenIdVerificator; use Stackkit\LaravelGoogleCloudTasksQueue\StackkitCloudTask; use Tests\Support\FailingJob; +use Tests\Support\FailingJobWithMaxTries; use Tests\Support\JobThatWillBeReleased; use Tests\Support\SimpleJob; @@ -391,11 +392,8 @@ public function when_a_job_fails_it_will_be_updated_in_the_dashboard() \Illuminate\Support\Carbon::setTestNow(now()); CloudTasksApi::fake(); OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig())->setMaxAttempts(3) - ); - $job = $this->dispatch(new FailingJob()); + $job = $this->dispatch(new FailingJobWithMaxTries()); $releasedJob = $job->runAndGetReleasedJob(); $releasedJob = $releasedJob->runAndGetReleasedJob(); $releasedJob->run(); @@ -423,9 +421,6 @@ public function when_a_job_is_released_it_will_be_updated_in_the_dashboard() \Illuminate\Support\Carbon::setTestNow(now()); CloudTasksApi::fake(); OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig())->setMaxAttempts(3) - ); $this->dispatch(new JobThatWillBeReleased())->run(); @@ -454,9 +449,6 @@ public function job_release_delay_is_added_to_the_metadata() \Illuminate\Support\Carbon::setTestNow(now()); CloudTasksApi::fake(); OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig())->setMaxAttempts(3) - ); $this->dispatch(new JobThatWillBeReleased(15))->run(); diff --git a/tests/QueueTest.php b/tests/QueueTest.php index c094f0a..bcf4062 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -381,15 +381,14 @@ public function test_failing_method_on_job() { // Arrange CloudTasksApi::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig())->setMaxAttempts(1) - ); - OpenIdVerificator::fake(); Log::swap(new LogFake()); // Act - $this->dispatch(new FailingJob())->run(); + $this->dispatch(new FailingJob()) + ->runAndGetReleasedJob() + ->runAndGetReleasedJob() + ->runAndGetReleasedJob(); // Assert Log::assertLogged('FailingJob:failed'); diff --git a/tests/Support/FailingJob.php b/tests/Support/FailingJob.php index 5fbbffc..3fad0d3 100644 --- a/tests/Support/FailingJob.php +++ b/tests/Support/FailingJob.php @@ -12,6 +12,8 @@ class FailingJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + public $tries = 3; + /** * Create a new job instance. * diff --git a/tests/Support/FailingJobWithExponentialBackoff.php b/tests/Support/FailingJobWithExponentialBackoff.php index aec644d..7d19f8d 100644 --- a/tests/Support/FailingJobWithExponentialBackoff.php +++ b/tests/Support/FailingJobWithExponentialBackoff.php @@ -12,6 +12,8 @@ class FailingJobWithExponentialBackoff implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + public $tries = 5; + public function handle() { throw new \Error('simulating a failing job'); diff --git a/tests/Support/FailingJobWithMaxTries.php b/tests/Support/FailingJobWithMaxTries.php new file mode 100644 index 0000000..bd950df --- /dev/null +++ b/tests/Support/FailingJobWithMaxTries.php @@ -0,0 +1,8 @@ +addMinutes(5); + } +} diff --git a/tests/Support/FailingJobWithRetryUntil.php b/tests/Support/FailingJobWithRetryUntil.php new file mode 100644 index 0000000..74b1027 --- /dev/null +++ b/tests/Support/FailingJobWithRetryUntil.php @@ -0,0 +1,13 @@ +addMinutes(5); + } +} diff --git a/tests/Support/JobThatWillBeReleased.php b/tests/Support/JobThatWillBeReleased.php index bbd2d98..a2b7d61 100644 --- a/tests/Support/JobThatWillBeReleased.php +++ b/tests/Support/JobThatWillBeReleased.php @@ -14,6 +14,8 @@ class JobThatWillBeReleased implements ShouldQueue private int $releaseDelay; + public $tries = 3; + /** * Create a new job instance. * diff --git a/tests/Support/SimpleJob.php b/tests/Support/SimpleJob.php index 34e1912..5eae133 100644 --- a/tests/Support/SimpleJob.php +++ b/tests/Support/SimpleJob.php @@ -13,6 +13,8 @@ class SimpleJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + public $tries = 3; + /** * Create a new job instance. * diff --git a/tests/TaskHandlerTest.php b/tests/TaskHandlerTest.php index 31a3b20..febbfdf 100644 --- a/tests/TaskHandlerTest.php +++ b/tests/TaskHandlerTest.php @@ -20,6 +20,9 @@ use Stackkit\LaravelGoogleCloudTasksQueue\TaskHandler; use Tests\Support\EncryptedJob; use Tests\Support\FailingJob; +use Tests\Support\FailingJobWithMaxTries; +use Tests\Support\FailingJobWithMaxTriesAndRetryUntil; +use Tests\Support\FailingJobWithRetryUntil; use Tests\Support\SimpleJob; use UnexpectedValueException; @@ -250,10 +253,7 @@ public function after_max_attempts_it_will_log_to_failed_table() { // Arrange OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig())->setMaxAttempts(3) - ); - $job = $this->dispatch(new FailingJob()); + $job = $this->dispatch(new FailingJobWithMaxTries()); // Act & Assert $this->assertDatabaseCount('failed_jobs', 0); @@ -276,10 +276,6 @@ public function after_max_attempts_it_will_delete_the_task() // Arrange OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig())->setMaxAttempts(3) - ); - $job = $this->dispatch(new FailingJob()); // Act & Assert @@ -301,16 +297,18 @@ public function after_max_attempts_it_will_delete_the_task() /** * @test + * + * @testWith [{"now": "2020-01-01 00:00:00", "try_at": "2020-01-01 00:00:00", "should_fail": false}] + * [{"now": "2020-01-01 00:00:00", "try_at": "2020-01-01 00:04:59", "should_fail": false}] + * [{"now": "2020-01-01 00:00:00", "try_at": "2020-01-01 00:05:00", "should_fail": true}] */ - public function after_max_retry_until_it_will_log_to_failed_table_and_delete_the_task() + public function after_max_retry_until_it_will_log_to_failed_table_and_delete_the_task(array $args) { // Arrange + $this->travelTo($args['now']); + OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig())->setMaxRetryDuration(new Duration(['seconds' => 30])) - ); - CloudTasksApi::partialMock()->shouldReceive('getRetryUntilTimestamp')->andReturn(1); - $job = $this->dispatch(new FailingJob()); + $job = $this->dispatch(new FailingJobWithRetryUntil()); // Act $releasedJob = $job->runAndGetReleasedJob(); @@ -321,13 +319,11 @@ public function after_max_retry_until_it_will_log_to_failed_table_and_delete_the $this->assertDatabaseCount('failed_jobs', 0); // Act - CloudTasksApi::partialMock()->shouldReceive('getRetryUntilTimestamp')->andReturn(1); + $this->travelTo($args['try_at']); $releasedJob->run(); // Assert - CloudTasksApi::assertDeletedTaskCount(2); - CloudTasksApi::assertTaskDeleted($job->task->getName()); - $this->assertDatabaseCount('failed_jobs', 1); + $this->assertDatabaseCount('failed_jobs', $args['should_fail'] ? 1 : 0); } /** @@ -337,10 +333,6 @@ public function test_unlimited_max_attempts() { // Arrange OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - // -1 is a valid option in Cloud Tasks to indicate there is no max. - (new RetryConfig())->setMaxAttempts(-1) - ); // Act $job = $this->dispatch(new FailingJob()); @@ -359,33 +351,28 @@ public function test_max_attempts_in_combination_with_retry_until() { // Arrange OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig()) - ->setMaxAttempts(3) - ->setMaxRetryDuration(new Duration(['seconds' => 3])) - ); - CloudTasksApi::partialMock()->shouldReceive('getRetryUntilTimestamp')->andReturn(time() + 10)->byDefault(); - $job = $this->dispatch(new FailingJob()); + $this->travelTo('2020-01-01 00:00:00'); - // Act & Assert - $releasedJob = $job->runAndGetReleasedJob(); - $releasedJob = $releasedJob->runAndGetReleasedJob(); + $job = $this->dispatch(new FailingJobWithMaxTriesAndRetryUntil()); - # After 2 attempts both Laravel versions should report the same: 2 errors and 0 failures. - $task = StackkitCloudTask::whereTaskUuid($job->payloadAsArray('uuid'))->firstOrFail(); - $this->assertEquals(2, $task->getNumberOfAttempts()); - $this->assertEquals('error', $task->status); + // When retryUntil is specified, the maxAttempts is ignored. - $releasedJob->run(); - - # Max attempts was reached - $this->assertEquals('error', $task->fresh()->status); + // Act & Assert - CloudTasksApi::shouldReceive('getRetryUntilTimestamp')->andReturn(time() - 1); - $releasedJob->run(); + // The max attempts is 3, but the retryUntil is set to 5 minutes from now. + // So when we attempt the job 10 times, it should still not fail. + foreach (range(1, 10) as $attempt) { + $job = $job->runAndGetReleasedJob(); + CloudTasksApi::assertDeletedTaskCount($attempt); + CloudTasksApi::assertTaskDeleted($job->task->getName()); + $this->assertDatabaseCount('failed_jobs', 0); + } - $this->assertEquals('failed', $task->fresh()->status); + // Now we travel to 5 minutes from now, and the job should fail. + $this->travelTo('2020-01-01 00:05:00'); + $job->run(); + $this->assertDatabaseCount('failed_jobs', 1); } /** @@ -417,9 +404,6 @@ public function failing_jobs_are_released() { // Arrange OpenIdVerificator::fake(); - CloudTasksApi::partialMock()->shouldReceive('getRetryConfig')->andReturn( - (new RetryConfig())->setMaxAttempts(3) - ); Event::fake(JobReleasedAfterException::class); // Act @@ -465,25 +449,6 @@ public function attempts_are_tracked_internally() }); } - /** - * @test - */ - public function attempts_are_copied_from_x_header() - { - // Arrange - OpenIdVerificator::fake(); - Event::fake(JobReleasedAfterException::class); - - // Act & Assert - $job = $this->dispatch(new FailingJob()); - request()->headers->set('X-CloudTasks-TaskRetryCount', 6); - $job->run(); - - Event::assertDispatched(JobReleasedAfterException::class, function ($event) { - return $event->job->attempts() === 7; - }); - } - /** * @test */ diff --git a/tests/TestCase.php b/tests/TestCase.php index b2c709e..4455831 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,7 +19,7 @@ class TestCase extends \Orchestra\Testbench\TestCase use DatabaseTransactions; /** - * @var \Mockery\Mock|CloudTasksClient $client + * @var CloudTasksClient $client */ public $client; From 75c80c20886207a939f11e99efaac7bee63086ef Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Mon, 12 Feb 2024 22:25:07 +0100 Subject: [PATCH 4/6] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 24bf4e8..9f8196f 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,7 @@ With Cloud Tasks, this is not the case. Instead, Cloud Tasks will schedule the j #### Good to know -- The "Min backoff" and "Max backoff" options in Cloud Tasks are ignored. This is intentional: Laravel has its own backoff feature (which is more powerful than what Cloud Tasks offers) and therefore I have chosen that over the Cloud Tasks one. -- Similarly to the backoff feature, I have also chosen to let the package do job retries the 'Laravel way'. In Cloud Tasks, when a task throws an exception, Cloud Tasks will decide for itself when to retry the task (based on the backoff values). It will also manage its own state and knows how many times a task has been retried. This is different from Laravel. In typical Laravel queues, when a job throws an exception, the job is deleted and released back onto the queue. In order to support Laravel's backoff feature, this package must behave the same way about job retries. +- Backoff, retries, max tries, retryUntil are all handled by Laravel. All these options are ignored in the Cloud Tasks configuration.
From 718af72539549a4caab645cf56f7687eb2848b88 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Mon, 12 Feb 2024 22:51:22 +0100 Subject: [PATCH 5/6] Install Laravel Pint --- composer.json | 3 ++- config/cloud-tasks.php | 2 +- factories/StackkitCloudTaskFactory.php | 2 +- src/CloudTasks.php | 9 ++----- src/CloudTasksApi.php | 1 - src/CloudTasksApiConcrete.php | 5 +--- src/CloudTasksApiContract.php | 2 ++ src/CloudTasksApiController.php | 22 ++++++++-------- src/CloudTasksApiFake.php | 9 +++---- src/CloudTasksJob.php | 7 +++--- src/CloudTasksQueue.php | 35 +++++++++++++------------- src/CloudTasksServiceProvider.php | 23 +++++++++-------- src/Config.php | 19 +++++++------- src/DashboardService.php | 3 ++- src/Entities/StatRow.php | 2 ++ src/Events/JobReleased.php | 9 ------- src/Events/TaskCreated.php | 1 + src/LogFake.php | 7 +++--- src/OpenIdVerificatorConcrete.php | 2 +- src/OpenIdVerificatorFake.php | 4 +-- src/StackkitCloudTask.php | 13 ++++++---- src/TaskHandler.php | 7 +++--- tests/CloudTasksApiTest.php | 6 ++--- tests/CloudTasksDashboardTest.php | 27 ++++++++++---------- tests/QueueTest.php | 10 +++++--- tests/Support/EncryptedJob.php | 2 +- tests/Support/SimpleJob.php | 1 - tests/Support/UserJob.php | 3 +-- tests/TaskHandlerTest.php | 10 +++++--- tests/TestCase.php | 33 ++++++++++++------------ 30 files changed, 135 insertions(+), 144 deletions(-) diff --git a/composer.json b/composer.json index 1eec0a3..dfb337b 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "orchestra/testbench": "^8.0", "nunomaduro/larastan": "^1.0 || ^2.0", "thecodingmachine/phpstan-safe-rule": "^1.2", - "laravel/legacy-factories": "^1.3" + "laravel/legacy-factories": "^1.3", + "laravel/pint": "^1.13" }, "autoload": { "psr-4": { diff --git a/config/cloud-tasks.php b/config/cloud-tasks.php index c8cbdca..1885df2 100644 --- a/config/cloud-tasks.php +++ b/config/cloud-tasks.php @@ -5,6 +5,6 @@ return [ 'dashboard' => [ 'enabled' => env('STACKKIT_CLOUD_TASKS_DASHBOARD_ENABLED', false), - 'password' => env('STACKKIT_CLOUD_TASKS_DASHBOARD_PASSWORD', 'MyPassword1!') + 'password' => env('STACKKIT_CLOUD_TASKS_DASHBOARD_PASSWORD', 'MyPassword1!'), ], ]; diff --git a/factories/StackkitCloudTaskFactory.php b/factories/StackkitCloudTaskFactory.php index ac0991e..926ce94 100644 --- a/factories/StackkitCloudTaskFactory.php +++ b/factories/StackkitCloudTaskFactory.php @@ -6,9 +6,9 @@ /** @var \Illuminate\Database\Eloquent\Factory $factory */ +use Faker\Generator as Faker; use Illuminate\Support\Str; use Stackkit\LaravelGoogleCloudTasksQueue\StackkitCloudTask; -use Faker\Generator as Faker; $factory->define(StackkitCloudTask::class, function (Faker $faker) { return [ diff --git a/src/CloudTasks.php b/src/CloudTasks.php index b9ca554..70d512d 100644 --- a/src/CloudTasks.php +++ b/src/CloudTasks.php @@ -3,7 +3,6 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; use Carbon\Carbon; -use Closure; use Throwable; final class CloudTasks @@ -18,7 +17,7 @@ public static function check($request) { $token = $request->bearerToken(); - if (!$token) { + if (! $token) { return false; } @@ -27,14 +26,12 @@ public static function check($request) return $expireTimestamp > Carbon::now()->timestamp; } catch (Throwable $e) { - return false; + return false; } } /** * Determine if the dashboard is enabled. - * - * @return bool */ public static function dashboardEnabled(): bool { @@ -43,8 +40,6 @@ public static function dashboardEnabled(): bool /** * Determine if the dashboard is disabled. - * - * @return bool */ public static function dashboardDisabled(): bool { diff --git a/src/CloudTasksApi.php b/src/CloudTasksApi.php index f18cc4e..e0c04a2 100644 --- a/src/CloudTasksApi.php +++ b/src/CloudTasksApi.php @@ -4,7 +4,6 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; -use Google\Cloud\Tasks\V2\RetryConfig; use Google\Cloud\Tasks\V2\Task; use Illuminate\Support\Facades\Facade; diff --git a/src/CloudTasksApiConcrete.php b/src/CloudTasksApiConcrete.php index 8b3b1ab..4e09517 100644 --- a/src/CloudTasksApiConcrete.php +++ b/src/CloudTasksApiConcrete.php @@ -4,16 +4,13 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; -use Google\Cloud\Tasks\V2\Attempt; use Google\Cloud\Tasks\V2\CloudTasksClient; use Google\Cloud\Tasks\V2\Task; -use Google\Protobuf\Duration; -use Google\Protobuf\Timestamp; class CloudTasksApiConcrete implements CloudTasksApiContract { /** - * @var CloudTasksClient $client + * @var CloudTasksClient */ private $client; diff --git a/src/CloudTasksApiContract.php b/src/CloudTasksApiContract.php index 6eab9bc..8e8aca3 100644 --- a/src/CloudTasksApiContract.php +++ b/src/CloudTasksApiContract.php @@ -9,6 +9,8 @@ interface CloudTasksApiContract { public function createTask(string $queueName, Task $task): Task; + public function deleteTask(string $taskName): void; + public function getTask(string $taskName): Task; } diff --git a/src/CloudTasksApiController.php b/src/CloudTasksApiController.php index 1d3a771..7726b7a 100644 --- a/src/CloudTasksApiController.php +++ b/src/CloudTasksApiController.php @@ -2,13 +2,14 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; +use const STR_PAD_LEFT; + use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Stackkit\LaravelGoogleCloudTasksQueue\Entities\StatRow; -use const STR_PAD_LEFT; class CloudTasksApiController { @@ -16,13 +17,13 @@ public function login(): ?string { $password = config('cloud-tasks.dashboard.password'); - if (!is_string($password)) { + if (! is_string($password)) { return null; } $validPassword = hash_equals($password, request('password')); - if (!$validPassword) { + if (! $validPassword) { return null; } @@ -31,9 +32,9 @@ public function login(): ?string public function dashboard(): array { - $dbDriver = config('database.connections.' . config('database.default') . '.driver'); + $dbDriver = config('database.connections.'.config('database.default').'.driver'); - if (!in_array($dbDriver, ['mysql', 'pgsql'])) { + if (! in_array($dbDriver, ['mysql', 'pgsql'])) { throw new Exception('Unsupported database driver for Cloud Tasks dashboard.'); } @@ -59,12 +60,12 @@ public function dashboard(): array DB::raw('CASE WHEN status = \'failed\' THEN 1 ELSE 0 END AS failed'), DB::raw(' CASE - WHEN ' . $groupBy['this_minute'] . ' = \'' . now()->utc()->format('H:i') . '\' THEN \'this_minute\' - WHEN ' . $groupBy['this_hour'] . ' = \'' . now()->utc()->format('H') . '\' THEN \'this_hour\' + WHEN '.$groupBy['this_minute'].' = \''.now()->utc()->format('H:i').'\' THEN \'this_minute\' + WHEN '.$groupBy['this_hour'].' = \''.now()->utc()->format('H').'\' THEN \'this_hour\' ELSE \'today\' END AS time_preset - ') + '), ] ) ->groupBy( @@ -74,7 +75,7 @@ public function dashboard(): array ] ) ->get() - ->map(fn($row) => StatRow::createFromObject($row)) + ->map(fn ($row) => StatRow::createFromObject($row)) ->toArray(); $response = [ @@ -156,8 +157,7 @@ public function tasks() $maxId = $tasks->max('id'); - return $tasks->map(function (StackkitCloudTask $task) use ($maxId) - { + return $tasks->map(function (StackkitCloudTask $task) use ($maxId) { return [ 'uuid' => $task->task_uuid, 'id' => str_pad((string) $task->id, strlen($maxId), '0', STR_PAD_LEFT), diff --git a/src/CloudTasksApiFake.php b/src/CloudTasksApiFake.php index 6c3cb58..1614efe 100644 --- a/src/CloudTasksApiFake.php +++ b/src/CloudTasksApiFake.php @@ -5,16 +5,13 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; use Closure; -use Google\Cloud\Tasks\V2\RetryConfig; use Google\Cloud\Tasks\V2\Task; -use Google\Protobuf\Duration; -use Illuminate\Support\Arr; -use Illuminate\Support\Str; use PHPUnit\Framework\Assert; class CloudTasksApiFake implements CloudTasksApiContract { public array $createdTasks = []; + public array $deletedTasks = []; public function createTask(string $queueName, Task $task): Task @@ -39,7 +36,7 @@ public function assertTaskDeleted(string $taskName): void { Assert::assertTrue( in_array($taskName, $this->deletedTasks), - 'The task [' . $taskName . '] should have been deleted but it is not.' + 'The task ['.$taskName.'] should have been deleted but it is not.' ); } @@ -47,7 +44,7 @@ public function assertTaskNotDeleted(string $taskName): void { Assert::assertTrue( ! in_array($taskName, $this->deletedTasks), - 'The task [' . $taskName . '] should not have been deleted but it was.' + 'The task ['.$taskName.'] should not have been deleted but it was.' ); } diff --git a/src/CloudTasksJob.php b/src/CloudTasksJob.php index 5953bf7..77eb862 100644 --- a/src/CloudTasksJob.php +++ b/src/CloudTasksJob.php @@ -6,14 +6,13 @@ use Illuminate\Contracts\Queue\Job as JobContract; use Illuminate\Queue\Jobs\Job as LaravelJob; use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleased; + use function Safe\json_encode; class CloudTasksJob extends LaravelJob implements JobContract { /** * The Cloud Tasks raw job payload (request payload). - * - * @var array */ public array $job; @@ -27,9 +26,9 @@ public function __construct(array $job, CloudTasksQueue $cloudTasksQueue) $this->job = $job; $this->container = Container::getInstance(); $this->cloudTasksQueue = $cloudTasksQueue; - + $command = TaskHandler::getCommandProperties($job['data']['command']); - $this->queue = $command['queue'] ?? config('queue.connections.' .config('queue.default') . '.queue'); + $this->queue = $command['queue'] ?? config('queue.connections.'.config('queue.default').'.queue'); } public function job() diff --git a/src/CloudTasksQueue.php b/src/CloudTasksQueue.php index 4828b6f..a7947e6 100644 --- a/src/CloudTasksQueue.php +++ b/src/CloudTasksQueue.php @@ -12,8 +12,8 @@ use Illuminate\Contracts\Queue\Queue as QueueContract; use Illuminate\Queue\Queue as LaravelQueue; use Illuminate\Support\Carbon; -use Illuminate\Support\Str; use Stackkit\LaravelGoogleCloudTasksQueue\Events\TaskCreated; + use function Safe\json_decode; use function Safe\json_encode; @@ -36,7 +36,7 @@ public function __construct(array $config, CloudTasksClient $client, $dispatchAf /** * Get the size of the queue. * - * @param string|null $queue + * @param string|null $queue * @return int */ public function size($queue = null) @@ -48,9 +48,9 @@ public function size($queue = null) /** * Push a new job onto the queue. * - * @param string|object $job - * @param mixed $data - * @param string|null $queue + * @param string|object $job + * @param mixed $data + * @param string|null $queue * @return void */ public function push($job, $data = '', $queue = null) @@ -69,9 +69,8 @@ function ($payload, $queue) { /** * Push a raw payload onto the queue. * - * @param string $payload - * @param string|null $queue - * @param array $options + * @param string $payload + * @param string|null $queue * @return string */ public function pushRaw($payload, $queue = null, array $options = []) @@ -84,10 +83,10 @@ public function pushRaw($payload, $queue = null, array $options = []) /** * Push a new job onto the queue after a delay. * - * @param \DateTimeInterface|\DateInterval|int $delay - * @param string|object $job - * @param mixed $data - * @param string|null $queue + * @param \DateTimeInterface|\DateInterval|int $delay + * @param string|object $job + * @param mixed $data + * @param string|null $queue * @return void */ public function later($delay, $job, $data = '', $queue = null) @@ -106,9 +105,9 @@ function ($payload, $queue, $delay) { /** * Push a job to Cloud Tasks. * - * @param string|null $queue - * @param string $payload - * @param \DateTimeInterface|\DateInterval|int $delay + * @param string|null $queue + * @param string $payload + * @param \DateTimeInterface|\DateInterval|int $delay * @return string */ protected function pushToCloudTasks($queue, $payload, $delay = 0) @@ -137,7 +136,7 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0) // The deadline for requests sent to the app. If the app does not respond by // this deadline then the request is cancelled and the attempt is marked as // a failure. Cloud Tasks will retry the task according to the RetryConfig. - if (!empty($this->config['dispatch_deadline'])) { + if (! empty($this->config['dispatch_deadline'])) { $task->setDispatchDeadline(new Duration(['seconds' => $this->config['dispatch_deadline']])); } @@ -167,7 +166,7 @@ private function taskName(string $queueName, array $payload): string $this->config['project'], $this->config['location'], $queueName, - $displayName . '-' . $payload['uuid'] . '-' . Carbon::now()->getTimeStampMs(), + $displayName.'-'.$payload['uuid'].'-'.Carbon::now()->getTimeStampMs(), ); } @@ -184,7 +183,7 @@ private function sanitizeTaskName(string $taskName) private function withAttempts(array $payload): array { - if (!isset($payload['internal']['attempts'])) { + if (! isset($payload['internal']['attempts'])) { $payload['internal']['attempts'] = 0; } diff --git a/src/CloudTasksServiceProvider.php b/src/CloudTasksServiceProvider.php index d22a281..66f8dfb 100644 --- a/src/CloudTasksServiceProvider.php +++ b/src/CloudTasksServiceProvider.php @@ -10,6 +10,7 @@ use Illuminate\Support\ServiceProvider as LaravelServiceProvider; use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleased; use Stackkit\LaravelGoogleCloudTasksQueue\Events\TaskCreated; + use function Safe\file_get_contents; use function Safe\json_decode; @@ -52,10 +53,10 @@ private function registerConnector(): void private function registerConfig(): void { $this->publishes([ - __DIR__ . '/../config/cloud-tasks.php' => config_path('cloud-tasks.php'), + __DIR__.'/../config/cloud-tasks.php' => config_path('cloud-tasks.php'), ], ['cloud-tasks']); - $this->mergeConfigFrom(__DIR__ . '/../config/cloud-tasks.php', 'cloud-tasks'); + $this->mergeConfigFrom(__DIR__.'/../config/cloud-tasks.php', 'cloud-tasks'); } private function registerViews(): void @@ -65,7 +66,7 @@ private function registerViews(): void // return; } - $this->loadViewsFrom(__DIR__ . '/../views', 'cloud-tasks'); + $this->loadViewsFrom(__DIR__.'/../views', 'cloud-tasks'); } private function registerAssets(): void @@ -75,7 +76,7 @@ private function registerAssets(): void } $this->publishes([ - __DIR__ . '/../dashboard/dist' => public_path('vendor/cloud-tasks'), + __DIR__.'/../dashboard/dist' => public_path('vendor/cloud-tasks'), ], ['cloud-tasks']); } @@ -86,7 +87,7 @@ private function registerMigrations(): void } $this->loadMigrationsFrom([ - __DIR__ . '/../migrations', + __DIR__.'/../migrations', ]); } @@ -139,7 +140,7 @@ private function registerDashboard(): void }); $events->listen(JobFailed::class, function (JobFailed $event) { - if (!$event->job instanceof CloudTasksJob) { + if (! $event->job instanceof CloudTasksJob) { return; } @@ -152,7 +153,7 @@ private function registerDashboard(): void }); $events->listen(JobProcessing::class, function (JobProcessing $event) { - if (!$event->job instanceof CloudTasksJob) { + if (! $event->job instanceof CloudTasksJob) { return; } @@ -162,7 +163,7 @@ private function registerDashboard(): void }); $events->listen(JobProcessed::class, function (JobProcessed $event) { - if (!$event->job instanceof CloudTasksJob) { + if (! $event->job instanceof CloudTasksJob) { return; } @@ -174,7 +175,7 @@ private function registerDashboard(): void }); $events->listen(JobExceptionOccurred::class, function (JobExceptionOccurred $event) { - if (!$event->job instanceof CloudTasksJob) { + if (! $event->job instanceof CloudTasksJob) { return; } @@ -186,7 +187,7 @@ private function registerDashboard(): void }); $events->listen(JobFailed::class, function ($event) { - if (!$event->job instanceof CloudTasksJob) { + if (! $event->job instanceof CloudTasksJob) { return; } @@ -196,7 +197,7 @@ private function registerDashboard(): void }); $events->listen(JobReleased::class, function (JobReleased $event) { - if (!$event->job instanceof CloudTasksJob) { + if (! $event->job instanceof CloudTasksJob) { return; } diff --git a/src/Config.php b/src/Config.php index 422ce44..9912fae 100644 --- a/src/Config.php +++ b/src/Config.php @@ -25,7 +25,7 @@ public static function validate(array $config): void } /** - * @param Closure|string $handler + * @param Closure|string $handler */ public static function getHandler($handler): string { @@ -43,9 +43,9 @@ public static function getHandler($handler): string // should always call a public address / hostname to process tasks. if (in_array($parse['host'], ['localhost', '127.0.0.1', '::1'])) { throw new Exception(sprintf( - 'Unable to push task to Cloud Tasks because the handler URL is set to a local host: %s. ' . - 'This does not work because Google is not able to call the given local URL. ' . - 'If you are developing on locally, consider using Ngrok or Expose for Laravel to expose your local ' . + 'Unable to push task to Cloud Tasks because the handler URL is set to a local host: %s. '. + 'This does not work because Google is not able to call the given local URL. '. + 'If you are developing on locally, consider using Ngrok or Expose for Laravel to expose your local '. 'application to the internet.', $handler )); @@ -56,30 +56,29 @@ public static function getHandler($handler): string // always be https, we will provide a little extra information on how to fix this. if ($parse['scheme'] !== 'https') { throw new Exception(sprintf( - 'Unable to push task to Cloud Tasks because the hander URL is not https. Google Cloud Tasks ' . - 'will only call safe (https) URLs. If you are running Laravel behind a proxy (e.g. Ngrok, Expose), make sure it is ' . - 'as a trusted proxy. To quickly fix this, add the following to the [app/Http/Middleware/TrustProxies] middleware: ' . + 'Unable to push task to Cloud Tasks because the hander URL is not https. Google Cloud Tasks '. + 'will only call safe (https) URLs. If you are running Laravel behind a proxy (e.g. Ngrok, Expose), make sure it is '. + 'as a trusted proxy. To quickly fix this, add the following to the [app/Http/Middleware/TrustProxies] middleware: '. 'protected $proxies = \'*\';' )); } $trimmedHandlerUrl = rtrim($handler, '/'); - if (!str_ends_with($trimmedHandlerUrl, '/handle-task')) { + if (! str_ends_with($trimmedHandlerUrl, '/handle-task')) { return "$trimmedHandlerUrl/handle-task"; } return $trimmedHandlerUrl; } catch (UrlException $e) { throw new Exception( - 'Unable to push task to Cloud Tasks because the task handler URL (' . $handler . ') is ' . + 'Unable to push task to Cloud Tasks because the task handler URL ('.$handler.') is '. 'malformed. Please inspect the URL closely for any mistakes.' ); } } /** - * @param array $config * @return string|null The audience as an hash or null if not needed */ public static function getAudience(array $config): ?string diff --git a/src/DashboardService.php b/src/DashboardService.php index 8800e3d..bd5e973 100644 --- a/src/DashboardService.php +++ b/src/DashboardService.php @@ -10,6 +10,7 @@ use Illuminate\Queue\Events\JobFailed; use Illuminate\Support\Facades\DB; use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleased; + use function Safe\json_decode; class DashboardService @@ -61,7 +62,7 @@ public function add(string $queue, Task $task): void 'task_uuid' => $uuid, 'name' => $this->getTaskName($task), 'queue' => $queue, - 'payload' => $this->getTaskBody($task), + 'payload' => $this->getTaskBody($task), 'status' => $status, 'metadata' => $metadata->toJson(), 'created_at' => now()->utc(), diff --git a/src/Entities/StatRow.php b/src/Entities/StatRow.php index a92d18a..244c7c9 100644 --- a/src/Entities/StatRow.php +++ b/src/Entities/StatRow.php @@ -5,7 +5,9 @@ class StatRow { public int $count; + public int $failed; + public string $time_preset; public static function createFromObject(object $row): StatRow diff --git a/src/Events/JobReleased.php b/src/Events/JobReleased.php index 614e45b..530f1e8 100644 --- a/src/Events/JobReleased.php +++ b/src/Events/JobReleased.php @@ -10,31 +10,22 @@ class JobReleased { /** * The connection name. - * - * @var string */ public string $connectionName; /** * The job instance. - * - * @var Job */ public Job $job; /** * The job delay in seconds. - * - * @var int */ public int $delay; /** * Create a new event instance. * - * @param string $connectionName - * @param Job $job - * @param int $delay * @return void */ public function __construct(string $connectionName, Job $job, int $delay = 0) diff --git a/src/Events/TaskCreated.php b/src/Events/TaskCreated.php index a05f415..9ab76aa 100644 --- a/src/Events/TaskCreated.php +++ b/src/Events/TaskCreated.php @@ -9,6 +9,7 @@ class TaskCreated { public string $queue; + public Task $task; public function task(Task $task): self diff --git a/src/LogFake.php b/src/LogFake.php index e4e86ff..c9a3476 100644 --- a/src/LogFake.php +++ b/src/LogFake.php @@ -5,7 +5,6 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; use PHPUnit\Framework\Assert as PHPUnit; -use Psr\Log\LoggerInterface; class LogFake { @@ -52,7 +51,7 @@ public function debug(string $message, array $context = []): void } /** - * @param string $level + * @param string $level */ public function log($level, string $message, array $context = []): void { @@ -66,14 +65,14 @@ public function channel(): self public function assertLogged(string $message): void { - PHPUnit::assertTrue(in_array($message, $this->loggedMessages), 'The message [' . $message . '] was not logged.'); + PHPUnit::assertTrue(in_array($message, $this->loggedMessages), 'The message ['.$message.'] was not logged.'); } public function assertNotLogged(string $message): void { PHPUnit::assertTrue( ! in_array($message, $this->loggedMessages), - 'The message [' . $message . '] was logged.' + 'The message ['.$message.'] was logged.' ); } } diff --git a/src/OpenIdVerificatorConcrete.php b/src/OpenIdVerificatorConcrete.php index a3c1c4a..8e5541c 100644 --- a/src/OpenIdVerificatorConcrete.php +++ b/src/OpenIdVerificatorConcrete.php @@ -11,7 +11,7 @@ class OpenIdVerificatorConcrete extends Facade { public function verify(?string $token, array $config): void { - if (!$token) { + if (! $token) { throw new CloudTasksException('Missing [Authorization] header'); } diff --git a/src/OpenIdVerificatorFake.php b/src/OpenIdVerificatorFake.php index 79cedb6..5666e76 100644 --- a/src/OpenIdVerificatorFake.php +++ b/src/OpenIdVerificatorFake.php @@ -10,7 +10,7 @@ class OpenIdVerificatorFake { public function verify(?string $token, array $config): void { - if (!$token) { + if (! $token) { return; } @@ -19,7 +19,7 @@ public function verify(?string $token, array $config): void [ 'audience' => Config::getAudience($config), 'throwException' => true, - 'certsLocation' => __DIR__ . '/../tests/Support/self-signed-public-key-as-jwk.json', + 'certsLocation' => __DIR__.'/../tests/Support/self-signed-public-key-as-jwk.json', ] ); } diff --git a/src/StackkitCloudTask.php b/src/StackkitCloudTask.php index 4af02f1..87277d1 100644 --- a/src/StackkitCloudTask.php +++ b/src/StackkitCloudTask.php @@ -2,13 +2,15 @@ namespace Stackkit\LaravelGoogleCloudTasksQueue; +use const JSON_PRETTY_PRINT; + use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; -use const JSON_PRETTY_PRINT; -use function Safe\json_encode; + use function Safe\json_decode; +use function Safe\json_encode; /** * @property int $id @@ -31,7 +33,7 @@ public static function findByUuid(string $uuid): StackkitCloudTask } /** - * @param Builder $builder + * @param Builder $builder * @return Builder */ public function scopeNewestFirst(Builder $builder): Builder @@ -40,7 +42,7 @@ public function scopeNewestFirst(Builder $builder): Builder } /** - * @param Builder $builder + * @param Builder $builder * @return Builder */ public function scopeFailed(Builder $builder): Builder @@ -69,7 +71,7 @@ public function getNumberOfAttempts(): int } /** - * @param mixed $value + * @param mixed $value */ public function setMetadata(string $key, $value): void { @@ -101,6 +103,7 @@ public function getEvents(): array return collect($events)->map(function ($event) { /** @var array $event */ $event['diff'] = Carbon::parse($event['datetime'])->diffForHumans(); + return $event; })->toArray(); } diff --git a/src/TaskHandler.php b/src/TaskHandler.php index 5262a3f..1ad2bc2 100644 --- a/src/TaskHandler.php +++ b/src/TaskHandler.php @@ -9,6 +9,7 @@ use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; use Safe\Exceptions\JsonException; + use function Safe\json_decode; class TaskHandler @@ -47,8 +48,8 @@ public function handle(?string $task = null): void } /** - * @param string|array|null $task - * @return array + * @param string|array|null $task + * * @throws JsonException */ private function captureTask($task): array @@ -89,7 +90,7 @@ private function loadQueueConnectionConfiguration(array $task): void { $command = self::getCommandProperties($task['data']['command']); $connection = $command['connection'] ?? config('queue.default'); - $baseConfig = config('queue.connections.' . $connection); + $baseConfig = config('queue.connections.'.$connection); $config = (new CloudTasksConnector())->connect($baseConfig)->config; // The connection name from the config may not be the actual connection name diff --git a/tests/CloudTasksApiTest.php b/tests/CloudTasksApiTest.php index e8769e5..e78c275 100644 --- a/tests/CloudTasksApiTest.php +++ b/tests/CloudTasksApiTest.php @@ -27,8 +27,8 @@ protected function setUp(): void ]; foreach ($requiredEnvs as $env) { - if (!env($env)) { - $this->fail('Missing [' . $env . '] environment variable.'); + if (! env($env)) { + $this->fail('Missing ['.$env.'] environment variable.'); } } @@ -67,7 +67,7 @@ public function test_create_task() // Assert $this->assertMatchesRegularExpression( - '/projects\/' . env('CI_CLOUD_TASKS_PROJECT_ID') . '\/locations\/' . env('CI_CLOUD_TASKS_LOCATION') . '\/queues\/' . env('CI_CLOUD_TASKS_QUEUE') . '\/tasks\/\d+$/', + '/projects\/'.env('CI_CLOUD_TASKS_PROJECT_ID').'\/locations\/'.env('CI_CLOUD_TASKS_LOCATION').'\/queues\/'.env('CI_CLOUD_TASKS_QUEUE').'\/tasks\/\d+$/', $taskName ); } diff --git a/tests/CloudTasksDashboardTest.php b/tests/CloudTasksDashboardTest.php index 7dd759e..88c642a 100644 --- a/tests/CloudTasksDashboardTest.php +++ b/tests/CloudTasksDashboardTest.php @@ -2,7 +2,6 @@ namespace Tests; -use Google\Cloud\Tasks\V2\RetryConfig; use Illuminate\Routing\Route; use Illuminate\Routing\Router; use Illuminate\Support\Carbon; @@ -120,10 +119,10 @@ public function it_can_filter_only_failed_tasks() public function it_can_filter_tasks_created_at_exact_time() { // Arrange - factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(15,4, 59)]); - factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16,5, 0)]); - factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16,5, 59)]); - factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16,6, 0)]); + factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(15, 4, 59)]); + factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16, 5, 0)]); + factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16, 5, 59)]); + factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16, 6, 0)]); // Act $response = $this->getJson('/cloud-tasks-api/tasks?time=16:05'); @@ -138,9 +137,9 @@ public function it_can_filter_tasks_created_at_exact_time() public function it_can_filter_tasks_created_at_exact_hour() { // Arrange - factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(15,59, 59)]); - factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16,5, 59)]); - factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16,32, 32)]); + factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(15, 59, 59)]); + factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16, 5, 59)]); + factory(StackkitCloudTask::class)->create(['created_at' => now()->setTime(16, 32, 32)]); // Act $response = $this->getJson('/cloud-tasks-api/tasks?hour=16'); @@ -229,7 +228,7 @@ public function it_returns_info_about_a_specific_task() $task = factory(StackkitCloudTask::class)->create(); // Act - $response = $this->getJson('/cloud-tasks-api/task/' . $task->task_uuid); + $response = $this->getJson('/cloud-tasks-api/task/'.$task->task_uuid); // Assert $this->assertEquals($task->id, $response['id']); @@ -481,8 +480,8 @@ public function test_publish() $this->artisan('vendor:publish --tag=cloud-tasks --force') ->expectsOutputToContain('Publishing [cloud-tasks] assets.') - ->expectsOutputToContain('Copying file [' . $expectedPublishBase . '/config/cloud-tasks.php] to [config/cloud-tasks.php]') - ->expectsOutputToContain('Copying directory [' . $expectedPublishBase . '/dashboard/dist] to [public/vendor/cloud-tasks]'); + ->expectsOutputToContain('Copying file ['.$expectedPublishBase.'/config/cloud-tasks.php] to [config/cloud-tasks.php]') + ->expectsOutputToContain('Copying directory ['.$expectedPublishBase.'/dashboard/dist] to [public/vendor/cloud-tasks]'); } @@ -507,7 +506,7 @@ public function when_dashboard_is_enabled_it_adds_the_necessary_routes() */ public function when_dashboard_is_enabled_it_adds_the_necessary_migrations() { - $this->assertTrue(in_array(dirname(__DIR__) . '/src/../migrations', app('migrator')->paths())); + $this->assertTrue(in_array(dirname(__DIR__).'/src/../migrations', app('migrator')->paths())); } /** @@ -555,7 +554,7 @@ public function dashboard_is_password_protected() public function can_enter_with_token() { // Arrange - $this->defaultHeaders['Authorization'] = 'Bearer ' . encrypt(time() + 10); + $this->defaultHeaders['Authorization'] = 'Bearer '.encrypt(time() + 10); // Act $response = $this->getJson('/cloud-tasks-api/dashboard'); @@ -570,7 +569,7 @@ public function can_enter_with_token() public function token_can_expire() { // Arrange - $this->defaultHeaders['Authorization'] = 'Bearer ' . encrypt(Carbon::create(2020, 5, 15, 15, 15, 15)->timestamp); + $this->defaultHeaders['Authorization'] = 'Bearer '.encrypt(Carbon::create(2020, 5, 15, 15, 15, 15)->timestamp); // Act & Assert Carbon::setTestNow(Carbon::create(2020, 5, 15, 15, 15, 14)); diff --git a/tests/QueueTest.php b/tests/QueueTest.php index bcf4062..e459991 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -5,7 +5,6 @@ namespace Tests; use Google\Cloud\Tasks\V2\HttpMethod; -use Google\Cloud\Tasks\V2\RetryConfig; use Google\Cloud\Tasks\V2\Task; use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessing; @@ -239,11 +238,13 @@ public function jobs_can_be_released() $releasedJob = null; Event::assertDispatched(JobReleased::class, function (JobReleased $event) use (&$releasedJob) { $releasedJob = $event->job; + return true; }); CloudTasksApi::assertTaskCreated(function (Task $task) { $body = $task->getHttpRequest()->getBody(); $decoded = json_decode($body, true); + return $decoded['data']['commandName'] === 'Tests\\Support\\JobThatWillBeReleased' && $decoded['internal']['attempts'] === 1; }); @@ -254,6 +255,7 @@ public function jobs_can_be_released() CloudTasksApi::assertTaskCreated(function (Task $task) { $body = $task->getHttpRequest()->getBody(); $decoded = json_decode($body, true); + return $decoded['data']['commandName'] === 'Tests\\Support\\JobThatWillBeReleased' && $decoded['internal']['attempts'] === 2; }); @@ -404,10 +406,10 @@ public function test_queue_before_and_after_hooks() // Act Queue::before(function (JobProcessing $event) { - logger('Queue::before:' . $event->job->payload()['data']['commandName']); + logger('Queue::before:'.$event->job->payload()['data']['commandName']); }); Queue::after(function (JobProcessed $event) { - logger('Queue::after:' . $event->job->payload()['data']['commandName']); + logger('Queue::after:'.$event->job->payload()['data']['commandName']); }); $this->dispatch(new SimpleJob())->run(); @@ -482,7 +484,7 @@ public function it_adds_a_task_name_based_on_the_display_name() CloudTasksApi::assertTaskCreated(function (Task $task, string $queueName): bool { $uuid = \Safe\json_decode($task->getHttpRequest()->getBody(), true)['uuid']; - return $task->getName() === 'projects/my-test-project/locations/europe-west6/queues/barbequeue/tasks/Tests-Support-SimpleJob-' . $uuid . '-1685649757000'; + return $task->getName() === 'projects/my-test-project/locations/europe-west6/queues/barbequeue/tasks/Tests-Support-SimpleJob-'.$uuid.'-1685649757000'; }); } } diff --git a/tests/Support/EncryptedJob.php b/tests/Support/EncryptedJob.php index 8f8e4ff..f1d7d18 100644 --- a/tests/Support/EncryptedJob.php +++ b/tests/Support/EncryptedJob.php @@ -9,7 +9,7 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -class EncryptedJob implements ShouldQueue, ShouldBeEncrypted +class EncryptedJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; diff --git a/tests/Support/SimpleJob.php b/tests/Support/SimpleJob.php index 5eae133..1a1f6d0 100644 --- a/tests/Support/SimpleJob.php +++ b/tests/Support/SimpleJob.php @@ -7,7 +7,6 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Mail; class SimpleJob implements ShouldQueue { diff --git a/tests/Support/UserJob.php b/tests/Support/UserJob.php index de586e5..e6ed197 100644 --- a/tests/Support/UserJob.php +++ b/tests/Support/UserJob.php @@ -7,7 +7,6 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Mail; class UserJob implements ShouldQueue { @@ -29,6 +28,6 @@ public function __construct(User $user) */ public function handle() { - logger('UserJob:' . $this->user->name); + logger('UserJob:'.$this->user->name); } } diff --git a/tests/TaskHandlerTest.php b/tests/TaskHandlerTest.php index febbfdf..304ff9d 100644 --- a/tests/TaskHandlerTest.php +++ b/tests/TaskHandlerTest.php @@ -3,9 +3,7 @@ namespace Tests; use Firebase\JWT\ExpiredException; -use Google\Cloud\Tasks\V2\RetryConfig; use Google\Cloud\Tasks\V2\Task; -use Google\Protobuf\Duration; use Illuminate\Queue\Events\JobProcessed; use Illuminate\Queue\Events\JobProcessing; use Illuminate\Queue\Events\JobReleasedAfterException; @@ -16,7 +14,6 @@ use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksException; use Stackkit\LaravelGoogleCloudTasksQueue\LogFake; use Stackkit\LaravelGoogleCloudTasksQueue\OpenIdVerificator; -use Stackkit\LaravelGoogleCloudTasksQueue\StackkitCloudTask; use Stackkit\LaravelGoogleCloudTasksQueue\TaskHandler; use Tests\Support\EncryptedJob; use Tests\Support\FailingJob; @@ -37,6 +34,7 @@ protected function setUp(): void /** * @test + * * @testWith [true] * [false] */ @@ -58,6 +56,7 @@ public function it_returns_responses_for_empty_payloads($debug) /** * @test + * * @testWith [true] * [false] */ @@ -89,6 +88,7 @@ public function it_returns_responses_for_invalid_json($debug) /** * @test + * * @testWith ["{\"invalid\": \"data\"}"] * ["{\"data\": \"\"}"] * ["{\"data\": \"test\"}"] @@ -116,6 +116,7 @@ public function it_returns_responses_for_invalid_payloads(string $payload) /** * @test + * * @testWith [true] * [false] */ @@ -439,6 +440,7 @@ public function attempts_are_tracked_internally() Event::assertDispatched(JobReleasedAfterException::class, function ($event) use (&$releasedJob) { $releasedJob = $event->job->getRawBody(); + return $event->job->attempts() === 1; }); @@ -470,10 +472,12 @@ public function retried_jobs_get_a_new_name() CloudTasksApi::assertCreatedTaskCount(2); CloudTasksApi::assertTaskCreated(function (Task $task): bool { [$timestamp] = array_reverse(explode('-', $task->getName())); + return $timestamp === '1685035628000'; }); CloudTasksApi::assertTaskCreated(function (Task $task): bool { [$timestamp] = array_reverse(explode('-', $task->getName())); + return $timestamp === '1685035629000'; }); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 4455831..98ac4ae 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,7 +19,7 @@ class TestCase extends \Orchestra\Testbench\TestCase use DatabaseTransactions; /** - * @var CloudTasksClient $client + * @var CloudTasksClient */ public $client; @@ -29,9 +29,9 @@ protected function setUp(): void { parent::setUp(); - $this->withFactories(__DIR__ . '/../factories'); + $this->withFactories(__DIR__.'/../factories'); - $this->defaultHeaders['Authorization'] = 'Bearer ' . encrypt(time() + 10); + $this->defaultHeaders['Authorization'] = 'Bearer '.encrypt(time() + 10); Event::listen( JobReleasedAfterException::class, @@ -47,8 +47,7 @@ function ($event) { * In a normal app environment these would be added to the 'providers' array in * the config/app.php file. * - * @param \Illuminate\Foundation\Application $app - * + * @param \Illuminate\Foundation\Application $app * @return array */ protected function getPackageProviders($app) @@ -65,14 +64,14 @@ protected function getPackageProviders($app) */ protected function defineDatabaseMigrations() { - $this->loadMigrationsFrom(__DIR__ . '/../migrations'); - $this->loadMigrationsFrom(__DIR__ . '/../vendor/orchestra/testbench-core/laravel/migrations'); + $this->loadMigrationsFrom(__DIR__.'/../migrations'); + $this->loadMigrationsFrom(__DIR__.'/../vendor/orchestra/testbench-core/laravel/migrations'); } /** * Define environment setup. * - * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Foundation\Application $app * @return void */ protected function getEnvironmentSetUp($app) @@ -84,13 +83,13 @@ protected function getEnvironmentSetUp($app) $app['config']->set('database.default', 'testbench'); $port = env('DB_DRIVER') === 'mysql' ? 3307 : 5432; $app['config']->set('database.connections.testbench', [ - 'driver' => env('DB_DRIVER', 'mysql'), + 'driver' => env('DB_DRIVER', 'mysql'), 'host' => '127.0.0.1', 'port' => $port, 'database' => 'cloudtasks', 'username' => 'cloudtasks', 'password' => 'cloudtasks', - 'prefix' => '', + 'prefix' => '', ]); $app['config']->set('cache.default', 'file'); @@ -119,7 +118,7 @@ protected function getEnvironmentSetUp($app) protected function setConfigValue($key, $value) { - $this->app['config']->set('queue.connections.my-cloudtasks-connection.' . $key, $value); + $this->app['config']->set('queue.connections.my-cloudtasks-connection.'.$key, $value); } public function dispatch($job) @@ -139,9 +138,12 @@ public function dispatch($job) dispatch($job); - return new class($payload, $task, $this) { + return new class($payload, $task, $this) + { public string $payload; + public Task $task; + public TestCase $testCase; public function __construct(string $payload, Task $task, TestCase $testCase) @@ -210,7 +212,7 @@ public function assertTaskExists(string $taskId): void $this->assertInstanceOf(Task::class, $task); } catch (ApiException $e) { - $this->fail('Task [' . $taskId . '] should exist but it does not (or something else went wrong).'); + $this->fail('Task ['.$taskId.'] should exist but it does not (or something else went wrong).'); } } @@ -226,16 +228,15 @@ protected function addIdTokenToHeader(?Closure $closure = null): void $base = $closure($base); } - $privateKey = file_get_contents(__DIR__ . '/../tests/Support/self-signed-private-key.txt'); + $privateKey = file_get_contents(__DIR__.'/../tests/Support/self-signed-private-key.txt'); $token = JWT::encode($base, $privateKey, 'RS256', 'abc123'); - request()->headers->set('Authorization', 'Bearer ' . $token); + request()->headers->set('Authorization', 'Bearer '.$token); } protected function assertDatabaseCount($table, int $count, $connection = null) { $this->assertEquals($count, DB::connection($connection)->table($table)->count()); } - } From 41c0566188221d23d54886ea1a21e9da3da87356 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Mon, 12 Feb 2024 22:57:50 +0100 Subject: [PATCH 6/6] Add strict_types --- ...16_171140_create_stackkit_cloud_tasks_table.php | 2 ++ pint.json | 7 +++++++ src/Authenticate.php | 2 ++ src/CloudTasks.php | 2 ++ src/CloudTasksApiController.php | 4 +++- src/CloudTasksConnector.php | 2 ++ src/CloudTasksException.php | 2 ++ src/CloudTasksJob.php | 2 ++ src/CloudTasksQueue.php | 2 ++ src/CloudTasksServiceProvider.php | 2 ++ src/Config.php | 2 ++ src/DashboardService.php | 2 ++ src/Entities/StatRow.php | 2 ++ src/Errors.php | 2 ++ src/StackkitCloudTask.php | 2 ++ src/TaskHandler.php | 2 ++ src/TaskMetadata.php | 2 ++ tests/CloudTasksDashboardTest.php | 14 ++++++++------ tests/ConfigHandlerTest.php | 2 ++ tests/ConfigTest.php | 2 ++ tests/Support/EncryptedJob.php | 2 ++ tests/Support/FailingJob.php | 2 ++ tests/Support/FailingJobWithExponentialBackoff.php | 2 ++ tests/Support/FailingJobWithMaxTries.php | 2 ++ .../FailingJobWithMaxTriesAndRetryUntil.php | 2 ++ tests/Support/FailingJobWithRetryUntil.php | 2 ++ tests/Support/JobThatWillBeReleased.php | 2 ++ tests/Support/SimpleJob.php | 2 ++ tests/Support/UserJob.php | 2 ++ tests/TaskHandlerTest.php | 2 ++ tests/TestCase.php | 2 ++ 31 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 pint.json diff --git a/migrations/2021_10_16_171140_create_stackkit_cloud_tasks_table.php b/migrations/2021_10_16_171140_create_stackkit_cloud_tasks_table.php index 2455a5a..a78f17c 100644 --- a/migrations/2021_10_16_171140_create_stackkit_cloud_tasks_table.php +++ b/migrations/2021_10_16_171140_create_stackkit_cloud_tasks_table.php @@ -1,5 +1,7 @@ map(function (StackkitCloudTask $task) use ($maxId) { return [ 'uuid' => $task->task_uuid, - 'id' => str_pad((string) $task->id, strlen($maxId), '0', STR_PAD_LEFT), + 'id' => str_pad((string) $task->id, strlen((string) $maxId), '0', STR_PAD_LEFT), 'name' => $task->name, 'status' => $task->status, 'attempts' => $task->getNumberOfAttempts(), diff --git a/src/CloudTasksConnector.php b/src/CloudTasksConnector.php index db81cd6..0eec81b 100644 --- a/src/CloudTasksConnector.php +++ b/src/CloudTasksConnector.php @@ -1,5 +1,7 @@