fix(facebook): empty-message rejection + state consistency + no re-publish on terminal
Production incident: a customer's Facebook Page post failed with 'The post is empty. Please enter a message to share.' (error code 197) and ended up with a contradictory DB state (status=published + error_message=set). Three independent bugs were uncovered: A. FacebookPublisher sends 'message'/'description' as null when the user posts media without text. Graph API requires the key be omitted, not null. Fixed in publishSingleImagePost, publishMultiImagePost, publishVideoPost, publishReel. B. markAsPublished/markAsFailed leak stale fields across transitions (a published row could retain error_message from a prior failure, vice-versa). Both transitions now explicitly clear the opposite side's fields. C. status='failed' was editable in the UI and the backend, so users were re-clicking Publish, generating duplicate failure emails and the contradictory state from bug B. The frontend isReadOnly check and the UpdatePost backend guard now treat Published/PartiallyPublished/ Failed/Publishing as terminal. To retry, the user duplicates the post. 11 new tests guarantee these can't regress silently: FB payload shape per content type, PostPlatform field-clearing on transitions, and the terminal-status block at the controller level.
This commit is contained in:
parent
19f3b02941
commit
3f6032c152
17 changed files with 377 additions and 23 deletions
|
|
@ -20,8 +20,10 @@ class UpdatePost
|
|||
*/
|
||||
public static function execute(Workspace $workspace, Post $post, array $data): array
|
||||
{
|
||||
if ($post->status === PostStatus::Published) {
|
||||
return ['post' => $post, 'action' => PostAction::AlreadyPublished];
|
||||
$terminalStatuses = [PostStatus::Published, PostStatus::PartiallyPublished, PostStatus::Failed, PostStatus::Publishing];
|
||||
|
||||
if (in_array($post->status, $terminalStatuses, true)) {
|
||||
return ['post' => $post, 'action' => PostAction::Finalized];
|
||||
}
|
||||
|
||||
$scheduledAt = $post->scheduled_at;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
enum Action: string
|
||||
{
|
||||
case AlreadyPublished = 'already_published';
|
||||
case Finalized = 'finalized';
|
||||
case Publishing = 'publishing';
|
||||
case Scheduled = 'scheduled';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ public function update(UpdatePostRequest $request, Post $post): PostResource|Jso
|
|||
|
||||
$result = UpdatePost::execute($request->user()->currentWorkspace, $post, $request->validated());
|
||||
|
||||
if (data_get($result, 'action') === PostAction::AlreadyPublished) {
|
||||
if (in_array(data_get($result, 'action'), [PostAction::AlreadyPublished, PostAction::Finalized], true)) {
|
||||
return response()->json(
|
||||
['message' => 'Cannot edit a published post.'],
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ public function edit(Request $request, Post $post): Response|RedirectResponse
|
|||
|
||||
$this->authorize('update', $post);
|
||||
|
||||
if (in_array($post->status, [PostStatus::Publishing, PostStatus::Published, PostStatus::PartiallyPublished], true)) {
|
||||
if (in_array($post->status, [PostStatus::Publishing, PostStatus::Published, PostStatus::PartiallyPublished, PostStatus::Failed], true)) {
|
||||
return redirect()->route('app.posts.show', $post);
|
||||
}
|
||||
|
||||
|
|
@ -289,6 +289,13 @@ public function update(UpdatePostRequest $request, Post $post): RedirectResponse
|
|||
return back();
|
||||
}
|
||||
|
||||
if ($action === PostAction::Finalized) {
|
||||
session()->flash('flash.banner', __('posts.flash.cannot_edit_finalized'));
|
||||
session()->flash('flash.bannerStyle', 'danger');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
if ($action === PostAction::Publishing) {
|
||||
return redirect()->route('app.posts.show', $post);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public function handle(Request $request): Response|ResponseFactory
|
|||
|
||||
$result = UpdatePost::execute($workspace, $post, $payload);
|
||||
|
||||
if (data_get($result, 'action') === PostAction::AlreadyPublished) {
|
||||
if (in_array(data_get($result, 'action'), [PostAction::AlreadyPublished, PostAction::Finalized], true)) {
|
||||
return Response::error('Cannot edit a published post.');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ public function markAsPublished(string $platformPostId, ?string $platformUrl = n
|
|||
'platform_post_id' => $platformPostId,
|
||||
'platform_url' => $platformUrl,
|
||||
'published_at' => $now,
|
||||
'error_message' => null,
|
||||
'error_context' => null,
|
||||
]);
|
||||
|
||||
$this->socialAccount?->update(['last_used_at' => $now]);
|
||||
|
|
@ -113,6 +115,8 @@ public function markAsFailed(string $errorMessage, ?array $errorContext = null):
|
|||
'status' => Status::Failed,
|
||||
'error_message' => $errorMessage,
|
||||
'error_context' => $errorContext,
|
||||
'platform_post_id' => null,
|
||||
'platform_url' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,11 +102,16 @@ private function publishTextPost(string $pageId, string $accessToken, string $co
|
|||
|
||||
private function publishSingleImagePost(string $pageId, string $accessToken, ?string $content, $media): array
|
||||
{
|
||||
$response = $this->socialHttp()->post("{$this->baseUrl}/{$pageId}/photos", [
|
||||
'message' => $content,
|
||||
$payload = [
|
||||
'url' => $media->url,
|
||||
'access_token' => $accessToken,
|
||||
]);
|
||||
];
|
||||
|
||||
if (! empty($content)) {
|
||||
$payload['message'] = $content;
|
||||
}
|
||||
|
||||
$response = $this->socialHttp()->post("{$this->baseUrl}/{$pageId}/photos", $payload);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error('Facebook single image post failed', [
|
||||
|
|
@ -159,10 +164,13 @@ private function publishMultiImagePost(string $pageId, string $accessToken, ?str
|
|||
|
||||
// Create the post with attached media
|
||||
$postData = [
|
||||
'message' => $content,
|
||||
'access_token' => $accessToken,
|
||||
];
|
||||
|
||||
if (! empty($content)) {
|
||||
$postData['message'] = $content;
|
||||
}
|
||||
|
||||
foreach ($attachedMedia as $index => $media) {
|
||||
$postData["attached_media[{$index}]"] = json_encode($media);
|
||||
}
|
||||
|
|
@ -188,12 +196,16 @@ private function publishMultiImagePost(string $pageId, string $accessToken, ?str
|
|||
|
||||
private function publishVideoPost(string $pageId, string $accessToken, ?string $content, $media): array
|
||||
{
|
||||
// Use resumable upload for videos
|
||||
$response = $this->socialHttp()->post("{$this->baseUrl}/{$pageId}/videos", [
|
||||
'description' => $content,
|
||||
$payload = [
|
||||
'file_url' => $media->url,
|
||||
'access_token' => $accessToken,
|
||||
]);
|
||||
];
|
||||
|
||||
if (! empty($content)) {
|
||||
$payload['description'] = $content;
|
||||
}
|
||||
|
||||
$response = $this->socialHttp()->post("{$this->baseUrl}/{$pageId}/videos", $payload);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error('Facebook video post failed', [
|
||||
|
|
@ -287,13 +299,18 @@ private function publishReel(string $pageId, string $accessToken, ?string $conte
|
|||
}
|
||||
|
||||
// Phase 3 (finish) — publish the reel.
|
||||
$finishResponse = $this->socialHttp()->post("{$this->baseUrl}/{$pageId}/video_reels", [
|
||||
$finishPayload = [
|
||||
'upload_phase' => 'finish',
|
||||
'video_id' => $videoId,
|
||||
'video_state' => 'PUBLISHED',
|
||||
'description' => $content,
|
||||
'access_token' => $accessToken,
|
||||
]);
|
||||
];
|
||||
|
||||
if (! empty($content)) {
|
||||
$finishPayload['description'] = $content;
|
||||
}
|
||||
|
||||
$finishResponse = $this->socialHttp()->post("{$this->baseUrl}/{$pageId}/video_reels", $finishPayload);
|
||||
|
||||
if ($finishResponse->failed()) {
|
||||
$this->handleApiError($finishResponse);
|
||||
|
|
|
|||
|
|
@ -476,6 +476,7 @@
|
|||
'deleted' => 'Post deleted successfully!',
|
||||
'duplicated' => 'Post duplicated as a draft.',
|
||||
'cannot_edit_published' => 'Published posts cannot be edited.',
|
||||
'cannot_edit_finalized' => 'This post has already been processed and cannot be re-published. Duplicate it to try again.',
|
||||
'cannot_delete_published' => 'Published posts cannot be deleted.',
|
||||
'connect_first' => 'Connect at least one social network before creating a post.',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -476,6 +476,7 @@
|
|||
'deleted' => '¡Post eliminado correctamente!',
|
||||
'duplicated' => 'Post duplicado como borrador.',
|
||||
'cannot_edit_published' => 'Los posts publicados no se pueden editar.',
|
||||
'cannot_edit_finalized' => 'Este post ya fue procesado y no puede republicarse. Duplícalo para intentar de nuevo.',
|
||||
'cannot_delete_published' => 'Los posts publicados no se pueden eliminar.',
|
||||
'connect_first' => 'Conecta al menos una red social antes de crear un post.',
|
||||
],
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -476,6 +476,7 @@
|
|||
'deleted' => 'Post excluído com sucesso!',
|
||||
'duplicated' => 'Post duplicado como rascunho.',
|
||||
'cannot_edit_published' => 'Posts publicados não podem ser editados.',
|
||||
'cannot_edit_finalized' => 'Este post já foi processado e não pode ser republicado. Duplique-o para tentar de novo.',
|
||||
'cannot_delete_published' => 'Posts publicados não podem ser excluídos.',
|
||||
'connect_first' => 'Conecte pelo menos uma rede social antes de criar um post.',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ const props = defineProps<{
|
|||
|
||||
const post = computed(() => props.post);
|
||||
// Terminal states the user cannot recover from — delete, navigate, but never edit.
|
||||
const isReadOnly = computed(() => ['publishing', 'published', 'partially_published'].includes(post.value.status));
|
||||
const isReadOnly = computed(() => ['publishing', 'published', 'partially_published', 'failed'].includes(post.value.status));
|
||||
const isPublishing = computed(() => post.value.status === 'publishing');
|
||||
const isScheduled = computed(() => post.value.status === 'scheduled');
|
||||
// Locked states — terminal + scheduled. Field edits and auto-save suppressed;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@
|
|||
use App\Enums\PostPlatform\Status;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Enums\UserWorkspace\Role;
|
||||
use App\Jobs\PublishPost;
|
||||
use App\Models\Post;
|
||||
use App\Models\PostPlatform;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use App\Models\WorkspaceLabel;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
|
|
@ -308,7 +310,7 @@
|
|||
});
|
||||
|
||||
test('edit redirects to show for non-editable statuses', function () {
|
||||
foreach ([PostStatus::Published, PostStatus::PartiallyPublished, PostStatus::Publishing] as $status) {
|
||||
foreach ([PostStatus::Published, PostStatus::PartiallyPublished, PostStatus::Publishing, PostStatus::Failed] as $status) {
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
|
|
@ -326,8 +328,8 @@
|
|||
}
|
||||
});
|
||||
|
||||
test('edit allows draft, scheduled, and failed posts', function () {
|
||||
foreach ([PostStatus::Draft, PostStatus::Scheduled, PostStatus::Failed] as $status) {
|
||||
test('edit allows draft and scheduled posts', function () {
|
||||
foreach ([PostStatus::Draft, PostStatus::Scheduled] as $status) {
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
|
|
@ -415,6 +417,138 @@
|
|||
$response->assertRedirect();
|
||||
});
|
||||
|
||||
test('cannot re-publish a failed post', function () {
|
||||
Bus::fake();
|
||||
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
'status' => PostStatus::Failed,
|
||||
]);
|
||||
|
||||
$postPlatform = PostPlatform::factory()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->user)->put(route('app.posts.update', $post), [
|
||||
'status' => 'publishing',
|
||||
'content' => 'Test content',
|
||||
'platforms' => [
|
||||
[
|
||||
'id' => $postPlatform->id,
|
||||
'content_type' => ContentType::LinkedInPost->value,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$response->assertSessionHas('flash.bannerStyle', 'danger');
|
||||
|
||||
$post->refresh();
|
||||
expect($post->status)->toBe(PostStatus::Failed);
|
||||
Bus::assertNotDispatched(PublishPost::class);
|
||||
});
|
||||
|
||||
test('cannot update a post in publishing state', function () {
|
||||
Bus::fake();
|
||||
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
'status' => PostStatus::Publishing,
|
||||
]);
|
||||
|
||||
$postPlatform = PostPlatform::factory()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->user)->put(route('app.posts.update', $post), [
|
||||
'status' => 'draft',
|
||||
'content' => 'Test content',
|
||||
'platforms' => [
|
||||
[
|
||||
'id' => $postPlatform->id,
|
||||
'content_type' => ContentType::LinkedInPost->value,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$response->assertSessionHas('flash.bannerStyle', 'danger');
|
||||
|
||||
$post->refresh();
|
||||
expect($post->status)->toBe(PostStatus::Publishing);
|
||||
Bus::assertNotDispatched(PublishPost::class);
|
||||
});
|
||||
|
||||
test('cannot update a partially published post', function () {
|
||||
Bus::fake();
|
||||
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
'status' => PostStatus::PartiallyPublished,
|
||||
]);
|
||||
|
||||
$postPlatform = PostPlatform::factory()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->user)->put(route('app.posts.update', $post), [
|
||||
'status' => 'publishing',
|
||||
'content' => 'Test content',
|
||||
'platforms' => [
|
||||
[
|
||||
'id' => $postPlatform->id,
|
||||
'content_type' => ContentType::LinkedInPost->value,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$response->assertSessionHas('flash.bannerStyle', 'danger');
|
||||
|
||||
$post->refresh();
|
||||
expect($post->status)->toBe(PostStatus::PartiallyPublished);
|
||||
Bus::assertNotDispatched(PublishPost::class);
|
||||
});
|
||||
|
||||
test('cannot update a published post', function () {
|
||||
Bus::fake();
|
||||
|
||||
$post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
'status' => PostStatus::Published,
|
||||
]);
|
||||
|
||||
$postPlatform = PostPlatform::factory()->create([
|
||||
'post_id' => $post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->user)->put(route('app.posts.update', $post), [
|
||||
'status' => 'publishing',
|
||||
'content' => 'Test content',
|
||||
'platforms' => [
|
||||
[
|
||||
'id' => $postPlatform->id,
|
||||
'content_type' => ContentType::LinkedInPost->value,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$response->assertSessionHas('flash.bannerStyle', 'danger');
|
||||
|
||||
$post->refresh();
|
||||
expect($post->status)->toBe(PostStatus::Published);
|
||||
Bus::assertNotDispatched(PublishPost::class);
|
||||
});
|
||||
|
||||
test('publish now updates scheduled_at to current time', function () {
|
||||
Mail::fake();
|
||||
$this->freezeTime();
|
||||
|
|
|
|||
|
|
@ -499,3 +499,125 @@
|
|||
|
||||
expect($result['id'])->toBe('post-123');
|
||||
});
|
||||
|
||||
test('single image post without caption omits message from payload', function () {
|
||||
$this->post->update([
|
||||
'content' => null,
|
||||
'media' => [
|
||||
[
|
||||
'id' => 'test-media-id',
|
||||
'path' => 'media/2026-01/image.jpg',
|
||||
'url' => 'https://example.com/media/2026-01/image.jpg',
|
||||
'mime_type' => 'image/jpeg',
|
||||
'original_filename' => 'image.jpg',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'*/page_123/photos' => Http::response(['id' => 'photo-123', 'post_id' => 'post-123'], 200),
|
||||
]);
|
||||
|
||||
$this->publisher->publish($this->postPlatform);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/page_123/photos')
|
||||
&& ! array_key_exists('message', $request->data());
|
||||
});
|
||||
});
|
||||
|
||||
test('multi-image post without caption omits message from payload', function () {
|
||||
$mediaItems = [];
|
||||
for ($i = 1; $i <= 2; $i++) {
|
||||
$mediaItems[] = [
|
||||
'id' => "test-media-{$i}",
|
||||
'path' => "media/2026-01/image{$i}.jpg",
|
||||
'url' => "https://example.com/media/2026-01/image{$i}.jpg",
|
||||
'mime_type' => 'image/jpeg',
|
||||
'original_filename' => "image{$i}.jpg",
|
||||
];
|
||||
}
|
||||
|
||||
$this->post->update(['content' => null, 'media' => $mediaItems]);
|
||||
|
||||
Http::fake([
|
||||
'*/page_123/photos' => Http::sequence()
|
||||
->push(['id' => 'photo_1'], 200)
|
||||
->push(['id' => 'photo_2'], 200),
|
||||
'*/page_123/feed' => Http::response(['id' => 'multi_post_789'], 200),
|
||||
]);
|
||||
|
||||
$this->publisher->publish($this->postPlatform);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/page_123/feed')
|
||||
&& ! array_key_exists('message', $request->data());
|
||||
});
|
||||
});
|
||||
|
||||
test('video post without description omits description from payload', function () {
|
||||
$this->post->update([
|
||||
'content' => null,
|
||||
'media' => [
|
||||
[
|
||||
'id' => 'test-media-video',
|
||||
'path' => 'media/2026-01/video.mp4',
|
||||
'url' => 'https://example.com/media/2026-01/video.mp4',
|
||||
'mime_type' => 'video/mp4',
|
||||
'original_filename' => 'video.mp4',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'*/page_123/videos' => Http::response(['id' => 'video_123'], 200),
|
||||
]);
|
||||
|
||||
$this->publisher->publish($this->postPlatform);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/page_123/videos')
|
||||
&& ! array_key_exists('description', $request->data());
|
||||
});
|
||||
});
|
||||
|
||||
test('reel post without description omits description from payload (finish phase)', function () {
|
||||
$this->postPlatform->update(['content_type' => ContentType::FacebookReel]);
|
||||
|
||||
$this->post->update([
|
||||
'content' => null,
|
||||
'media' => [
|
||||
[
|
||||
'id' => 'test-media-reel',
|
||||
'path' => 'media/2026-01/reel.mp4',
|
||||
'url' => 'https://example.com/media/2026-01/reel.mp4',
|
||||
'mime_type' => 'video/mp4',
|
||||
'original_filename' => 'reel.mp4',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'*/page_123/video_reels' => Http::sequence()
|
||||
->push([
|
||||
'video_id' => 'reel_video_123',
|
||||
'upload_url' => 'https://rupload.facebook.com/video-upload/v25.0/reel_video_123',
|
||||
], 200)
|
||||
->push(['id' => 'reel_123', 'success' => true], 200),
|
||||
'*example.com/media/*' => Http::response('fake-video-binary-content', 200),
|
||||
'*rupload.facebook.com/*' => Http::response(['success' => true], 200),
|
||||
]);
|
||||
|
||||
$this->publisher->publish($this->postPlatform);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
if (! str_contains($request->url(), '/page_123/video_reels')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $request->data();
|
||||
|
||||
return data_get($data, 'upload_phase') === 'finish'
|
||||
&& ! array_key_exists('description', $data);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
64
tests/Unit/PostPlatformTest.php
Normal file
64
tests/Unit/PostPlatformTest.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\PostPlatform\Status;
|
||||
use App\Models\Post;
|
||||
use App\Models\PostPlatform;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->user = User::factory()->create();
|
||||
$this->workspace = Workspace::factory()->create(['user_id' => $this->user->id]);
|
||||
|
||||
$this->post = Post::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
$this->socialAccount = SocialAccount::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
]);
|
||||
|
||||
$this->postPlatform = PostPlatform::factory()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $this->socialAccount->id,
|
||||
]);
|
||||
});
|
||||
|
||||
test('markAsPublished clears stale error_message and error_context', function () {
|
||||
$this->postPlatform->update([
|
||||
'status' => Status::Failed,
|
||||
'error_message' => 'The post is empty. Please enter a message to share.',
|
||||
'error_context' => ['platform_error_code' => 197, 'content_length' => 0],
|
||||
]);
|
||||
|
||||
$this->postPlatform->markAsPublished('platform_post_abc', 'https://www.facebook.com/platform_post_abc');
|
||||
|
||||
$this->postPlatform->refresh();
|
||||
|
||||
expect($this->postPlatform->status)->toBe(Status::Published)
|
||||
->and($this->postPlatform->platform_post_id)->toBe('platform_post_abc')
|
||||
->and($this->postPlatform->error_message)->toBeNull()
|
||||
->and($this->postPlatform->error_context)->toBeNull();
|
||||
});
|
||||
|
||||
test('markAsFailed clears stale platform_post_id and platform_url', function () {
|
||||
$this->postPlatform->update([
|
||||
'status' => Status::Published,
|
||||
'platform_post_id' => 'old_post_id',
|
||||
'platform_url' => 'https://www.facebook.com/old_post_id',
|
||||
'published_at' => now(),
|
||||
]);
|
||||
|
||||
$this->postPlatform->markAsFailed('Something went wrong.', ['platform_error_code' => 500]);
|
||||
|
||||
$this->postPlatform->refresh();
|
||||
|
||||
expect($this->postPlatform->status)->toBe(Status::Failed)
|
||||
->and($this->postPlatform->error_message)->toBe('Something went wrong.')
|
||||
->and($this->postPlatform->platform_post_id)->toBeNull()
|
||||
->and($this->postPlatform->platform_url)->toBeNull();
|
||||
});
|
||||
Loading…
Reference in a new issue