'bot-token']); Http::fake(['api.telegram.org/*' => Http::response(['ok' => true], 200)]); (new SendTelegramMessage('-100', 'hello'))->handle(app(TelegramNotifier::class)); Http::assertSentCount(1); } #[Test] public function timeout_throws_so_the_queue_retries(): void { config(['coruna.telegram.bot_token' => 'bot-token']); Http::fake(function () { throw new \Illuminate\Http\Client\ConnectionException( 'cURL error 28: Failed to connect to api.telegram.org port 443: Connection timed out' ); }); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('cURL error 28'); (new SendTelegramMessage('-100', 'hello'))->handle(app(TelegramNotifier::class)); } #[Test] public function chat_not_found_does_not_throw(): void { config(['coruna.telegram.bot_token' => 'bot-token']); Http::fake([ 'api.telegram.org/*' => Http::response([ 'ok' => false, 'description' => 'Bad Request: chat not found', ], 400), ]); (new SendTelegramMessage('-100', 'hello'))->handle(app(TelegramNotifier::class)); Http::assertSentCount(1); } #[Test] public function backoff_is_10s_then_1m_then_5m(): void { $this->assertSame([10, 60, 300], (new SendTelegramMessage('-1', 'x'))->backoff()); $this->assertSame(4, (new SendTelegramMessage('-1', 'x'))->tries); } #[Test] #[DataProvider('retryableErrors')] public function classifies_retryable_errors(string $error, bool $retryable): void { $this->assertSame($retryable, TelegramNotifier::isRetryableSendError($error)); } /** @return array */ public static function retryableErrors(): array { return [ 'curl timeout' => ['cURL error 28: Failed to connect to api.telegram.org port 443: Connection timed out', true], 'too many' => ['Too Many Requests: retry after 12', true], 'http 502' => ['HTTP 502', true], 'chat missing' => ['Bad Request: chat not found', false], 'kicked' => ['Forbidden: bot was kicked from the group chat', false], 'blocked' => ['Forbidden: bot was blocked by the user', false], 'no token' => ['未配置 Bot Token', false], ]; } }