engine = new WorkflowEngine; }); test('it runs the matching handler and completes the step', function () { Http::fake(['api.openai.com/*' => Http::response(['choices' => [['message' => ['content' => 'a script']]]])]); AiProvider::factory()->create(['provider' => 'openai']); $project = VideoProject::factory()->create(); $step = $this->engine->start($project, [['type' => 'script_generation']]); (new RunWorkflowStepJob($step->id))->handle(app(StepHandlerManager::class), $this->engine); expect($step->fresh()->status)->toBe('completed'); }); test('it fails the step when no handler is registered for its type', function () { $project = VideoProject::factory()->create(); // A made-up type — not a real WorkflowStep::TYPES value — so this test // stays valid regardless of which real types have handlers over time. $step = $this->engine->start($project, [['type' => 'some_future_step_type']]); (new RunWorkflowStepJob($step->id))->handle(app(StepHandlerManager::class), $this->engine); expect($step->fresh()) ->status->toBe('failed') ->error->toContain('No handler registered'); }); test('it does nothing for a step that is no longer in_progress', function () { $project = VideoProject::factory()->create(); $step = $this->engine->start($project, [['type' => 'script_generation']]); $this->engine->skipStep($step); (new RunWorkflowStepJob($step->id))->handle(app(StepHandlerManager::class), $this->engine); expect($step->fresh()->status)->toBe('skipped'); }); test('it does nothing for a missing step', function () { (new RunWorkflowStepJob(999999))->handle(app(StepHandlerManager::class), $this->engine); })->throwsNoExceptions(); test('it fails the step when the handler throws unexpectedly', function () { $badHandler = new class implements \App\Support\VideoWorkflow\StepHandlers\StepHandler { public function type(): string { return 'script_generation'; } public function handle(\App\Models\WorkflowStep $step, WorkflowEngine $engine): void { throw new \RuntimeException('boom'); } }; $manager = new StepHandlerManager([$badHandler]); $project = VideoProject::factory()->create(); $step = $this->engine->start($project, [['type' => 'script_generation']]); (new RunWorkflowStepJob($step->id))->handle($manager, $this->engine); expect($step->fresh()) ->status->toBe('failed') ->error->toBe('boom'); });