create(); $organisation = Organisation::factory()->create(['owner_id' => $user->id]); $application = Application::factory()->for($organisation)->create(); $environment = Environment::factory()->for($application)->create(); $service = Service::factory()->for($environment)->create([ 'organisation_id' => $organisation->id, 'name' => 'web', 'category' => ServiceCategory::APPLICATION, 'type' => ServiceType::LARAVEL, 'version' => 'php-8.4', 'version_track' => 'php-8.4', 'driver_name' => 'laravel.php-8.4', 'process_roles' => ['web'], 'config' => [ 'migration_mode' => 'manual', 'migration_command' => 'php artisan migrate --force', ], ]); $response = $this->actingAs($user)->post(route('environment-migrations.store', [ 'organisation' => $organisation->id, 'application' => $application->id, 'environment' => $environment->id, ])); $response->assertRedirect(route('applications.show', [ 'organisation' => $organisation->id, 'application' => $application->id, ])); $operation = $service->operations()->firstOrFail(); expect($operation->kind)->toBe(OperationKind::CONFIG_CHANGE) ->and($operation->steps()->first()->script) ->toBe("docker compose -f /home/keystone/services/{$service->id}/compose.yml run --rm web php artisan migrate --force"); }); it('rejects migration operations without a laravel runtime service', function () { $environment = Environment::factory()->create(); expect(fn () => app(CreateMigrationOperation::class)->execute($environment)) ->toThrow(InvalidArgumentException::class, 'Laravel migrations must run against a Laravel runtime service.'); });