Files
keystone/tests/Feature/StatefulServiceUpdateTest.php

81 lines
3.4 KiB
PHP

<?php
use App\Actions\Services\CreateStatefulServiceUpdateOperation;
use App\Enums\ServiceCategory;
use App\Enums\ServiceType;
use App\Models\Service;
it('creates explicit downtime-aware update operations for postgres', function () {
$service = Service::factory()->create([
'name' => 'postgres',
'category' => ServiceCategory::DATABASE,
'type' => ServiceType::POSTGRES,
'version' => '18',
'version_track' => '18',
'driver_name' => 'postgres.18',
'current_image_digest' => 'sha256:old',
'config' => [
'backup_enabled' => true,
'backup_command' => 'pg_dump --format=custom keystone > /home/keystone/backups/pre-update.dump',
],
]);
$operation = app(CreateStatefulServiceUpdateOperation::class)->execute(
service: $service,
imageDigest: 'sha256:new',
backupRequested: true,
);
expect($service->refresh()->available_image_digest)->toBe('sha256:new')
->and($service->update_status)->toBe('update_pending')
->and($operation->steps()->pluck('name')->all())
->toBe([
'Acknowledge downtime and data risk',
'Run pre-update backup',
'Render compose with updated image digest',
'Stop existing container',
'Preserve named volume',
'Start service with updated image digest',
'Health check updated service',
])
->and($operation->steps()->where('name', 'Run pre-update backup')->first()->script)
->toBe('pg_dump --format=custom keystone > /home/keystone/backups/pre-update.dump')
->and($operation->steps()->where('name', 'Render compose with updated image digest')->first()->script)
->toContain('base64 -d')
->and($operation->steps()->where('name', 'Stop existing container')->first()->script)
->toBe("docker compose -f /home/keystone/services/{$service->id}/compose.yml stop {$service->name}")
->and($operation->steps()->where('name', 'Preserve named volume')->first()->script)
->toBe("docker volume inspect keystone_service_{$service->id}_postgres_data >/dev/null")
->and($operation->steps()->where('name', 'Health check updated service')->first()->script)
->toContain('docker inspect --format');
});
it('rejects backup requests when no backup capability is configured', function () {
$service = Service::factory()->create([
'category' => ServiceCategory::DATABASE,
'type' => ServiceType::POSTGRES,
'version' => '18',
'version_track' => '18',
'driver_name' => 'postgres.18',
'config' => [
'backup_enabled' => false,
],
]);
expect(fn () => app(CreateStatefulServiceUpdateOperation::class)->execute($service, 'sha256:new', backupRequested: true))
->toThrow(InvalidArgumentException::class, 'Backups are not configured for this service.');
});
it('rejects stateful update operations for stateless laravel services', function () {
$service = Service::factory()->create([
'category' => ServiceCategory::APPLICATION,
'type' => ServiceType::LARAVEL,
'version' => 'php-8.4',
'version_track' => 'php-8.4',
'driver_name' => 'laravel.php-8.4',
]);
expect(fn () => app(CreateStatefulServiceUpdateOperation::class)->execute($service, 'sha256:new'))
->toThrow(InvalidArgumentException::class, 'Only Postgres and Valkey have v1 stateful update operations.');
});