fix(social): publish retry honors PlatformUnavailable too

Edge case from the prior commits: if the publisher throws TokenExpired
(401 path), PublishToSocialPlatform attempts refreshAccountToken() to
recover. That internally goes through ConnectionVerifier::verify, which
can now raise PlatformUnavailable (5xx). The old catch (\Throwable)
swallowed it but the loop still fell through to markAsTokenExpired —
meaning a transient platform outage during a retry could still flip the
account to expired.

Adds an explicit PlatformUnavailable catch in the retry block: marks
the post failed with category platform_unavailable and breaks before
touching the account status.
This commit is contained in:
Paulo Castellano 2026-05-19 09:47:12 -03:00
parent 04975020e4
commit a42962c0ca
2 changed files with 43 additions and 0 deletions

View file

@ -130,6 +130,19 @@ public function handle(): void
$this->refreshAccountToken();
continue;
} catch (PlatformUnavailableException $refreshError) {
Log::warning('Publish skipped: platform unavailable during retry refresh', [
'post_platform_id' => $this->postPlatform->id,
'platform' => $this->postPlatform->platform->value,
'error' => $refreshError->getMessage(),
]);
$this->postPlatform->markAsFailed($refreshError->getMessage(), [
'category' => 'platform_unavailable',
'http_status' => $refreshError->httpStatus,
'failed_at' => now()->toIso8601String(),
]);
break;
} catch (\Throwable $refreshError) {
Log::error('Token refresh failed during publish retry', [
'post_platform_id' => $this->postPlatform->id,

View file

@ -110,6 +110,36 @@
expect($this->socialAccount->status)->toBe(AccountStatus::TokenExpired);
});
test('publish does NOT mark account expired when retry refresh hits platform unavailable', function () {
Event::fake();
Mail::fake();
// Publisher first throws TokenExpired (401-style), the retry-refresh
// path goes through ConnectionVerifier::verify which can in turn raise
// PlatformUnavailable if the platform is down. The account must stay
// Connected — it was the platform that failed, not the token.
$publisher = Mockery::mock(LinkedInPublisher::class);
$publisher->shouldReceive('publish')->andThrow(new TokenExpiredException('Token expired', '401'));
$verifier = Mockery::mock(ConnectionVerifier::class);
$verifier->shouldReceive('verify')->andThrow(
new PlatformUnavailableException('LinkedIn API returned 503 during token refresh', 503)
);
$this->app->instance(LinkedInPublisher::class, $publisher);
$this->app->instance(ConnectionVerifier::class, $verifier);
(new PublishToSocialPlatform($this->postPlatform))->handle();
$this->postPlatform->refresh();
$this->socialAccount->refresh();
expect($this->postPlatform->status)->toBe(PlatformStatus::Failed);
expect($this->postPlatform->error_context['category'] ?? null)->toBe('platform_unavailable');
expect($this->postPlatform->error_context['http_status'] ?? null)->toBe(503);
expect($this->socialAccount->status)->toBe(AccountStatus::Connected);
});
test('publish to social platform does NOT mark account expired when platform is unavailable', function () {
Event::fake();
Mail::fake();