create(); $hfOnly = in_array(strtolower((string) env('TOOLS_HF_ONLY', 'false')), ['1', 'true', 'yes', 'on'], true); if (! $hfOnly) { $this->mock(FreepikStockContentService::class, function (MockInterface $mock): void { $mock->shouldReceive('search') ->once() ->with('nature', 'all', 'relevance', 1) ->andReturn([ 'results' => [ 'images' => [[ 'id' => 10, 'kind' => 'image', 'title' => 'Waterfall hero', 'url' => 'https://www.freepik.com/asset/10', 'thumb' => 'https://img.example.com/resource.jpg', 'subtitle' => 'PHOTO • Vertical • Freepik Author', 'badge' => 'PHOTO', 'meta_line' => 'Likes 9', ]], 'videos' => [[ 'id' => 20, 'kind' => 'video', 'title' => 'Forest Walk', 'url' => 'https://www.freepik.com/video/20', 'thumb' => 'https://img.example.com/video-thumb.jpg', 'preview_url' => 'https://img.example.com/video-preview.mp4', 'subtitle' => '4K • 00:00:10 • Video Author', 'badge' => 'VIDEO', 'meta_line' => '16:9 • footage', ]], 'icons' => [[ 'id' => 30, 'kind' => 'icon', 'title' => 'Nature icon', 'url' => 'https://www.freepik.com/icon/nature-icon_30', 'thumb' => 'https://img.example.com/icon.png', 'subtitle' => 'Outline • Icon Author', 'badge' => 'ICON', 'meta_line' => 'leaf • tree', ]], 'audio' => [[ 'id' => 40, 'kind' => 'audio', 'title' => 'Soft Seasons', 'url' => 'https://img.example.com/music.mp3', 'thumb' => 'https://img.example.com/music.jpg', 'subtitle' => '3:05 • MudiG', 'badge' => 'AUDIO', 'meta_line' => 'Pop • Acoustic • Free', ]], ], 'errors' => [], 'summary' => [ 'images' => 1, 'videos' => 1, 'icons' => 1, 'audio' => 1, ], ]); }); } $response = $this->actingAs($user) ->get(route('tools.index', [ 'stock_q' => 'nature', 'stock_type' => 'all', 'stock_order' => 'relevance', ])) ->assertOk(); if ($hfOnly) { $response ->assertSeeText('Hugging Face Mode') ->assertSeeText('HF-only workspace is active.') ->assertSeeText('Image Generator') ->assertSeeText('Remix Lab'); return; } $response ->assertSeeText('Freepik Asset Explorer') ->assertSeeText('Images & Templates') ->assertSeeText('Videos') ->assertSeeText('Audio') ->assertSeeText('Icons') ->assertSeeText('Waterfall hero') ->assertSeeText('Forest Walk') ->assertSeeText('Soft Seasons') ->assertSeeText('Nature icon') ->assertSeeText('Click to edit or download') ->assertSeeText('Use Asset') ->assertSeeText('Edit image') ->assertSeeText('Create video'); } public function test_app_page_displays_template_launcher_and_defaults_video_lab_to_replicate(): void { $user = User::factory()->create(); $this->actingAs($user) ->get(route('app.index')) ->assertOk() ->assertSee('id="video-provider-input" name="video_provider" value="replicate"', false) ->assertSee('id="video-quality-input" name="video_quality" value="720p"', false) ->assertSee('id="video-duration-input" name="video_duration" value="4s"', false) ->assertSeeText('Movie Poster') ->assertSeeText('Create a Movie Poster') ->assertSeeText('Portrait in, poster out') ->assertSeeText('Generate poster') ->assertSeeText('Replicate WAN 2.1') ->assertSeeText('Sora 2') ->assertSeeText('Default') ->assertSeeText('Alt') ->assertSeeText('Generate Video memakai WaveSpeed WAN 2.1 I2V 480p di Replicate sebagai default service.'); } public function test_video_generation_defaults_to_replicate_when_video_provider_is_missing(): void { $user = User::factory()->create(); $this->mock(SocialMediaStudioService::class, function (MockInterface $mock): void { $mock->shouldReceive('generateVideo') ->once() ->with('Animate a clean product reveal', \Mockery::on(function (array $options): bool { return ($options['video_provider'] ?? null) === 'replicate' && ($options['video_model'] ?? null) === 'minimax' && ($options['aspect_ratio'] ?? null) === '16:9' && ($options['video_quality'] ?? null) === '720p' && ($options['video_duration'] ?? null) === '4s'; })) ->andReturn([ 'url' => 'https://cdn.example.com/generated-video.mp4', 'provider' => 'replicate', 'prompt' => 'Animate a clean product reveal', 'aspect' => '16:9', 'duration_seconds' => 5, 'source_image_url' => 'https://cdn.example.com/generated-video-thumb.jpg', ]); }); $this->actingAs($user) ->postJson(route('tools.video'), [ 'prompt' => 'Animate a clean product reveal', ]) ->assertOk() ->assertJsonPath('status', 'succeeded') ->assertJsonPath('provider', 'replicate') ->assertJsonPath('effective_settings.aspect_ratio', '16:9') ->assertJsonPath('effective_settings.video_quality', '720p') ->assertJsonPath('effective_settings.video_duration', '4s'); } }