| <?php |
|
|
| namespace Tests\Feature; |
|
|
| use App\Models\SocialPlatformCredential; |
| use App\Models\SocialPost; |
| use App\Models\User; |
| use App\Services\SocialContentGeneratorService; |
| use App\Services\SocialPublisherService; |
| use Illuminate\Foundation\Testing\DatabaseTransactions; |
| use Mockery\MockInterface; |
| use RuntimeException; |
| use Tests\TestCase; |
|
|
| class SocialAutomationTest extends TestCase |
| { |
| use DatabaseTransactions; |
|
|
| public function test_generate_failures_are_returned_as_flash_error(): void |
| { |
| $user = User::factory()->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); |
| } |
| } |
|
|