approved()->create(); $this->freezeTime(function () use ($routeArticle) { $routeArticle->recordPublishAttemptFailed(); $this->assertSame(1, $routeArticle->publish_attempts); $this->assertSame( now()->addMinutes(5)->format('Y-m-d H:i:s'), $routeArticle->next_attempt_at->format('Y-m-d H:i:s') ); }); } public function test_backoff_grows_with_each_failure(): void { /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->approved()->create(); $this->freezeTime(function () use ($routeArticle) { foreach ([5, 30, 120, 360] as $expectedDelay) { $routeArticle->recordPublishAttemptFailed(); $this->assertSame( now()->addMinutes($expectedDelay)->format('Y-m-d H:i:s'), $routeArticle->next_attempt_at->format('Y-m-d H:i:s'), "Attempt {$routeArticle->publish_attempts} should wait {$expectedDelay} minutes" ); } }); } public function test_attempts_are_exhausted_after_the_configured_maximum(): void { /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->approved()->create(); for ($i = 0; $i < RouteArticle::MAX_PUBLISH_ATTEMPTS - 1; $i++) { $routeArticle->recordPublishAttemptFailed(); $this->assertFalse($routeArticle->hasExhaustedPublishAttempts()); } $routeArticle->recordPublishAttemptFailed(); $this->assertTrue($routeArticle->hasExhaustedPublishAttempts()); } public function test_a_successful_publish_clears_previous_attempts(): void { /** @var RouteArticle $routeArticle */ $routeArticle = RouteArticle::factory()->approved()->create(); $routeArticle->recordPublishAttemptFailed(); $routeArticle->recordPublishAttemptFailed(); $routeArticle->clearPublishAttempts(); $this->assertSame(0, $routeArticle->publish_attempts); $this->assertNull($routeArticle->next_attempt_at); $this->assertFalse($routeArticle->hasExhaustedPublishAttempts()); } }