strategy = new FibonacciRetryStrategy(); } public function testIsRetryableForTransientError(): void { $envelope = new Envelope(new stdClass()); $throwable = new TransportException('SMTP timeout'); $this->assertTrue($this->strategy->isRetryable($envelope, $throwable)); } public function testIsNotRetryableForPermanentError(): void { $envelope = new Envelope(new stdClass()); $throwable = new LoaderError('Template not found'); $this->assertFalse($this->strategy->isRetryable($envelope, $throwable)); } public function testIsNotRetryableForInvalidArgumentException(): void { $envelope = new Envelope(new stdClass()); $throwable = new InvalidArgumentException('Invalid email'); $this->assertFalse($this->strategy->isRetryable($envelope, $throwable)); } public function testIsNotRetryableForLogicException(): void { $envelope = new Envelope(new stdClass()); $throwable = new LogicException('Logic error'); $this->assertFalse($this->strategy->isRetryable($envelope, $throwable)); } public function testIsNotRetryableWhenMaxRetriesReached(): void { $envelope = new Envelope(new stdClass(), [new RedeliveryStamp(7)]); $throwable = new TransportException('SMTP timeout'); $this->assertFalse($this->strategy->isRetryable($envelope, $throwable)); } public function testIsRetryableAtRetry6(): void { $envelope = new Envelope(new stdClass(), [new RedeliveryStamp(6)]); $throwable = new TransportException('SMTP timeout'); $this->assertTrue($this->strategy->isRetryable($envelope, $throwable)); } public function testIsRetryableWithNullThrowable(): void { $envelope = new Envelope(new stdClass()); $this->assertTrue($this->strategy->isRetryable($envelope, null)); } /** * @dataProvider fibonacciSequenceProvider */ public function testFibonacciWaitingTimeSequence(int $retryCount, int $expectedDelayMs): void { $envelope = new Envelope(new stdClass()); if ($retryCount > 0) { $envelope = $envelope->with(new RedeliveryStamp($retryCount)); } $this->assertSame($expectedDelayMs, $this->strategy->getWaitingTime($envelope)); } /** * @return iterable */ public static function fibonacciSequenceProvider(): iterable { yield 'retry 0 → 1s' => [0, 1000]; yield 'retry 1 → 1s' => [1, 1000]; yield 'retry 2 → 2s' => [2, 2000]; yield 'retry 3 → 3s' => [3, 3000]; yield 'retry 4 → 5s' => [4, 5000]; yield 'retry 5 → 8s' => [5, 8000]; yield 'retry 6 → 13s' => [6, 13000]; } public function testIsRetryableWithRuntimeException(): void { $envelope = new Envelope(new stdClass()); $throwable = new RuntimeException('Connection refused'); $this->assertTrue($this->strategy->isRetryable($envelope, $throwable)); } public function testIsRetryableWithWrappedTransientError(): void { $envelope = new Envelope(new stdClass()); $inner = new TransportException('SMTP timeout'); $throwable = new RuntimeException('Wrapped', 0, $inner); $this->assertTrue($this->strategy->isRetryable($envelope, $throwable)); } public function testIsNotRetryableWithWrappedPermanentError(): void { $envelope = new Envelope(new stdClass()); $inner = new LoaderError('Template not found'); $throwable = new RuntimeException('Wrapped', 0, $inner); $this->assertFalse($this->strategy->isRetryable($envelope, $throwable)); } }