create(); WorkflowStep::factory()->for($project)->create(['type' => 'video_generation', 'order' => 1]); WorkflowStep::factory()->for($project)->create(['type' => 'script_generation', 'order' => 0]); expect($project->steps->pluck('type')->all())->toBe(['script_generation', 'video_generation']); }); test('a video project belongs to a workflow template', function () { $template = WorkflowTemplate::factory()->create(); $project = VideoProject::factory()->create(['workflow_template_id' => $template->id]); expect($project->workflowTemplate->id)->toBe($template->id); expect($template->videoProjects->first()->id)->toBe($project->id); }); test('deleting a video project cascades to its steps and assets', function () { $project = VideoProject::factory()->create(); $step = WorkflowStep::factory()->for($project)->create(); $asset = Asset::factory()->for($project)->create(['workflow_step_id' => $step->id]); $project->forceDelete(); expect(WorkflowStep::find($step->id))->toBeNull(); expect(Asset::find($asset->id))->toBeNull(); }); test('a video project is soft deleted by default', function () { $project = VideoProject::factory()->create(); $project->delete(); expect(VideoProject::find($project->id))->toBeNull(); expect(VideoProject::withTrashed()->find($project->id))->not->toBeNull(); }); test('deleting the workflow step an asset belongs to nulls the reference instead of deleting the asset', function () { $project = VideoProject::factory()->create(); $step = WorkflowStep::factory()->for($project)->create(); $asset = Asset::factory()->for($project)->create(['workflow_step_id' => $step->id]); $step->delete(); expect($asset->fresh()->workflow_step_id)->toBeNull(); }); test('workflow step input/output/requires_approval cast correctly', function () { $step = WorkflowStep::factory()->create([ 'requires_approval' => true, 'input' => ['prompt' => 'write a script'], 'output' => ['script' => 'once upon a time'], ]); expect($step->fresh()) ->requires_approval->toBeTrue() ->input->toBe(['prompt' => 'write a script']) ->output->toBe(['script' => 'once upon a time']); }); test('asset metadata casts to an array', function () { $asset = Asset::factory()->create(['metadata' => ['duration_seconds' => 42]]); expect($asset->fresh()->metadata)->toBe(['duration_seconds' => 42]); }); test('an ai provider stores its config as an array and defaults to active', function () { $provider = AiProvider::factory()->create([ 'provider' => 'elevenlabs', 'config' => ['voice_id' => 'abc123'], ]); expect($provider->fresh()) ->provider->toBe('elevenlabs') ->config->toBe(['voice_id' => 'abc123']) ->is_active->toBeTrue(); }); test('a workflow template stores its steps as an ordered array', function () { $template = WorkflowTemplate::factory()->create([ 'steps' => [ ['type' => 'script_generation', 'requires_approval' => true], ['type' => 'youtube_publish', 'requires_approval' => false], ], ]); expect($template->fresh()->steps)->toBe([ ['type' => 'script_generation', 'requires_approval' => true], ['type' => 'youtube_publish', 'requires_approval' => false], ]); });