diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0d9d3b0..0532e49 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -32,7 +32,7 @@ jobs: - name: Install dependencies run: | composer config allow-plugins.pestphp/pest-plugin true - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" pestphp/pest --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: Execute tests run: XDEBUG_MODE=coverage php vendor/bin/pest --coverage --min=100 diff --git a/CHANGELOG.md b/CHANGELOG.md index 115009a..7f817e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -[Unreleased changes](https://github.com/justbetter/laravel-magento-async/compare/1.0.2...main) +[Unreleased changes](https://github.com/justbetter/laravel-magento-async/compare/1.0.3...main) +## [1.0.3](https://github.com/justbetter/laravel-magento-async/releases/tag/1.0.3) - 2024-08-28 + +### What's Changed +* Adjust bulk request cleanup by @VincentBean in https://github.com/justbetter/laravel-magento-async/pull/3 + + +**Full Changelog**: https://github.com/justbetter/laravel-magento-async/compare/1.0.2...1.0.3 + ## [1.0.2](https://github.com/justbetter/laravel-magento-async/releases/tag/1.0.2) - 2024-08-27 ### What's Changed diff --git a/composer.json b/composer.json index 07feefd..ec9b6fd 100644 --- a/composer.json +++ b/composer.json @@ -11,11 +11,12 @@ "justbetter/laravel-magento-webhooks": "^2.1" }, "require-dev": { - "laravel/pint": "^1.16", "larastan/larastan": "^2.5", + "laravel/pint": "^1.16", + "orchestra/testbench": "^9.0", + "pestphp/pest": "^2.0", "phpstan/phpstan-mockery": "^1.1", - "phpunit/phpunit": "^10.0", - "orchestra/testbench": "^9.0" + "phpunit/phpunit": "^10.0" }, "authors": [ { @@ -51,7 +52,10 @@ "fix-style": "pint" }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true + } }, "extra": { "laravel": { diff --git a/database/migrations/2024_09_18_113000_magento_bulk_requests_method_field.php b/database/migrations/2024_09_18_113000_magento_bulk_requests_method_field.php new file mode 100644 index 0000000..b677230 --- /dev/null +++ b/database/migrations/2024_09_18_113000_magento_bulk_requests_method_field.php @@ -0,0 +1,20 @@ +string('method')->after('store_code'); + }); + } + + public function down(): void + { + Schema::dropColumns('magento_bulk_requests', ['method']); + } +}; diff --git a/database/migrations/2024_09_18_140000_magento_bulk_requests_retry_of_field.php b/database/migrations/2024_09_18_140000_magento_bulk_requests_retry_of_field.php new file mode 100644 index 0000000..5e5fef0 --- /dev/null +++ b/database/migrations/2024_09_18_140000_magento_bulk_requests_retry_of_field.php @@ -0,0 +1,22 @@ +unsignedBigInteger('retry_of')->after('id')->nullable(); + + $table->foreign('retry_of')->references('id')->on('magento_bulk_requests')->onDelete('set null'); + }); + } + + public function down(): void + { + Schema::dropColumns('magento_bulk_requests', ['retry_of']); + } +}; diff --git a/src/Actions/RetryBulkRequest.php b/src/Actions/RetryBulkRequest.php new file mode 100644 index 0000000..119e153 --- /dev/null +++ b/src/Actions/RetryBulkRequest.php @@ -0,0 +1,66 @@ + $payload */ + $payload = []; + /** @var array $subjects */ + $subjects = []; + + $operations = $bulkRequest->operations; + + foreach ($bulkRequest->request as $index => $request) { + + /** @var BulkOperation $operation */ + $operation = $operations->where('operation_id', '=', $index)->firstOrFail(); + + if ($onlyFailed && ! in_array($operation->status, OperationStatus::failedStatuses())) { + continue; + } + + $payload[] = $request; + $subjects[] = $operation->subject; + } + + if ($payload === []) { + return null; + } + + $pendingRequest = $this->client + ->configure(fn (Magento $client): Magento => $client->store($bulkRequest->store_code)) + ->subjects($subjects); + + $retry = match ($bulkRequest->method) { + 'POST' => $pendingRequest->postBulk($bulkRequest->path, $payload), + 'PUT' => $pendingRequest->putBulk($bulkRequest->path, $payload), + 'DELETE' => $pendingRequest->deleteBulk($bulkRequest->path, $payload), + default => throw new InvalidMethodException('Unsupported method "'.$bulkRequest->method.'"'), + }; + + if ($retry !== null) { + $bulkRequest->retries()->save($retry); + } + + return $retry; + } + + public static function bind(): void + { + app()->singleton(RetriesBulkRequest::class, static::class); + } +} diff --git a/src/Client/MagentoAsync.php b/src/Client/MagentoAsync.php index d4ed710..7e34cde 100644 --- a/src/Client/MagentoAsync.php +++ b/src/Client/MagentoAsync.php @@ -21,7 +21,7 @@ class MagentoAsync protected ?Model $subject = null; - /** @var array */ + /** @var array */ protected array $subjects = []; public function __construct( @@ -70,7 +70,7 @@ public function subject(Model $subject): static return $this; } - /** @param array $subjects */ + /** @param array $subjects */ public function subjects(array $subjects): static { $this->subjects = $subjects; @@ -78,60 +78,61 @@ public function subjects(array $subjects): static return $this; } - /** @param array $data */ + /** @param array $data */ public function post(string $path, array $data = []): BulkRequest { $response = $this->magento->postAsync($path, $data); - return $this->processResponse($response, $path, $data); + return $this->processResponse($response, 'POST', $path, $data); } - /** @param array $data */ + /** @param array $data */ public function postBulk(string $path, array $data = []): BulkRequest { $response = $this->magento->postBulk($path, $data); - return $this->processResponse($response, $path, $data, true); + return $this->processResponse($response, 'POST', $path, $data, true); } - /** @param array $data */ + /** @param array $data */ public function put(string $path, array $data = []): BulkRequest { $response = $this->magento->putAsync($path, $data); - return $this->processResponse($response, $path, $data); + return $this->processResponse($response, 'PUT', $path, $data); } - /** @param array $data */ + /** @param array $data */ public function putBulk(string $path, array $data = []): BulkRequest { $response = $this->magento->putBulk($path, $data); - return $this->processResponse($response, $path, $data, true); + return $this->processResponse($response, 'PUT', $path, $data, true); } - /** @param array $data */ + /** @param array $data */ public function delete(string $path, array $data = []): BulkRequest { $response = $this->magento->deleteAsync($path, $data); - return $this->processResponse($response, $path, $data); + return $this->processResponse($response, 'DELETE', $path, $data); } - /** @param array $data */ + /** @param array $data */ public function deleteBulk(string $path, array $data = []): BulkRequest { $response = $this->magento->deleteBulk($path, $data); - return $this->processResponse($response, $path, $data, true); + return $this->processResponse($response, 'DELETE', $path, $data, true); } - /** @param array $data */ + /** @param array $data */ public function processResponse( Response $response, + string $method, string $path, array $data = [], - bool $bulk = false + bool $bulk = false, ): BulkRequest { $response->throw(); @@ -141,16 +142,17 @@ public function processResponse( /** @var array> $requestItems */ $requestItems = $response->json('request_items', []); - $response = $response->json(null, []); + $responseData = $response->json(null, []); /** @var BulkRequest $bulkRequest */ $bulkRequest = BulkRequest::query()->create([ 'magento_connection' => $this->magento->connection, 'store_code' => $this->magento->storeCode ?? 'all', + 'method' => $method, 'path' => $path, 'bulk_uuid' => $bulkUuid, 'request' => $data, - 'response' => $response, + 'response' => $responseData, ]); foreach ($requestItems as $index => $requestItem) { diff --git a/src/Commands/RetryBulkRequestCommand.php b/src/Commands/RetryBulkRequestCommand.php new file mode 100644 index 0000000..56ee59c --- /dev/null +++ b/src/Commands/RetryBulkRequestCommand.php @@ -0,0 +1,38 @@ +argument('id'); + + /** @var bool $onlyFailed */ + $onlyFailed = $this->option('only-failed'); + + /** @var BulkRequest $request */ + $request = BulkRequest::query()->findOrFail($id); + + $bulkRequest = $contract->retry($request, $onlyFailed); + + if ($bulkRequest === null) { + $this->error('Failed to retry bulk request'); + + return static::FAILURE; + } + + $this->info('Retried with bulk uuid "'.$bulkRequest->bulk_uuid.'"'); + + return static::SUCCESS; + } +} diff --git a/src/Contracts/RetriesBulkRequest.php b/src/Contracts/RetriesBulkRequest.php new file mode 100644 index 0000000..290f375 --- /dev/null +++ b/src/Contracts/RetriesBulkRequest.php @@ -0,0 +1,10 @@ + */ + public static function failedStatuses(): array + { + return [ + OperationStatus::RetriablyFailed, + OperationStatus::NotRetriablyFailed, + ]; + } } diff --git a/src/Exceptions/InvalidMethodException.php b/src/Exceptions/InvalidMethodException.php new file mode 100644 index 0000000..fb4725c --- /dev/null +++ b/src/Exceptions/InvalidMethodException.php @@ -0,0 +1,7 @@ + $operations + * @property Collection $retries + * @property ?BulkRequest $retryOf */ class BulkRequest extends Model { @@ -37,4 +42,16 @@ public function operations(): HasMany { return $this->hasMany(BulkOperation::class); } + + /** @return HasMany */ + public function retries(): HasMany + { + return $this->hasMany(BulkRequest::class, 'retry_of', 'id'); + } + + /** @return BelongsTo */ + public function retryOf(): BelongsTo + { + return $this->belongsTo(BulkRequest::class, 'retry_of', 'id'); + } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index ab5a01e..96668e2 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -4,9 +4,11 @@ use Illuminate\Support\ServiceProvider as BaseServiceProvider; use JustBetter\MagentoAsync\Actions\CleanBulkRequests; +use JustBetter\MagentoAsync\Actions\RetryBulkRequest; use JustBetter\MagentoAsync\Actions\UpdateBulkStatus; use JustBetter\MagentoAsync\Actions\UpdateBulkStatuses; use JustBetter\MagentoAsync\Commands\CleanBulkRequestsCommand; +use JustBetter\MagentoAsync\Commands\RetryBulkRequestCommand; use JustBetter\MagentoAsync\Commands\UpdateBulkStatusCommand; use JustBetter\MagentoAsync\Commands\UpdateBulkStatusesCommand; @@ -31,6 +33,7 @@ protected function registerActions(): static UpdateBulkStatus::bind(); UpdateBulkStatuses::bind(); CleanBulkRequests::bind(); + RetryBulkRequest::bind(); return $this; } @@ -66,6 +69,7 @@ protected function bootCommands(): static UpdateBulkStatusCommand::class, UpdateBulkStatusesCommand::class, CleanBulkRequestsCommand::class, + RetryBulkRequestCommand::class, ]); } diff --git a/tests/Actions/CleanBulkRequestsTest.php b/tests/Actions/CleanBulkRequestsTest.php index 8745ac4..b64d022 100644 --- a/tests/Actions/CleanBulkRequestsTest.php +++ b/tests/Actions/CleanBulkRequestsTest.php @@ -19,6 +19,7 @@ public function it_deletes_completed_operations(): void $request = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid-1::', 'request' => [], @@ -60,6 +61,7 @@ public function it_deletes_request(Carbon $requestCreatedAt, array $operations, $request = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid-1::', 'request' => [], diff --git a/tests/Actions/RetryBulkRequestTest.php b/tests/Actions/RetryBulkRequestTest.php new file mode 100644 index 0000000..216b72c --- /dev/null +++ b/tests/Actions/RetryBulkRequestTest.php @@ -0,0 +1,146 @@ + Http::response([ + 'bulk_uuid' => 'new-uuid', + ]), + ])->preventStrayRequests(); + + /** @var BulkRequest $request */ + $request = BulkRequest::query()->create([ + 'magento_connection' => '::magento-connection::', + 'store_code' => '::store-code::', + 'method' => $method, + 'path' => '::path::', + 'bulk_uuid' => '::bulk-uuid-1::', + 'request' => [ + [ + 'call-1', + ], + [ + 'call-2', + ], + [ + 'call-3', + ], + ], + 'response' => [], + 'created_at' => now(), + ]); + + $request->operations()->create([ + 'operation_id' => 0, + 'status' => OperationStatus::Complete, + 'updated_at' => now()->subHours(2), + ]); + + $request->operations()->create([ + 'operation_id' => 1, + 'status' => OperationStatus::RetriablyFailed, + 'subject_id' => $request->id, + 'subject_type' => get_class($request), + ]); + + $request->operations()->create([ + 'operation_id' => 2, + 'status' => OperationStatus::Open, + ]); + + /** @var RetryBulkRequest $action */ + $action = app(RetryBulkRequest::class); + + $retry = $action->retry($request, true); + + $this->assertNotNull($retry); + $this->assertEquals('::path::', $retry->path); + $this->assertEquals($method, $retry->method); + $this->assertEquals([['call-2']], $retry->request); + $this->assertEquals($request->id, $retry->retry_of); + } + + /** @return array> */ + public static function httpMethodProvider(): array + { + return [ + ['method' => 'POST'], + ['method' => 'PUT'], + ['method' => 'DELETE'], + ]; + } + + #[Test] + public function it_does_nothing_without_payload(): void + { + Http::fake()->preventStrayRequests(); + + /** @var BulkRequest $request */ + $request = BulkRequest::query()->create([ + 'magento_connection' => '::magento-connection::', + 'store_code' => '::store-code::', + 'method' => 'POST', + 'path' => '::path::', + 'bulk_uuid' => '::bulk-uuid-1::', + 'request' => [], + 'response' => [], + 'created_at' => now(), + ]); + + /** @var RetryBulkRequest $action */ + $action = app(RetryBulkRequest::class); + + $result = $action->retry($request, false); + $this->assertNull($result); + + Http::assertNothingSent(); + } + + #[Test] + public function it_throws_exception_without_method(): void + { + Http::fake()->preventStrayRequests(); + + /** @var BulkRequest $request */ + $request = BulkRequest::query()->create([ + 'magento_connection' => '::magento-connection::', + 'store_code' => '::store-code::', + 'method' => '', + 'path' => '::path::', + 'bulk_uuid' => '::bulk-uuid-1::', + 'request' => [['call-1']], + 'response' => [], + 'created_at' => now(), + ]); + + $request->operations()->create([ + 'operation_id' => 0, + 'status' => OperationStatus::Complete, + 'updated_at' => now()->subHours(2), + ]); + + /** @var RetryBulkRequest $action */ + $action = app(RetryBulkRequest::class); + + $this->expectException(InvalidMethodException::class); + $action->retry($request, false); + } +} diff --git a/tests/Actions/UpdateBulkStatusTest.php b/tests/Actions/UpdateBulkStatusTest.php index e00ff30..b264cd7 100644 --- a/tests/Actions/UpdateBulkStatusTest.php +++ b/tests/Actions/UpdateBulkStatusTest.php @@ -42,6 +42,7 @@ public function it_can_update_bulk_statuses(): void $bulkRequest = BulkRequest::query()->create([ 'magento_connection' => 'default', 'store_code' => 'all', + 'method' => 'POST', 'path' => 'products', 'bulk_uuid' => '4c51f869-0a77-4238-be2a-290dd5081666', 'request' => [], @@ -96,6 +97,7 @@ public function it_can_skip_the_started_at(): void $bulkRequest = BulkRequest::query()->create([ 'magento_connection' => 'default', 'store_code' => 'all', + 'method' => 'POST', 'path' => 'products', 'bulk_uuid' => '4c51f869-0a77-4238-be2a-290dd5081666', 'request' => [], diff --git a/tests/Actions/UpdateBulkStatusesTest.php b/tests/Actions/UpdateBulkStatusesTest.php index 933f9ed..4631aba 100644 --- a/tests/Actions/UpdateBulkStatusesTest.php +++ b/tests/Actions/UpdateBulkStatusesTest.php @@ -21,6 +21,7 @@ public function it_can_update_bulk_statuses(): void $status1 = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid-1::', 'request' => [], @@ -31,6 +32,7 @@ public function it_can_update_bulk_statuses(): void $status2 = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid-2::', 'request' => [], @@ -41,6 +43,7 @@ public function it_can_update_bulk_statuses(): void $status3 = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid-3::', 'request' => [], diff --git a/tests/Client/MagentoAsyncTest.php b/tests/Client/MagentoAsyncTest.php index a5789c8..9570856 100644 --- a/tests/Client/MagentoAsyncTest.php +++ b/tests/Client/MagentoAsyncTest.php @@ -37,6 +37,7 @@ public function it_can_process_responses(): void $subject = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid::', 'request' => [], @@ -106,6 +107,7 @@ public function it_can_process_bulk_responses(): void BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid-1::', 'request' => [], @@ -114,6 +116,7 @@ public function it_can_process_bulk_responses(): void BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid-2::', 'request' => [], diff --git a/tests/Commands/RetryBulkRequestCommandTest.php b/tests/Commands/RetryBulkRequestCommandTest.php new file mode 100644 index 0000000..fc690bd --- /dev/null +++ b/tests/Commands/RetryBulkRequestCommandTest.php @@ -0,0 +1,78 @@ +create([ + 'magento_connection' => '::magento-connection::', + 'store_code' => '::store-code::', + 'method' => 'POST', + 'path' => '::path::', + 'bulk_uuid' => '::bulk-uuid-1::', + 'request' => [], + 'response' => [], + 'created_at' => now(), + ]); + + $this->mock(RetriesBulkRequest::class, function (MockInterface $mock) use ($request): void { + $mock->shouldReceive('retry') + ->withArgs(function (BulkRequest $bulkRequest, bool $onlyFailed) use ($request) { + return $bulkRequest->id === $request->id && $onlyFailed; + })->once() + ->andReturn($request); + }); + + /** @var PendingCommand $result */ + $result = $this->artisan(RetryBulkRequestCommand::class, [ + 'id' => $request->id, + '--only-failed' => true, + ]); + + $result->assertSuccessful(); + } + + #[Test] + public function it_fails_without_retry(): void + { + /* @var BulkRequest $request */ + $request = BulkRequest::query()->create([ + 'magento_connection' => '::magento-connection::', + 'store_code' => '::store-code::', + 'method' => 'POST', + 'path' => '::path::', + 'bulk_uuid' => '::bulk-uuid-1::', + 'request' => [], + 'response' => [], + 'created_at' => now(), + ]); + + $this->mock(RetriesBulkRequest::class, function (MockInterface $mock) use ($request): void { + $mock->shouldReceive('retry') + ->withArgs(function (BulkRequest $bulkRequest, bool $onlyFailed) use ($request) { + return $bulkRequest->id === $request->id && $onlyFailed; + })->once() + ->andReturnNull(); + }); + + /** @var PendingCommand $result */ + $result = $this->artisan(RetryBulkRequestCommand::class, [ + 'id' => $request->id, + '--only-failed' => true, + ]); + + $result->assertFailed(); + } +} diff --git a/tests/Commands/UpdateBulkStatusCommandTest.php b/tests/Commands/UpdateBulkStatusCommandTest.php index 88e43d1..7c58dce 100644 --- a/tests/Commands/UpdateBulkStatusCommandTest.php +++ b/tests/Commands/UpdateBulkStatusCommandTest.php @@ -21,6 +21,7 @@ public function it_can_dispatch_jobs(): void $bulkRequest = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid::', 'request' => [], diff --git a/tests/Jobs/UpdateBulkStatusJobTest.php b/tests/Jobs/UpdateBulkStatusJobTest.php index 9c7a4ce..6ffe02c 100644 --- a/tests/Jobs/UpdateBulkStatusJobTest.php +++ b/tests/Jobs/UpdateBulkStatusJobTest.php @@ -25,6 +25,7 @@ public function it_can_update_bulk_statuses(): void $bulkRequest = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid::', 'request' => [], diff --git a/tests/Listeners/BulkOperationStatusListenerTest.php b/tests/Listeners/BulkOperationStatusListenerTest.php index 581f72b..f50ac52 100644 --- a/tests/Listeners/BulkOperationStatusListenerTest.php +++ b/tests/Listeners/BulkOperationStatusListenerTest.php @@ -19,6 +19,7 @@ public function it_can_handle_a_status_change(): void $request = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid::', 'request' => [], @@ -52,6 +53,7 @@ public function it_can_skip_executing(): void $request = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid::', 'request' => [], diff --git a/tests/Models/BulkOperationTest.php b/tests/Models/BulkOperationTest.php index 0dce7a7..b6ae9a8 100644 --- a/tests/Models/BulkOperationTest.php +++ b/tests/Models/BulkOperationTest.php @@ -16,6 +16,7 @@ public function it_is_linked_to_a_request_and_can_have_a_subject(): void $request = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid::', 'request' => [], diff --git a/tests/Models/BulkRequestTest.php b/tests/Models/BulkRequestTest.php index 84e8fb5..a4325a5 100644 --- a/tests/Models/BulkRequestTest.php +++ b/tests/Models/BulkRequestTest.php @@ -15,6 +15,7 @@ public function it_can_have_operations(): void $request = BulkRequest::query()->create([ 'magento_connection' => '::magento-connection::', 'store_code' => '::store-code::', + 'method' => 'POST', 'path' => '::path::', 'bulk_uuid' => '::bulk-uuid::', 'request' => [], @@ -35,4 +36,34 @@ public function it_can_have_operations(): void $this->assertCount(3, $request->operations); } + + #[Test] + public function it_has_retry_relationship(): void + { + /** @var BulkRequest $request */ + $request = BulkRequest::query()->create([ + 'magento_connection' => '::magento-connection::', + 'store_code' => '::store-code::', + 'method' => 'POST', + 'path' => '::path::', + 'bulk_uuid' => '::bulk-uuid::', + 'request' => [], + 'response' => [], + ]); + + /** @var BulkRequest $retry */ + $retry = BulkRequest::query()->create([ + 'retry_of' => $request->id, + 'magento_connection' => '::magento-connection::', + 'store_code' => '::store-code::', + 'method' => 'POST', + 'path' => '::path::', + 'bulk_uuid' => '::bulk-uuid::', + 'request' => [], + 'response' => [], + ]); + + $this->assertEquals($retry->id, $request->retries->first()?->id); + $this->assertEquals($request->id, $retry->retryOf?->id); + } }