create(); $this->mock(SocialContentGeneratorService::class, function (MockInterface $mock): void { $mock->shouldReceive('generate') ->once() ->andThrow(new RuntimeException('OpenAI unavailable')); }); $response = $this ->actingAs($user) ->from(route('social.index')) ->post(route('social.generate'), [ 'topic' => 'Promo hosting ramadhan', 'tone' => 'friendly-professional', 'platform_hint' => 'facebook, instagram', ]); $response ->assertRedirect(route('social.index')) ->assertSessionHas('social_error', 'OpenAI unavailable'); } public function test_instagram_posts_require_an_image_url(): void { $user = User::factory()->create(); $response = $this ->actingAs($user) ->from(route('social.index')) ->post(route('social.store'), [ 'platforms' => ['instagram'], 'title' => 'Promo baru', 'caption' => 'Caption promo', 'hashtags' => 'promo,bisnis', 'scheduled_at' => now()->addHour()->toDateTimeString(), 'status' => 'scheduled', ]); $response ->assertRedirect(route('social.index')) ->assertSessionHas('social_error', 'Instagram membutuhkan image_url publik sebelum post bisa dibuat.'); $this->assertDatabaseCount('social_posts', 0); } public function test_tiktok_posts_require_a_video_url(): void { $user = User::factory()->create(); $response = $this ->actingAs($user) ->from(route('social.index')) ->post(route('social.store'), [ 'platforms' => ['tiktok'], 'title' => 'Promo video', 'caption' => 'Caption tiktok', 'hashtags' => 'promo,video', 'scheduled_at' => now()->addHour()->toDateTimeString(), 'status' => 'scheduled', ]); $response ->assertRedirect(route('social.index')) ->assertSessionHas('social_error', 'TikTok membutuhkan video_url publik sebelum post bisa dibuat.'); $this->assertDatabaseCount('social_posts', 0); } public function test_credential_settings_page_is_displayed(): void { $user = User::factory()->create(); $this->actingAs($user) ->get(route('social.credentials.index')) ->assertOk() ->assertSeeText('Setting Credential'); } public function test_saving_credentials_preserves_existing_token_and_meta_when_left_blank(): void { $user = User::factory()->create(); $credential = SocialPlatformCredential::create([ 'user_id' => $user->id, 'platform' => 'facebook', 'account_label' => 'Main page', 'account_id' => 'page-1', 'access_token' => 'token-lama', 'meta' => [ 'client_id' => 'client-lama', 'client_secret' => 'secret-lama', ], ]); $response = $this ->actingAs($user) ->from(route('social.credentials.index')) ->post(route('social.credentials.store'), [ 'platform' => 'facebook', 'account_label' => 'Main page updated', 'account_id' => 'page-2', 'access_token' => '', 'meta_client_id' => '', 'meta_client_secret' => '', ]); $response ->assertRedirect(route('social.credentials.index').'#platform-facebook') ->assertSessionHasNoErrors(); $credential->refresh(); $this->assertSame('Main page updated', $credential->account_label); $this->assertSame('page-2', $credential->account_id); $this->assertSame('token-lama', $credential->access_token); $this->assertSame([ 'client_id' => 'client-lama', 'client_secret' => 'secret-lama', ], $credential->meta); } public function test_publish_now_rejects_posts_that_are_already_posted(): void { $user = User::factory()->create(); $post = SocialPost::create([ 'user_id' => $user->id, 'platform' => 'x', 'status' => 'posted', 'requires_approval' => false, 'caption' => 'Sudah terbit', 'scheduled_at' => now()->subHour(), 'posted_at' => now()->subMinutes(10), ]); $response = $this ->actingAs($user) ->post(route('social.publish-now', $post)); $response ->assertRedirect(route('social.index')) ->assertSessionHas('social_error', 'Post ini sudah pernah dipublish.'); } public function test_publish_now_clears_retry_metadata_after_success(): void { $user = User::factory()->create(); SocialPlatformCredential::create([ 'user_id' => $user->id, 'platform' => 'x', 'access_token' => 'token-x', ]); $post = SocialPost::create([ 'user_id' => $user->id, 'platform' => 'x', 'status' => 'failed', 'requires_approval' => false, 'caption' => 'Coba kirim ulang', 'scheduled_at' => now()->subHour(), 'attempt_count' => 2, 'next_retry_at' => now()->addMinutes(15), ]); $this->mock(SocialPublisherService::class, function (MockInterface $mock): void { $mock->shouldReceive('publish') ->once() ->andReturn([ 'external_post_id' => 'tweet-123', 'payload' => ['ok' => true], ]); }); $response = $this ->actingAs($user) ->post(route('social.publish-now', $post)); $response ->assertRedirect(route('social.index')) ->assertSessionHas('social_success', 'Post berhasil dipublish ke x.'); $post->refresh(); $this->assertSame('posted', $post->status); $this->assertNull($post->next_retry_at); $this->assertNotNull($post->approved_at); $this->assertSame('tweet-123', $post->external_post_id); } }