7]); $plan = (new Valkey8Driver(service: $service))->getOperationPlan('hash'); expect($plan->steps)->toHaveCount(2); expect($plan->steps[0]->name)->toBe('Render Compose file'); expect($plan->steps[1]->getScript())->toContain('compose -f /home/keystone/services/7/compose.yml'); }); it('reports static metadata about the valkey driver', function () { $driver = new Valkey8Driver; expect($driver->serviceType())->toBe(ServiceType::VALKEY); expect($driver->versionTrack())->toBe('8'); expect($driver->defaultImage())->toBe('valkey/valkey:8'); expect($driver->defaultPorts())->toBe([6379]); expect($driver->firewallRules())->toBe(['6379/tcp']); expect($driver->environmentSchema())->toBe([]); expect($driver->resourceDefaults())->toBe([]); expect($driver->updateBehavior())->toBe('stateful_downtime'); expect($driver->supportedSliceTypes())->toBe(['logical_database']); expect($driver->environmentExports())->toBe([]); }); it('exports redis env vars for a slice and adapts to the attachment role', function () { $service = new Service(['id' => 3, 'name' => 'cache']); $slice = new ServiceSlice([ 'service_id' => 3, 'config' => ['host' => 'valkey-host', 'port' => 7000, 'database' => 2], ]); $slice->setRelation('service', $service); $driver = new Valkey8Driver(service: $service); $base = $driver->environmentExportsForSlice($slice); expect($base)->toMatchArray([ 'REDIS_HOST' => 'valkey-host', 'REDIS_PORT' => '7000', 'REDIS_DB' => '2', ]); expect($driver->environmentExportsForSlice($slice, EnvironmentAttachmentRole::CACHE)) ->toHaveKey('CACHE_STORE', 'redis'); expect($driver->environmentExportsForSlice($slice, EnvironmentAttachmentRole::QUEUE)) ->toHaveKey('QUEUE_CONNECTION', 'redis'); expect($driver->environmentExportsForSlice($slice, EnvironmentAttachmentRole::DATABASE)) ->not->toHaveKey('CACHE_STORE'); }); it('uses defaults when slice config is missing', function () { $service = new Service(['id' => 3]); $slice = new ServiceSlice(['service_id' => 3, 'config' => []]); $driver = new Valkey8Driver(service: $service); expect($driver->environmentExportsForSlice($slice))->toBe([ 'REDIS_HOST' => 'keystone-service-3', 'REDIS_PORT' => '6379', 'REDIS_DB' => '0', ]); }); it('builds a slice provision script using the service slug', function () { $service = new Service(['id' => 9, 'name' => 'My Cache']); $slice = new ServiceSlice(['service_id' => 9, 'config' => ['database' => 4]]); $slice->setRelation('service', $service); $driver = new Valkey8Driver(service: $service); $script = $driver->provisionSliceScript($slice); expect($script)->toContain('docker compose -f /home/keystone/services/9/compose.yml exec -T my_cache valkey-cli'); expect($script)->toContain("-n '4' PING"); }); it('renders a compose service without persistence by default', function () { $service = new Service(['id' => 1, 'config' => []]); $compose = (new Valkey8Driver(service: $service))->composeService(); expect($compose['image'])->toBe('valkey/valkey:8'); expect($compose)->not->toHaveKey('volumes'); expect($compose)->not->toHaveKey('command'); }); it('renders a compose service with persistence enabled', function () { $service = new Service(['id' => 1, 'config' => ['persistence' => true]]); $driver = new Valkey8Driver(service: $service); $compose = $driver->composeService(); expect($compose['volumes'])->toBe(['keystone_service_1_valkey_data:/data']); expect($compose['command'])->toBe(['valkey-server', '--appendonly', 'yes']); expect($driver->composeVolumes())->toBe(['keystone_service_1_valkey_data' => null]); }); it('returns no compose volumes without persistence', function () { $service = new Service(['id' => 1, 'config' => []]); expect((new Valkey8Driver(service: $service))->composeVolumes())->toBe([]); });