Files
keystone/tests/Feature/EnvironmentDeploymentPlanTest.php

216 lines
8.1 KiB
PHP

<?php
use App\Actions\Environments\PlanEnvironmentDeployment;
use App\Enums\DeployPolicy;
use App\Enums\EnvironmentAttachmentRole;
use App\Enums\RegistryType;
use App\Enums\SchedulerMode;
use App\Enums\ServiceCategory;
use App\Enums\ServiceType;
use App\Models\Application;
use App\Models\Environment;
use App\Models\Network;
use App\Models\Organisation;
use App\Models\Provider;
use App\Models\Server;
use App\Models\Service;
it('deploys only with-environment services and checks dependency attachments', function () {
$organisation = Organisation::factory()->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.');
});