engine = new WorkflowEngine; $this->handler = new ScriptGenerationHandler(app(AiProviderManager::class), new AssetStorage); }); test('it generates a script, stores it as an asset, and completes the step', function () { Http::fake([ 'api.openai.com/*' => Http::response([ 'choices' => [['message' => ['content' => 'Welcome back to the channel today']]], ]), ]); AiProvider::factory()->create(['provider' => 'openai', 'is_active' => true]); $project = VideoProject::factory()->create(['title' => 'Best Coffee Gadgets 2026']); $step = $this->engine->start($project, [['type' => 'script_generation']]); $this->handler->handle($step, $this->engine); $fresh = $step->fresh(); expect($fresh->status)->toBe('completed'); expect($fresh->output['word_count'])->toBe(6); $asset = \App\Models\Asset::find($fresh->output['asset_id']); expect($asset->type)->toBe('script'); expect($asset->video_project_id)->toBe($project->id); expect(Storage::get($asset->disk_path))->toBe('Welcome back to the channel today'); Http::assertSent(fn ($request) => str_contains($request['messages'][0]['content'], 'Best Coffee Gadgets 2026')); }); test('it uses an explicit outline, tone, keywords, and target length when given', function () { Http::fake(['api.openai.com/*' => Http::response(['choices' => [['message' => ['content' => 'script']]]])]); AiProvider::factory()->create(['provider' => 'openai']); $project = VideoProject::factory()->create(); $step = $this->engine->start($project, [['type' => 'script_generation']]); $step->update(['input' => [ 'outline' => 'A history of espresso machines', 'tone' => 'casual and funny', 'seo_keywords' => ['espresso', 'coffee history'], 'target_words' => 800, ]]); $this->handler->handle($step->fresh(), $this->engine); Http::assertSent(function ($request) { $prompt = $request['messages'][0]['content']; return str_contains($prompt, 'A history of espresso machines') && str_contains($prompt, 'casual and funny') && str_contains($prompt, 'espresso, coffee history') && str_contains($prompt, 'approximately 800 words'); }); }); test('it fails the step when no active OpenAI provider is configured', function () { $project = VideoProject::factory()->create(); $step = $this->engine->start($project, [['type' => 'script_generation']]); $this->handler->handle($step, $this->engine); expect($step->fresh()) ->status->toBe('failed') ->error->toContain('No active OpenAI provider'); }); test('it fails the step when the AI provider call fails', function () { Http::fake(['api.openai.com/*' => Http::response(['error' => ['message' => 'rate limited']], 429)]); AiProvider::factory()->create(['provider' => 'openai']); $project = VideoProject::factory()->create(); $step = $this->engine->start($project, [['type' => 'script_generation']]); $this->handler->handle($step, $this->engine); expect($step->fresh()) ->status->toBe('failed') ->error->toBe('rate limited'); });