create(); Livewire::test(Show::class, ['videoProject' => $project]) ->call('startDefault'); expect($project->fresh()->status)->toBe('in_progress'); expect($project->steps()->count())->toBe(8); expect($project->steps()->orderBy('order')->first()->type)->toBe('script_generation'); Queue::assertPushed(RunWorkflowStepJob::class, function ($job) use ($project) { $step = $project->steps()->orderBy('order')->first(); return $job->stepId === $step->id; }); }); test('starting from a template uses its steps and does not queue unhandled types', function () { Queue::fake(); // Made-up types — not real WorkflowStep::TYPES values — so this test // stays valid regardless of which real types have handlers over time. $template = WorkflowTemplate::factory()->create(['steps' => [ ['type' => 'some_future_step_type'], ['type' => 'another_future_step_type'], ]]); $project = VideoProject::factory()->create(); Livewire::test(Show::class, ['videoProject' => $project]) ->set('workflowTemplateId', $template->id) ->call('startWithTemplate'); expect($project->fresh()->workflow_template_id)->toBe($template->id); expect($project->steps()->count())->toBe(2); Queue::assertNotPushed(RunWorkflowStepJob::class); }); test('startWithTemplate shows a notice when no template is selected', function () { $project = VideoProject::factory()->create(); Livewire::test(Show::class, ['videoProject' => $project]) ->call('startWithTemplate') ->assertSee('Pick a template first.'); expect($project->fresh()->status)->toBe('draft'); }); test('runStep dispatches the job for the given step', function () { Queue::fake(); $project = VideoProject::factory()->create(); $step = (new WorkflowEngine)->start($project, [['type' => 'script_generation']]); Livewire::test(Show::class, ['videoProject' => $project]) ->call('runStep', $step->id); Queue::assertPushed(RunWorkflowStepJob::class, fn ($job) => $job->stepId === $step->id); }); test('approving a step advances and queues the next handled step', function () { Queue::fake(); $project = VideoProject::factory()->create(); $engine = new WorkflowEngine; $step = $engine->start($project, [ ['type' => 'script_generation', 'requires_approval' => true], ['type' => 'tts_voiceover'], ]); $engine->completeStep($step, ['script' => 'draft']); Livewire::test(Show::class, ['videoProject' => $project]) ->call('approveStep', $step->id); $next = $project->steps()->where('order', 1)->first(); expect($next->status)->toBe('in_progress'); Queue::assertPushed(RunWorkflowStepJob::class, fn ($job) => $job->stepId === $next->id); }); test('rejecting a step blocks the pipeline', function () { $project = VideoProject::factory()->create(); $engine = new WorkflowEngine; $step = $engine->start($project, [['type' => 'script_generation', 'requires_approval' => true]]); $engine->completeStep($step, ['script' => 'bad']); Livewire::test(Show::class, ['videoProject' => $project]) ->call('rejectStep', $step->id); expect($step->fresh()->status)->toBe('rejected'); }); test('retrying a failed step re-queues it', function () { Queue::fake(); $project = VideoProject::factory()->create(); $engine = new WorkflowEngine; $step = $engine->start($project, [['type' => 'script_generation']]); $engine->failStep($step, 'boom'); Livewire::test(Show::class, ['videoProject' => $project]) ->call('retryStep', $step->id); expect($step->fresh()->status)->toBe('in_progress'); Queue::assertPushed(RunWorkflowStepJob::class, fn ($job) => $job->stepId === $step->id); }); test('skipping a step advances and queues the next handled step', function () { Queue::fake(); $project = VideoProject::factory()->create(); $engine = new WorkflowEngine; $step = $engine->start($project, [ ['type' => 'video_generation'], ['type' => 'script_generation'], ]); Livewire::test(Show::class, ['videoProject' => $project]) ->call('skipStep', $step->id); $next = $project->steps()->where('order', 1)->first(); expect($step->fresh()->status)->toBe('skipped'); expect($next->status)->toBe('in_progress'); Queue::assertPushed(RunWorkflowStepJob::class, fn ($job) => $job->stepId === $next->id); }); test('an awaiting_approval step shows a preview of its generated asset', function () { Storage::fake(); $project = VideoProject::factory()->create(); $engine = new WorkflowEngine; $step = $engine->start($project, [['type' => 'script_generation', 'requires_approval' => true]]); $asset = (new AssetStorage)->store($project, 'script', 'Once upon a time, a script.', 'txt', step: $step, mimeType: 'text/plain'); $engine->completeStep($step, ['asset_id' => $asset->id]); $this->actingAs(User::factory()->create()) ->get(route('video-projects.show', $project)) ->assertOk() ->assertSee('Once upon a time, a script.'); }); test('the assets section renders a preview for an image asset', function () { Storage::fake(); $project = VideoProject::factory()->create(); $asset = (new AssetStorage)->store($project, 'image', 'fake-png-bytes', 'png', mimeType: 'image/png'); $response = $this->actingAs(User::factory()->create()) ->get(route('video-projects.show', $project)); $response->assertOk(); $response->assertSee(route('video-projects.assets.show', [$project, $asset]), false); });