create(); $application = Application::factory()->for($organisation)->create(); $environment = Environment::factory()->for($application)->create(); $web = 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', 'deploy_policy' => DeployPolicy::WITH_ENVIRONMENT, ]); $postgres = Service::factory()->for($environment)->create([ 'organisation_id' => $organisation->id, 'name' => 'postgres', 'deploy_policy' => DeployPolicy::DEPENDENCY_ONLY, ]); $environment->attachments()->create([ 'service_id' => $postgres->id, 'role' => EnvironmentAttachmentRole::DATABASE, 'is_primary' => true, ]); $plan = app(PlanEnvironmentDeployment::class)->execute($environment); expect($plan->services) ->toHaveCount(1) ->and($plan->services[0]->is($web))->toBeTrue() ->and($plan->dependencies)->toHaveCount(1) ->and($plan->dependencies[0]->is($postgres))->toBeTrue(); }); it('blocks multi-server environment deployments when no registry exists', function () { config(['keystone.managed_registry.url' => null]); $organisation = Organisation::factory()->create(); $application = Application::factory()->for($organisation)->create(); $environment = Environment::factory()->for($application)->create(); 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', 'deploy_policy' => DeployPolicy::WITH_ENVIRONMENT, 'desired_replicas' => 2, ]); $plan = app(PlanEnvironmentDeployment::class)->execute($environment); expect($plan->requiresRegistry)->toBeTrue(); }); it('blocks multi-server environment deployments when managed registry smoke checks have not passed', function () { $organisation = Organisation::factory()->create(); $server = deploymentPlanBuildServerFor($organisation); $organisation->registries()->create([ 'name' => 'Managed', 'type' => RegistryType::MANAGED, 'url' => 'registry.example.com', 'credentials' => [ 'build_username' => 'keystone-build', 'build_password' => 'secret', 'runtime_username' => 'keystone-runtime', 'runtime_password' => 'runtime-secret', ], 'control_server_id' => $server->id, 'health_status' => 'pending', 'readiness_checks' => ['control_https' => 'passed', 'build_push' => 'pending'], ]); $application = Application::factory()->for($organisation)->create(); $environment = Environment::factory()->for($application)->create(); 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', 'deploy_policy' => DeployPolicy::WITH_ENVIRONMENT, 'desired_replicas' => 2, ]); $plan = app(PlanEnvironmentDeployment::class)->execute($environment); expect($plan->requiresRegistry)->toBeTrue() ->and($plan->blockers)->toContain('Managed registry has not passed readiness checks.'); }); it('allows multi-server environment deployments when a managed registry exists', function () { $organisation = Organisation::factory()->create(); $server = deploymentPlanBuildServerFor($organisation); $organisation->registries()->create([ 'name' => 'Managed', 'type' => RegistryType::MANAGED, 'url' => 'registry.example.com', 'credentials' => [ 'build_username' => 'keystone-build', 'build_password' => 'secret', 'runtime_username' => 'keystone-runtime', 'runtime_password' => 'runtime-secret', ], 'control_server_id' => $server->id, 'health_status' => 'healthy', 'readiness_checks' => ['control_https' => 'passed', 'build_push' => 'passed'], 'ready_at' => now(), ]); $application = Application::factory()->for($organisation)->create(); $environment = Environment::factory()->for($application)->create(); 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', 'deploy_policy' => DeployPolicy::WITH_ENVIRONMENT, 'desired_replicas' => 2, ]); $plan = app(PlanEnvironmentDeployment::class)->execute($environment); expect($plan->requiresRegistry)->toBeFalse(); }); function deploymentPlanBuildServerFor(Organisation $organisation): Server { $provider = Provider::factory()->forOrganisation($organisation)->create(); $network = Network::create([ 'organisation_id' => $organisation->id, 'provider_id' => $provider->id, 'name' => 'test-network', 'ip_range' => '10.0.0.0/24', ]); return Server::factory() ->forOrganisation($organisation->id) ->forProvider($provider->id) ->forNetwork($network->id) ->create([ 'is_control_node' => true, 'build_enabled' => true, ]); } it('warns about sync queues without creating worker services', function () { $organisation = Organisation::factory()->create(); $application = Application::factory()->for($organisation)->create(); $environment = Environment::factory()->for($application)->create(); $environment->variables()->create([ 'key' => 'QUEUE_CONNECTION', 'value' => 'sync', 'source' => 'user', 'overridable' => true, ]); $plan = app(PlanEnvironmentDeployment::class)->execute($environment); expect($plan->warnings) ->toContain('QUEUE_CONNECTION=sync is not recommended for deployed Laravel environments.'); }); it('blocks single scheduler mode when the scheduler target has multiple replicas', function () { $organisation = Organisation::factory()->create(); $application = Application::factory()->for($organisation)->create(); $environment = Environment::factory()->for($application)->create([ 'scheduler_enabled' => true, 'scheduler_mode' => SchedulerMode::SINGLE, ]); $web = 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', 'deploy_policy' => DeployPolicy::WITH_ENVIRONMENT, 'process_roles' => ['web', 'scheduler'], 'desired_replicas' => 2, ]); $environment->forceFill(['scheduler_target_service_id' => $web->id])->save(); $plan = app(PlanEnvironmentDeployment::class)->execute($environment->refresh()); expect($plan->blockers)->toContain('Scheduler mode single requires the scheduler target service to run exactly one replica.'); });