feat: add LinkedIn, X, Threads publish exceptions
This commit is contained in:
parent
9999335da6
commit
92ef7428b1
6 changed files with 572 additions and 0 deletions
64
app/Exceptions/Social/LinkedInPublishException.php
Normal file
64
app/Exceptions/Social/LinkedInPublishException.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Social;
|
||||
|
||||
use App\Exceptions\TokenExpiredException;
|
||||
use Illuminate\Http\Client\Response;
|
||||
|
||||
class LinkedInPublishException extends SocialPublishException
|
||||
{
|
||||
public static function fromApiResponse(mixed $response): static
|
||||
{
|
||||
/** @var Response $response */
|
||||
$body = $response->json();
|
||||
$rawResponse = $response->body();
|
||||
$statusCode = $response->status();
|
||||
|
||||
$errorMessage = data_get($body, 'message', $rawResponse);
|
||||
|
||||
if ($statusCode === 401) {
|
||||
throw new TokenExpiredException(
|
||||
message: $errorMessage ?? 'Access token has expired or been revoked',
|
||||
platformErrorCode: (string) $statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
if (str_contains((string) $rawResponse, 'Unable to obtain activity')) {
|
||||
return new static(
|
||||
userMessage: 'LinkedIn server error. Please try again.',
|
||||
category: ErrorCategory::ServerError,
|
||||
platformErrorCode: (string) $statusCode,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
if (str_contains((string) $rawResponse, 'resource is forbidden')) {
|
||||
return new static(
|
||||
userMessage: 'Not authorized to post to this account.',
|
||||
category: ErrorCategory::Permission,
|
||||
platformErrorCode: (string) $statusCode,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
[$message, $category] = match ($statusCode) {
|
||||
403 => ['Not authorized to post to this account.', ErrorCategory::Permission],
|
||||
422 => ['Invalid post data. Please check your content.', ErrorCategory::ContentPolicy],
|
||||
default => [$errorMessage ?? 'An unknown LinkedIn error occurred.', ErrorCategory::Unknown],
|
||||
};
|
||||
|
||||
return new static(
|
||||
userMessage: $message,
|
||||
category: $category,
|
||||
platformErrorCode: (string) $statusCode,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
public function platform(): string
|
||||
{
|
||||
return 'linkedin';
|
||||
}
|
||||
}
|
||||
69
app/Exceptions/Social/ThreadsPublishException.php
Normal file
69
app/Exceptions/Social/ThreadsPublishException.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Social;
|
||||
|
||||
use App\Exceptions\TokenExpiredException;
|
||||
use Illuminate\Http\Client\Response;
|
||||
|
||||
class ThreadsPublishException extends SocialPublishException
|
||||
{
|
||||
public static function fromApiResponse(mixed $response): static
|
||||
{
|
||||
/** @var Response $response */
|
||||
$body = $response->json();
|
||||
$rawResponse = $response->body();
|
||||
$statusCode = $response->status();
|
||||
|
||||
$errorType = data_get($body, 'error.type');
|
||||
$errorCode = data_get($body, 'error.code');
|
||||
$errorMessage = data_get($body, 'error.message', 'An unknown Threads error occurred.');
|
||||
|
||||
if ($errorType === 'OAuthException' || $errorCode === 190) {
|
||||
throw new TokenExpiredException(
|
||||
message: $errorMessage,
|
||||
platformErrorCode: $errorCode !== null ? (string) $errorCode : null,
|
||||
);
|
||||
}
|
||||
|
||||
if (stripos((string) $rawResponse, "text can't be blank") !== false) {
|
||||
return new static(
|
||||
userMessage: 'Post text is required.',
|
||||
category: ErrorCategory::ContentPolicy,
|
||||
platformErrorCode: $errorCode !== null ? (string) $errorCode : null,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
if ($statusCode === 429) {
|
||||
return new static(
|
||||
userMessage: 'Rate limit exceeded. Please try again later.',
|
||||
category: ErrorCategory::RateLimit,
|
||||
platformErrorCode: (string) $statusCode,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
if ($statusCode >= 500) {
|
||||
return new static(
|
||||
userMessage: 'Threads server error. Please try again.',
|
||||
category: ErrorCategory::ServerError,
|
||||
platformErrorCode: (string) $statusCode,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
return new static(
|
||||
userMessage: $errorMessage,
|
||||
category: ErrorCategory::Unknown,
|
||||
platformErrorCode: $errorCode !== null ? (string) $errorCode : null,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
public function platform(): string
|
||||
{
|
||||
return 'threads';
|
||||
}
|
||||
}
|
||||
81
app/Exceptions/Social/XPublishException.php
Normal file
81
app/Exceptions/Social/XPublishException.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Social;
|
||||
|
||||
use App\Exceptions\TokenExpiredException;
|
||||
use Illuminate\Http\Client\Response;
|
||||
|
||||
class XPublishException extends SocialPublishException
|
||||
{
|
||||
public static function fromApiResponse(mixed $response): static
|
||||
{
|
||||
/** @var Response $response */
|
||||
$body = $response->json();
|
||||
$rawResponse = $response->body();
|
||||
$statusCode = $response->status();
|
||||
|
||||
$type = data_get($body, 'type', '');
|
||||
$title = data_get($body, 'title', '');
|
||||
$detail = data_get($body, 'detail', $title);
|
||||
|
||||
$typeSuffix = $type !== '' ? basename((string) $type) : '';
|
||||
|
||||
if ($statusCode === 401 || str_contains((string) $type, 'unsupported-authentication')) {
|
||||
throw new TokenExpiredException(
|
||||
message: $detail ?: 'Access token has expired or been revoked',
|
||||
platformErrorCode: $typeSuffix ?: (string) $statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
if (str_contains((string) $rawResponse, 'invalid URL')) {
|
||||
return new static(
|
||||
userMessage: 'Post contains an invalid URL.',
|
||||
category: ErrorCategory::ContentPolicy,
|
||||
platformErrorCode: $typeSuffix ?: null,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
if (str_contains((string) $rawResponse, 'video longer than 2 minutes')) {
|
||||
return new static(
|
||||
userMessage: 'Video exceeds the 2-minute limit.',
|
||||
category: ErrorCategory::MediaFormat,
|
||||
platformErrorCode: $typeSuffix ?: null,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
if (in_array($statusCode, [500, 502, 503, 504], true)) {
|
||||
return new static(
|
||||
userMessage: 'X server error. Please try again later.',
|
||||
category: ErrorCategory::ServerError,
|
||||
platformErrorCode: (string) $statusCode,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
[$message, $category] = match ($typeSuffix) {
|
||||
'usage-capped' => ['Usage limit exceeded. Please try again later.', ErrorCategory::RateLimit],
|
||||
'rate-limit-exceeded' => ['Rate limit exceeded. Please try again later.', ErrorCategory::RateLimit],
|
||||
'invalid-request' => ['Invalid request. Check your post content.', ErrorCategory::ContentPolicy],
|
||||
'client-forbidden' => ['App not enrolled or lacks required access.', ErrorCategory::Permission],
|
||||
'not-authorized-for-resource' => ['Not authorized for this resource.', ErrorCategory::Permission],
|
||||
'resource-not-found' => ['Resource not found.', ErrorCategory::ContentPolicy],
|
||||
default => [$detail ?: $title ?: 'An unknown X error occurred.', ErrorCategory::Unknown],
|
||||
};
|
||||
|
||||
return new static(
|
||||
userMessage: $message,
|
||||
category: $category,
|
||||
platformErrorCode: $typeSuffix ?: null,
|
||||
rawResponse: $rawResponse,
|
||||
);
|
||||
}
|
||||
|
||||
public function platform(): string
|
||||
{
|
||||
return 'x';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Exceptions\Social\ErrorCategory;
|
||||
use App\Exceptions\Social\LinkedInPublishException;
|
||||
use App\Exceptions\TokenExpiredException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
test('HTTP 401 throws TokenExpiredException', function () {
|
||||
$response = Http::response(['message' => 'Unauthorized'], 401);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.linkedin.com/test');
|
||||
|
||||
LinkedInPublishException::fromApiResponse($fakeResponse);
|
||||
})->throws(TokenExpiredException::class);
|
||||
|
||||
test('HTTP 403 maps to Permission category', function () {
|
||||
$response = Http::response(['message' => 'Forbidden'], 403);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.linkedin.com/test');
|
||||
|
||||
$exception = LinkedInPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception)
|
||||
->toBeInstanceOf(LinkedInPublishException::class)
|
||||
->and($exception->category)->toBe(ErrorCategory::Permission)
|
||||
->and($exception->userMessage)->toBe('Not authorized to post to this account.');
|
||||
});
|
||||
|
||||
test('body containing "Unable to obtain activity" maps to ServerError category', function () {
|
||||
$response = Http::response(['message' => 'Unable to obtain activity for URN'], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.linkedin.com/test');
|
||||
|
||||
$exception = LinkedInPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::ServerError)
|
||||
->and($exception->userMessage)->toBe('LinkedIn server error. Please try again.');
|
||||
});
|
||||
|
||||
test('unknown error maps to Unknown category with response body as message', function () {
|
||||
$response = Http::response(['message' => 'Something went wrong.'], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.linkedin.com/test');
|
||||
|
||||
$exception = LinkedInPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::Unknown)
|
||||
->and($exception->userMessage)->toBe('Something went wrong.');
|
||||
});
|
||||
|
||||
test('HTTP 422 maps to ContentPolicy category', function () {
|
||||
$response = Http::response(['message' => 'Validation error'], 422);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.linkedin.com/test');
|
||||
|
||||
$exception = LinkedInPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::ContentPolicy)
|
||||
->and($exception->userMessage)->toBe('Invalid post data. Please check your content.');
|
||||
});
|
||||
|
||||
test('body containing "resource is forbidden" maps to Permission category', function () {
|
||||
$response = Http::response(['message' => 'The resource is forbidden'], 403);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.linkedin.com/test');
|
||||
|
||||
$exception = LinkedInPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::Permission)
|
||||
->and($exception->userMessage)->toBe('Not authorized to post to this account.');
|
||||
});
|
||||
|
||||
test('platform returns linkedin', function () {
|
||||
$response = Http::response(['message' => 'Some error'], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.linkedin.com/test');
|
||||
|
||||
$exception = LinkedInPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->platform())->toBe('linkedin');
|
||||
});
|
||||
138
tests/Unit/Exceptions/Social/ThreadsPublishExceptionTest.php
Normal file
138
tests/Unit/Exceptions/Social/ThreadsPublishExceptionTest.php
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Exceptions\Social\ErrorCategory;
|
||||
use App\Exceptions\Social\ThreadsPublishException;
|
||||
use App\Exceptions\TokenExpiredException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
test('OAuthException type throws TokenExpiredException', function () {
|
||||
$response = Http::response([
|
||||
'error' => [
|
||||
'message' => 'Invalid OAuth access token.',
|
||||
'type' => 'OAuthException',
|
||||
'code' => 190,
|
||||
],
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://graph.threads.net/test');
|
||||
|
||||
ThreadsPublishException::fromApiResponse($fakeResponse);
|
||||
})->throws(TokenExpiredException::class);
|
||||
|
||||
test('error code 190 throws TokenExpiredException', function () {
|
||||
$response = Http::response([
|
||||
'error' => [
|
||||
'message' => 'Access token expired.',
|
||||
'type' => 'ThreadsException',
|
||||
'code' => 190,
|
||||
],
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://graph.threads.net/test');
|
||||
|
||||
ThreadsPublishException::fromApiResponse($fakeResponse);
|
||||
})->throws(TokenExpiredException::class);
|
||||
|
||||
test('HTTP 429 maps to RateLimit category', function () {
|
||||
$response = Http::response([
|
||||
'error' => [
|
||||
'message' => 'Application request limit reached.',
|
||||
'type' => 'ThreadsException',
|
||||
'code' => 4,
|
||||
],
|
||||
], 429);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://graph.threads.net/test');
|
||||
|
||||
$exception = ThreadsPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception)
|
||||
->toBeInstanceOf(ThreadsPublishException::class)
|
||||
->and($exception->category)->toBe(ErrorCategory::RateLimit)
|
||||
->and($exception->userMessage)->toBe('Rate limit exceeded. Please try again later.');
|
||||
});
|
||||
|
||||
test('body containing "text can\'t be blank" maps to ContentPolicy category', function () {
|
||||
$response = Http::response([
|
||||
'error' => [
|
||||
'message' => "text can't be blank",
|
||||
'type' => 'ThreadsException',
|
||||
'code' => 100,
|
||||
],
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://graph.threads.net/test');
|
||||
|
||||
$exception = ThreadsPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::ContentPolicy)
|
||||
->and($exception->userMessage)->toBe('Post text is required.');
|
||||
});
|
||||
|
||||
test('"text can\'t be blank" check is case insensitive', function () {
|
||||
$response = Http::response([
|
||||
'error' => [
|
||||
'message' => "Text Can't Be Blank",
|
||||
'type' => 'ThreadsException',
|
||||
'code' => 100,
|
||||
],
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://graph.threads.net/test');
|
||||
|
||||
$exception = ThreadsPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::ContentPolicy);
|
||||
});
|
||||
|
||||
test('unknown error maps to Unknown category with error message', function () {
|
||||
$response = Http::response([
|
||||
'error' => [
|
||||
'message' => 'An unexpected error occurred.',
|
||||
'type' => 'ThreadsException',
|
||||
'code' => 9999,
|
||||
],
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://graph.threads.net/test');
|
||||
|
||||
$exception = ThreadsPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::Unknown)
|
||||
->and($exception->userMessage)->toBe('An unexpected error occurred.');
|
||||
});
|
||||
|
||||
test('HTTP 500 maps to ServerError category', function () {
|
||||
$response = Http::response([
|
||||
'error' => [
|
||||
'message' => 'Internal server error.',
|
||||
'type' => 'ThreadsException',
|
||||
'code' => 1,
|
||||
],
|
||||
], 500);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://graph.threads.net/test');
|
||||
|
||||
$exception = ThreadsPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::ServerError)
|
||||
->and($exception->userMessage)->toBe('Threads server error. Please try again.');
|
||||
});
|
||||
|
||||
test('platform returns threads', function () {
|
||||
$response = Http::response([
|
||||
'error' => [
|
||||
'message' => 'Some error.',
|
||||
'type' => 'ThreadsException',
|
||||
'code' => 9999,
|
||||
],
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://graph.threads.net/test');
|
||||
|
||||
$exception = ThreadsPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->platform())->toBe('threads');
|
||||
});
|
||||
137
tests/Unit/Exceptions/Social/XPublishExceptionTest.php
Normal file
137
tests/Unit/Exceptions/Social/XPublishExceptionTest.php
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Exceptions\Social\ErrorCategory;
|
||||
use App\Exceptions\Social\XPublishException;
|
||||
use App\Exceptions\TokenExpiredException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
test('HTTP 401 throws TokenExpiredException', function () {
|
||||
$response = Http::response([
|
||||
'type' => 'https://api.x.com/2/problems/not-authorized-for-resource',
|
||||
'title' => 'Unauthorized',
|
||||
'detail' => 'Could not authenticate you.',
|
||||
], 401);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
XPublishException::fromApiResponse($fakeResponse);
|
||||
})->throws(TokenExpiredException::class);
|
||||
|
||||
test('type unsupported-authentication throws TokenExpiredException', function () {
|
||||
$response = Http::response([
|
||||
'type' => 'https://api.x.com/2/problems/unsupported-authentication',
|
||||
'title' => 'Unsupported Authentication',
|
||||
'detail' => 'Authentication method not supported.',
|
||||
], 403);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
XPublishException::fromApiResponse($fakeResponse);
|
||||
})->throws(TokenExpiredException::class);
|
||||
|
||||
test('type usage-capped maps to RateLimit category', function () {
|
||||
$response = Http::response([
|
||||
'type' => 'https://api.x.com/2/problems/usage-capped',
|
||||
'title' => 'Usage Capped',
|
||||
'detail' => 'You have exceeded your monthly usage cap.',
|
||||
], 403);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
$exception = XPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception)
|
||||
->toBeInstanceOf(XPublishException::class)
|
||||
->and($exception->category)->toBe(ErrorCategory::RateLimit)
|
||||
->and($exception->userMessage)->toBe('Usage limit exceeded. Please try again later.');
|
||||
});
|
||||
|
||||
test('type client-forbidden maps to Permission category', function () {
|
||||
$response = Http::response([
|
||||
'type' => 'https://api.x.com/2/problems/client-forbidden',
|
||||
'title' => 'Client Forbidden',
|
||||
'detail' => 'This app is not enrolled.',
|
||||
], 403);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
$exception = XPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::Permission)
|
||||
->and($exception->userMessage)->toBe('App not enrolled or lacks required access.');
|
||||
});
|
||||
|
||||
test('HTTP 500 maps to ServerError category', function () {
|
||||
$response = Http::response([
|
||||
'title' => 'Internal Server Error',
|
||||
'detail' => 'Something went wrong on our end.',
|
||||
], 500);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
$exception = XPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::ServerError)
|
||||
->and($exception->userMessage)->toBe('X server error. Please try again later.');
|
||||
});
|
||||
|
||||
test('unknown type maps to Unknown category with detail as message', function () {
|
||||
$response = Http::response([
|
||||
'type' => 'https://api.x.com/2/problems/some-unknown-problem',
|
||||
'title' => 'Some Unknown Problem',
|
||||
'detail' => 'An unknown issue occurred.',
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
$exception = XPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::Unknown)
|
||||
->and($exception->userMessage)->toBe('An unknown issue occurred.');
|
||||
});
|
||||
|
||||
test('body containing "invalid URL" maps to ContentPolicy category', function () {
|
||||
$response = Http::response([
|
||||
'type' => 'https://api.x.com/2/problems/invalid-request',
|
||||
'title' => 'Invalid Request',
|
||||
'detail' => 'The post contains an invalid URL in the content.',
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
$exception = XPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::ContentPolicy)
|
||||
->and($exception->userMessage)->toBe('Post contains an invalid URL.');
|
||||
});
|
||||
|
||||
test('body containing "video longer than 2 minutes" maps to MediaFormat category', function () {
|
||||
$response = Http::response([
|
||||
'type' => 'https://api.x.com/2/problems/invalid-request',
|
||||
'title' => 'Invalid Request',
|
||||
'detail' => 'The video longer than 2 minutes cannot be uploaded.',
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
$exception = XPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->category)->toBe(ErrorCategory::MediaFormat)
|
||||
->and($exception->userMessage)->toBe('Video exceeds the 2-minute limit.');
|
||||
});
|
||||
|
||||
test('platform returns x', function () {
|
||||
$response = Http::response([
|
||||
'type' => 'https://api.x.com/2/problems/some-unknown-problem',
|
||||
'title' => 'Error',
|
||||
'detail' => 'Some detail.',
|
||||
], 400);
|
||||
|
||||
$fakeResponse = Http::fake(['*' => $response])->post('https://api.x.com/test');
|
||||
|
||||
$exception = XPublishException::fromApiResponse($fakeResponse);
|
||||
|
||||
expect($exception->platform())->toBe('x');
|
||||
});
|
||||
Loading…
Reference in a new issue