import assert from "node:assert/strict" import test from "node:test" import { APP_STATE_VERSION, type AppSnapshot, type DnsRecordSet, type DomainPlan, type ProductCatalog, type ProviderConfig, type SchemeState, type ServerPlan, type StoredState, type VpsRecord } from "../../src/shared/app" import { buildPlannedDomainRemoval, buildPlannedDomainUpsert, buildPlannedNodeRemoval, buildPlannedNodeUpsert, findPlannedNodeOffers, getPlannedDomainNameServers } from "../../src/main/planned-resources" import { NodeCliService } from "../../src/main/cli/node-cli-service" import { deriveDomainDnsHosts } from "../../src/main/domain-hosts" import { resolveServer } from "../../src/main/node-admin-client" import type { LegionEngine } from "../../src/engine/runtime" function createCatalog(provider: "hetzner" | "ovh" | "scaleway"): ProductCatalog { return { provider, disclaimer: "", isDefaultProvider: false, generatedAt: "2026-04-07T00:00:00.000Z", defaultProductId: `${provider}-dev1-m`, products: [ { id: `${provider}-dev1-m`, provider, providerProductId: "dev1-m", label: "DEV1-M", planName: "DEV1-M", description: "", architecture: "x86_64", category: "general-purpose", cores: 3, cpuType: "shared", memoryGb: 4, diskGb: 40, storageType: "ssd", image: "debian-12", bootMode: provider === "scaleway" ? "efi" : "bios", defaultRegion: "fr-par-1", availableRegions: ["fr-par-1"], defaultPrice: { region: "fr-par-1", hourlyGrossEur: 0.03, hourlyNetEur: 0.025, monthlyGrossEur: 18, monthlyNetEur: 15 }, prices: [ { region: "fr-par-1", hourlyGrossEur: 0.03, hourlyNetEur: 0.025, monthlyGrossEur: 18, monthlyNetEur: 15 } ], variables: [ { key: "diskGb", label: "Disk", type: "integer", unit: "GB", defaultValue: 40, min: 40, max: 200, step: 10, affects: "diskGb" } ], commercialMetadata: { effectiveMonthlyGrossEur: 18, source: "provider", notes: [] }, recommended: true } ] } } function createProviderConfig(): ProviderConfig { return { id: "scaleway", configured: true, createdAt: "2026-04-07T00:00:00.000Z", projectId: "project-1", capabilities: { computeCatalog: true, computeProvisioning: true, computeReinstallFromImage: false, domainRegistration: false, standardDnsService: false, fallbackDnsService: false, dnsRecordManagement: false, firewallManagement: false }, credentials: { kind: "scaleway", accessKey: "access", secretKey: "secret", projectId: "project-1" } } } function createOvhProviderConfig(): ProviderConfig { return { id: "ovh", configured: true, createdAt: "2026-04-07T00:00:00.000Z", capabilities: { computeCatalog: true, computeProvisioning: true, computeReinstallFromImage: false, domainRegistration: true, standardDnsService: true, fallbackDnsService: false, dnsRecordManagement: true, firewallManagement: false }, credentials: { kind: "ovh", endpoint: "ovh-eu", applicationKey: "app-key", applicationSecret: "app-secret", consumerKey: "consumer-key" } } } function createStoredState(overrides: Partial = {}): StoredState { return { version: APP_STATE_VERSION, tribe: { name: "Tribe", description: "Test tribe", visibility: "invite_only" }, legionAdmin: null, clusterBootstrap: null, providers: [createProviderConfig()], bindings: [], trackedServers: [], scheme: { servers: [], domains: [], dnsHosts: [], dnsZones: [] }, actual: { servers: [], domains: [], dnsZones: [] }, tutorial: { completed: true }, ui: overrides.ui ?? {}, ...overrides, pendingProvisioning: overrides.pendingProvisioning ?? [], clusterMembership: overrides.clusterMembership ?? [] } } function createTrackedServer(overrides: Partial = {}): VpsRecord { return { id: "node-a", provider: "scaleway", providerProjectId: "project-1", providerServerId: "srv-123", providerSshKeyId: "key-1", providerSshKeyName: "key-1", providerResourceName: "legion-node-a", role: "primary", sshUsername: "root", sshPrivateKey: "PRIVATE", sshPublicKey: "PUBLIC", bootstrapSshAuthentication: null, status: "running", publicIp: "203.0.113.10", region: "fr-par-1", planName: "DEV1-M", billingTermMonths: 1, commercialMetadata: { effectiveMonthlyGrossEur: 18, source: "provider", notes: [] }, deployment: { status: "ready", snippets: [], lastAppliedAt: "2026-04-07T00:00:00.000Z" }, createdAt: "2026-04-07T00:00:00.000Z", updatedAt: "2026-04-07T00:00:00.000Z", ...overrides } } function createServerPlan(overrides: Partial = {}): ServerPlan { return { id: "node-a", label: "node-a", provider: "scaleway", instanceId: "scaleway-dev1-m", planName: "DEV1-M", region: "fr-par-1", commercialMetadata: { effectiveMonthlyGrossEur: 18, source: "provider", notes: [] }, cores: 3, memoryGb: 4, diskGb: 80, role: "primary", ...overrides } } function createDomainPlan(overrides: Partial = {}): DomainPlan { return { id: "domain-1", domain: "example.test", provider: "ovh", label: "example.test", autoRenew: true, ...overrides } } test("resolveServer accepts unique tracked node ID prefixes", () => { const state = createStoredState({ trackedServers: [ createTrackedServer({ id: "79d84fde-bca0-4c1c-9c7e-2827e285d8e8" }), createTrackedServer({ id: "01f62f94-1a0d-43f5-a945-c7729f340f3e" }) ] }) const server = resolveServer(state, "79d84fde") assert.equal(server.id, "79d84fde-bca0-4c1c-9c7e-2827e285d8e8") }) test("resolveServer accepts unique provider server ID prefixes", () => { const state = createStoredState({ trackedServers: [ createTrackedServer({ id: "node-a", providerServerId: "srv-79d84fde" }), createTrackedServer({ id: "node-b", providerServerId: "srv-01f62f94" }) ] }) const server = resolveServer(state, "srv-79") assert.equal(server.id, "node-a") }) test("resolveServer rejects ambiguous tracked node ID prefixes", () => { const state = createStoredState({ trackedServers: [ createTrackedServer({ id: "79d84fde-bca0-4c1c-9c7e-2827e285d8e8" }), createTrackedServer({ id: "79d84abc-1a0d-43f5-a945-c7729f340f3e" }) ] }) assert.throws( () => resolveServer(state, "79d84"), /Ambiguous node reference: 79d84 matches 79d84fde-bca0-4c1c-9c7e-2827e285d8e8, 79d84abc-1a0d-43f5-a945-c7729f340f3e/ ) }) test("buildPlannedNodeRemoval accepts unique planned node ID prefixes", () => { const state = createStoredState({ scheme: { servers: [ createServerPlan({ id: "79d84fde-bca0-4c1c-9c7e-2827e285d8e8" }), createServerPlan({ id: "01f62f94-1a0d-43f5-a945-c7729f340f3e" }) ], domains: [], dnsHosts: [], dnsZones: [] } }) const { result } = buildPlannedNodeRemoval(state, "79d84fde") assert.equal(result.serverId, "79d84fde-bca0-4c1c-9c7e-2827e285d8e8") }) test("buildPlannedNodeRemoval rejects ambiguous planned node ID prefixes", () => { const state = createStoredState({ scheme: { servers: [ createServerPlan({ id: "79d84fde-bca0-4c1c-9c7e-2827e285d8e8" }), createServerPlan({ id: "79d84abc-1a0d-43f5-a945-c7729f340f3e" }) ], domains: [], dnsHosts: [], dnsZones: [] } }) assert.throws( () => buildPlannedNodeRemoval(state, "79d84"), /Ambiguous node reference: 79d84 matches 79d84fde-bca0-4c1c-9c7e-2827e285d8e8, 79d84abc-1a0d-43f5-a945-c7729f340f3e/ ) }) test("buildPlannedNodeRemoval rejects ambiguous prefixes across planned and tracked nodes", () => { const state = createStoredState({ scheme: { servers: [createServerPlan({ id: "79d84fde-bca0-4c1c-9c7e-2827e285d8e8" })], domains: [], dnsHosts: [], dnsZones: [] }, trackedServers: [createTrackedServer({ id: "79d84abc-1a0d-43f5-a945-c7729f340f3e" })] }) assert.throws( () => buildPlannedNodeRemoval(state, "79d84"), /Ambiguous node reference: 79d84 matches 79d84fde-bca0-4c1c-9c7e-2827e285d8e8, 79d84abc-1a0d-43f5-a945-c7729f340f3e/ ) }) test("buildPlannedDomainRemoval accepts unique domain ID prefixes", () => { const state = createStoredState({ scheme: { servers: [], domains: [ createDomainPlan({ id: "domain-79d84fde-bca0" }), createDomainPlan({ id: "domain-01f62f94-1a0d", domain: "other.test", label: "other.test" }) ], dnsHosts: [], dnsZones: [] } }) const { domain } = buildPlannedDomainRemoval(state, "domain-79") assert.equal(domain.id, "domain-79d84fde-bca0") }) test("buildPlannedDomainRemoval rejects ambiguous domain ID prefixes", () => { const state = createStoredState({ scheme: { servers: [], domains: [ createDomainPlan({ id: "domain-79d84fde-bca0" }), createDomainPlan({ id: "domain-79d84abc-1a0d", domain: "other.test", label: "other.test" }) ], dnsHosts: [], dnsZones: [] } }) assert.throws( () => buildPlannedDomainRemoval(state, "domain-79d84"), /Ambiguous domain reference: domain-79d84 matches domain-79d84fde-bca0, domain-79d84abc-1a0d/ ) }) test("listNodes prefers planned labels over provider resource names", () => { const state = createStoredState({ scheme: { servers: [ { id: "node-a", label: "Primary server", provider: "scaleway", instanceId: "scaleway-dev1-m", planName: "DEV1-M", region: "fr-par-1", commercialMetadata: { effectiveMonthlyGrossEur: 18, source: "provider", notes: [] }, cores: 3, memoryGb: 4, diskGb: 80, role: "primary" } ], domains: [], dnsHosts: [], dnsZones: [] }, trackedServers: [ createTrackedServer({ providerResourceName: "steffen-79d84fde-bca0-4c1c-9c7e-2827e285" }) ] }) const { runtime } = createRuntime(state) const service = new NodeCliService(runtime) const [entry] = service.listNodes() assert.equal(entry?.name, "steffen-79d84fde-bca0-4c1c-9c7e-2827e285") assert.equal(entry?.label, "Primary server") }) test("listNodes omits labels that duplicate provider resource names", () => { const providerResourceName = "steffen-79d84fde-bca0-4c1c-9c7e-2827e285" const state = createStoredState({ scheme: { servers: [ { id: "node-a", label: providerResourceName, provider: "scaleway", instanceId: "scaleway-dev1-m", planName: "DEV1-M", region: "fr-par-1", commercialMetadata: { effectiveMonthlyGrossEur: 18, source: "provider", notes: [] }, cores: 3, memoryGb: 4, diskGb: 80, role: "primary" } ], domains: [], dnsHosts: [], dnsZones: [] }, trackedServers: [ createTrackedServer({ providerResourceName }) ] }) const { runtime } = createRuntime(state) const service = new NodeCliService(runtime) const [entry] = service.listNodes() assert.equal(entry?.name, providerResourceName) assert.equal(entry?.label, undefined) }) test("getNodeInfo returns detailed node fields with planned labels", () => { const state = createStoredState({ scheme: { servers: [createServerPlan({ id: "node-a", label: "Primary server" })], domains: [], dnsHosts: [], dnsZones: [] }, trackedServers: [ createTrackedServer({ id: "node-a", providerServerId: "srv-79d84fde", providerResourceName: "steffen-node-a", nbde: { mode: "degraded", tangPort: 7654, recoverySecret: "secret", localBootKeyPresent: true, peerTangNodeIds: [] }, execution: { operationId: "operation-1", operation: "add", status: "failed", phase: "rebooting-installed-system", retryable: true, startedAt: "2026-06-25T11:00:00.000Z", lastError: "ssh timeout", updatedAt: "2026-06-25T12:00:00.000Z" } }) ] }) const { runtime } = createRuntime(state) const service = new NodeCliService(runtime) const entry = service.getNodeInfo("srv-79") assert.equal(entry.id, "node-a") assert.equal(entry.name, "steffen-node-a") assert.equal(entry.label, "Primary server") assert.equal(entry.providerServerId, "srv-79d84fde") assert.equal(entry.nbdeMode, "degraded") assert.equal(entry.executionStatus, "failed") assert.equal(entry.executionPhase, "rebooting-installed-system") assert.equal(entry.executionRetryable, true) assert.equal(entry.executionError, "ssh timeout") }) function createRuntime(state: StoredState): { runtime: LegionEngine materializeCalls: { count: number } updateSchemeCalls: { count: number; last?: SchemeState } } { const materializeCalls = { count: 0 } const updateSchemeCalls: { count: number; last?: SchemeState } = { count: 0 } const createSnapshot = (): AppSnapshot => ({ tribe: state.tribe, legionAdmin: state.legionAdmin ? { id: state.legionAdmin.id, username: state.legionAdmin.username, role: state.legionAdmin.role, publicKeyHex: state.legionAdmin.publicKeyHex, npub: state.legionAdmin.npub, createdAt: state.legionAdmin.createdAt, updatedAt: state.legionAdmin.updatedAt } : null, providers: state.providers, scheme: state.scheme, instances: { servers: [], domains: [], dnsHosts: state.scheme.dnsHosts, dnsZones: [] }, plannedChanges: [], isConfigured: true, tutorial: state.tutorial, ui: state.ui, hasPendingChanges: false }) const plannedResources = { getState: () => state, getProductCatalog: async () => createCatalog("scaleway"), getDnsRecordSets: async () => [ { id: "ns-root", provider: "ovh", zoneName: "example.test", name: "@", type: "NS", values: [{ value: "ns10.ovh.net." }, { value: "dns10.ovh.net." }], observedAt: "2026-04-07T00:00:00.000Z" } ] satisfies DnsRecordSet[], quoteOvhDomainRegistration: async () => ({ domainName: "example.test", available: true, offers: [ { duration: "P1Y", months: 12, currency: "EUR", totalGrossEur: 12, monthlyGrossEur: 1, request: { planCode: "com", pricingMode: "create-default" } } ] }) } const runtime = { stateStore: { hasUnlockedState: () => true, getStateOrThrow: () => state, rekey: async () => undefined, exportStateFile: async () => undefined, importStateFile: async () => undefined, upsertActualDomain: async () => undefined, removeActualDomain: async () => undefined, replaceActualDomains: async () => undefined }, getSnapshot: createSnapshot, provisioning: { getActivity: () => null }, nodeDeployments: { setReporter: () => undefined, reinstallNode: async () => createTrackedServer(), retryNode: async () => createTrackedServer(), promoteNbdeNode: async () => createTrackedServer(), reconcileNbde: async () => [] }, getProductCatalog: plannedResources.getProductCatalog, getDnsRecordSets: plannedResources.getDnsRecordSets, findNodeOffers: async (request) => findPlannedNodeOffers(plannedResources, request), upsertServerPlan: async (request) => { const { scheme, result } = await buildPlannedNodeUpsert(plannedResources, request) state.scheme = scheme updateSchemeCalls.count += 1 updateSchemeCalls.last = scheme return result }, removeServerPlan: async (request: { serverId: string }) => { const { scheme, result } = buildPlannedNodeRemoval(state, request.serverId) state.scheme = scheme updateSchemeCalls.count += 1 updateSchemeCalls.last = scheme return result }, upsertDomainPlan: async (request) => { const { scheme, result } = buildPlannedDomainUpsert(plannedResources, request) state.scheme = scheme updateSchemeCalls.count += 1 updateSchemeCalls.last = scheme return result }, removeDomainPlan: async (ref: string) => { const { scheme, domain } = buildPlannedDomainRemoval(state, ref) state.scheme = scheme updateSchemeCalls.count += 1 updateSchemeCalls.last = scheme return domain }, getPlannedDomainNameServers: async (ref: string) => getPlannedDomainNameServers(plannedResources, ref), quoteDomain: async () => ({ domain: "example.test", provider: "ovh", available: true, source: "ovh-order-api", offers: [ { months: 12, duration: "P1Y", currency: "EUR", totalGrossEur: 12, monthlyGrossEur: 1, planCode: "com", pricingMode: "create-default", offerId: undefined } ] }), unlock: async () => ({ snapshot: state, activity: null, auth: { locked: false, availableMethods: ["password"] } }), updateScheme: async ({ scheme }: { scheme: SchemeState }) => { state.scheme = scheme updateSchemeCalls.count += 1 updateSchemeCalls.last = scheme return { snapshot: state, activity: null, auth: { locked: false, availableMethods: ["password"] } } }, materialize: async () => { materializeCalls.count += 1 state.trackedServers = state.scheme.servers.map((server) => createTrackedServer({ id: server.id, provider: server.provider, providerResourceName: `legion-${server.id}`, publicIp: server.publicIp ?? "203.0.113.10", region: server.region, planName: server.planName }) ) return { snapshot: state, activity: null, auth: { locked: false, availableMethods: ["password"] } } } } as unknown as LegionEngine return { runtime, materializeCalls, updateSchemeCalls } } test("rekeyConfig unlocks with the current password and persists the new unlock password", async () => { const state = createStoredState() let unlockedWith: string | null = null let rekeyedWith: { current?: string; next: string } | null = null const runtime = { stateStore: { hasUnlockedState: () => false, getStateOrThrow: () => state, getSnapshot: () => ({ tribe: state.tribe, legionAdmin: null, providers: state.providers, scheme: state.scheme, instances: { servers: [], domains: [], dnsHosts: state.scheme.dnsHosts, dnsZones: [] }, plannedChanges: [], isConfigured: true, tutorial: state.tutorial, ui: state.ui, hasPendingChanges: false }) satisfies AppSnapshot, rekey: async (currentPassword: string | undefined, nextPassword: string) => { rekeyedWith = { current: currentPassword, next: nextPassword } }, exportStateFile: async () => undefined, importStateFile: async () => undefined, upsertActualDomain: async () => undefined, removeActualDomain: async () => undefined, replaceActualDomains: async () => undefined }, provisioning: { getActivity: () => null }, nodeDeployments: { setReporter: () => undefined, reinstallNode: async () => createTrackedServer(), retryNode: async () => createTrackedServer(), promoteNbdeNode: async () => createTrackedServer(), reconcileNbde: async () => [] }, unlock: async ({ method }: { method?: { type: "password"; password: string } }) => { unlockedWith = method?.password ?? null return { snapshot: state, activity: null, auth: { locked: false, availableMethods: ["password"] } } } } as unknown as LegionEngine process.env["LEGION_NEXT_UNLOCK_PASSWORD"] = "new-password" try { const service = new NodeCliService(runtime) const result = await service.rekeyConfig({ currentPassword: "old-password", newPasswordEnv: "LEGION_NEXT_UNLOCK_PASSWORD" }) assert.equal(unlockedWith, "old-password") assert.deepEqual(rekeyedWith, { current: "old-password", next: "new-password" }) assert.equal(result.config.name, "Tribe") assert.equal(result.config.description, "Test tribe") } finally { delete process.env["LEGION_NEXT_UNLOCK_PASSWORD"] } }) test("exportConfig writes an encrypted backup with the current or overridden password", async () => { const state = createStoredState() let exported: { path: string; password?: string } | null = null const runtime = { stateStore: { hasUnlockedState: () => true, getStateOrThrow: () => state, getSnapshot: () => null, exportStateFile: async (path: string, password?: string) => { exported = { path, password } }, importStateFile: async () => undefined, rekey: async () => undefined, upsertActualDomain: async () => undefined, removeActualDomain: async () => undefined, replaceActualDomains: async () => undefined }, provisioning: { getActivity: () => null }, nodeDeployments: { setReporter: () => undefined, reinstallNode: async () => createTrackedServer(), retryNode: async () => createTrackedServer(), promoteNbdeNode: async () => createTrackedServer(), reconcileNbde: async () => [] } } as unknown as LegionEngine process.env["LEGION_BACKUP_PASSWORD"] = "backup-password" try { const service = new NodeCliService(runtime) const result = await service.exportConfig({ out: "/tmp/legion-backup.json", passwordEnv: "LEGION_BACKUP_PASSWORD" }) assert.deepEqual(exported, { path: "/tmp/legion-backup.json", password: "backup-password" }) assert.equal(result.path, "/tmp/legion-backup.json") assert.equal(result.config.name, "Tribe") } finally { delete process.env["LEGION_BACKUP_PASSWORD"] } }) test("importConfig restores an encrypted backup into the unlocked state store", async () => { const state = createStoredState() let imported: { path: string; password?: string } | null = null const runtime = { stateStore: { hasUnlockedState: () => true, getStateOrThrow: () => state, getSnapshot: () => null, exportStateFile: async () => undefined, importStateFile: async (path: string, password?: string) => { imported = { path, password } }, rekey: async () => undefined, upsertActualDomain: async () => undefined, removeActualDomain: async () => undefined, replaceActualDomains: async () => undefined }, provisioning: { getActivity: () => null }, nodeDeployments: { setReporter: () => undefined, reinstallNode: async () => createTrackedServer(), retryNode: async () => createTrackedServer(), promoteNbdeNode: async () => createTrackedServer(), reconcileNbde: async () => [] } } as unknown as LegionEngine const service = new NodeCliService(runtime) const result = await service.importConfig({ in: "/tmp/legion-backup.json", password: "backup-password" }) assert.deepEqual(imported, { path: "/tmp/legion-backup.json", password: "backup-password" }) assert.equal(result.path, "/tmp/legion-backup.json") assert.equal(result.config.visibility, "invite_only") }) test("addNode writes the desired server plan without materializing by default", async () => { const state = createStoredState() const { runtime, materializeCalls, updateSchemeCalls } = createRuntime(state) const service = new NodeCliService(runtime) const result = await service.addNode({ name: "node-a", provider: "scaleway", instance: "dev1-m", diskGb: 80, dnsHostname: "edge", bootMode: "efi" }) assert.equal(updateSchemeCalls.count, 1) assert.equal(materializeCalls.count, 0) assert.equal(state.scheme.servers.length, 1) assert.deepEqual(state.scheme.servers[0]!.deploymentIntent, { dnsHostname: "edge", bootMode: "efi" }) assert.equal(state.scheme.servers[0]!.instanceId, "scaleway-dev1-m") assert.equal(state.scheme.servers[0]!.diskGb, 80) assert.equal(result.node.id, "node-a") assert.equal(result.node.provider, "scaleway") assert.equal(state.trackedServers.length, 0) }) test("addNode materializes the full scheme when requested", async () => { const state = createStoredState() const { runtime, materializeCalls } = createRuntime(state) const service = new NodeCliService(runtime) const result = await service.addNode( { name: "node-a", provider: "scaleway", instance: "dev1-m" }, { materialize: true } ) assert.equal(materializeCalls.count, 1) assert.equal(result.node.id, "node-a") assert.equal(result.node.provider, "scaleway") }) test("addNode stores a relative DNS hostname and derives domain DNS hosts", async () => { const state = createStoredState({ scheme: { servers: [], domains: [ { id: "domain-1", domain: "example.test", provider: "ovh", label: "example.test" } ], dnsHosts: [], dnsZones: [ { id: "zone-1", zoneName: "example.test", provider: "ovh", records: [] } ] }, providers: [createProviderConfig(), createOvhProviderConfig()] }) const { runtime } = createRuntime(state) const service = new NodeCliService(runtime) await service.addNode({ name: "node-a", provider: "scaleway", instance: "dev1-m", dnsHostname: "edge" }) assert.deepEqual(state.scheme.dnsHosts, []) assert.deepEqual(deriveDomainDnsHosts(state), [ { id: "dns-host-node-a", zoneId: "zone-1", hostname: "edge.example.test", serverIds: ["node-a"] } ]) }) test("destroyNode removes the desired server from the scheme without materializing by default", async () => { const existingPlan: ServerPlan = { id: "node-a", label: "node-a", provider: "scaleway", instanceId: "scaleway-dev1-m", planName: "DEV1-M", region: "fr-par-1", commercialMetadata: { effectiveMonthlyGrossEur: 18, source: "provider", notes: [] }, cores: 3, memoryGb: 4, diskGb: 80, role: "primary" } const state = createStoredState({ scheme: { servers: [existingPlan], domains: [], dnsHosts: [], dnsZones: [] }, trackedServers: [createTrackedServer()] }) const { runtime, materializeCalls, updateSchemeCalls } = createRuntime(state) const service = new NodeCliService(runtime) const result = await service.destroyNode("node-a") assert.equal(updateSchemeCalls.count, 1) assert.equal(materializeCalls.count, 0) assert.deepEqual(state.scheme.servers, []) assert.equal(state.trackedServers.length, 1) assert.equal(result.node.id, "node-a") }) test("destroyNode materializes full deletion when requested", async () => { const existingPlan: ServerPlan = { id: "node-a", label: "node-a", provider: "scaleway", instanceId: "scaleway-dev1-m", planName: "DEV1-M", region: "fr-par-1", commercialMetadata: { effectiveMonthlyGrossEur: 18, source: "provider", notes: [] }, cores: 3, memoryGb: 4, diskGb: 80, role: "primary" } const state = createStoredState({ scheme: { servers: [existingPlan], domains: [], dnsHosts: [], dnsZones: [] }, trackedServers: [createTrackedServer()] }) const { runtime, materializeCalls } = createRuntime(state) const service = new NodeCliService(runtime) await service.destroyNode("node-a", { materialize: true }) assert.equal(materializeCalls.count, 1) assert.deepEqual(state.trackedServers, []) }) test("addDomain writes only the desired domain plan without materializing by default", async () => { const state = createStoredState({ scheme: { servers: [], domains: [], dnsHosts: [], dnsZones: [ { id: "zone-1", zoneName: "example.test", provider: "ovh", records: [] } ] }, providers: [createProviderConfig(), createOvhProviderConfig()] }) const { runtime, materializeCalls, updateSchemeCalls } = createRuntime(state) const service = new NodeCliService(runtime) const result = await service.addDomain({ domain: "example.test", provider: "ovh", registrantContact: { firstName: "Ada", lastName: "Lovelace", email: "ada@example.test", phone: "+49.30.555555", address: { line1: "Cloud Street 1", city: "Berlin", postalCode: "10115", countryCode: "DE" } } }) assert.equal(updateSchemeCalls.count, 1) assert.equal(materializeCalls.count, 0) assert.equal(state.scheme.domains.length, 1) assert.equal(state.scheme.dnsZones.length, 1) assert.equal(state.scheme.domains[0]?.domain, "example.test") assert.equal(state.scheme.domains[0]?.provider, "ovh") assert.equal(state.scheme.domains[0]?.autoRenew, true) assert.equal(state.scheme.domains[0]?.registrantContact?.email, "ada@example.test") assert.equal(result.domain.provider, "ovh") assert.equal(result.domain.zoneId, "zone-1") }) test("listDomains includes current registrar and delegation status when available", () => { const state = createStoredState({ providers: [createProviderConfig(), createOvhProviderConfig()], scheme: { servers: [], domains: [ { id: "domain-1", domain: "example.test", provider: "ovh", label: "example.test", autoRenew: true } ], dnsHosts: [], dnsZones: [ { id: "zone-1", zoneName: "example.test", provider: "ovh", records: [] } ] }, bindings: [ { resourceType: "domain", localId: "domain-1", provider: "ovh", remoteId: "example.test", remoteName: "example.test", matchSource: "name", status: "managed", lastSeenAt: "2026-04-10T00:00:00.000Z", createdAt: "2026-04-10T00:00:00.000Z", updatedAt: "2026-04-10T00:00:00.000Z" }, { resourceType: "dns-zone", localId: "zone-1", provider: "ovh", remoteId: "example.test", remoteName: "example.test", matchSource: "name", status: "managed", lastSeenAt: "2026-04-10T00:00:00.000Z", createdAt: "2026-04-10T00:00:00.000Z", updatedAt: "2026-04-10T00:00:00.000Z" } ], actual: { servers: [], domains: [ { provider: "ovh", remoteId: "example.test", remoteName: "example.test", domainName: "example.test", autoRenew: true, nameServers: ["dns10.ovh.net.", "ns10.ovh.net."], dnsMode: "hosted", observedAt: "2026-04-10T00:00:00.000Z", matchSource: "name" } ], dnsZones: [ { provider: "ovh", remoteId: "example.test", remoteName: "example.test", zoneName: "example.test", records: [ { id: "ns-root", provider: "ovh", zoneName: "example.test", name: "@", type: "NS", values: [{ value: "ns10.ovh.net." }, { value: "dns10.ovh.net." }], observedAt: "2026-04-10T00:00:00.000Z" } ], observedAt: "2026-04-10T00:00:00.000Z", matchSource: "name" } ], refreshedAt: "2026-04-10T00:00:00.000Z" } }) const { runtime } = createRuntime(state) const service = new NodeCliService(runtime) const [domain] = service.listDomains() assert.equal(domain?.registrationStatus, "registered") assert.equal(domain?.delegationStatus, "hosted") assert.deepEqual(domain?.desiredNameServers, ["dns10.ovh.net", "ns10.ovh.net"]) assert.deepEqual(domain?.observedNameServers, ["dns10.ovh.net", "ns10.ovh.net"]) assert.equal(domain?.bound, true) }) test("destroyDomain removes the domain plan and leaves the zone plan intact", async () => { const existingDomain: DomainPlan = { id: "domain-1", domain: "example.test", provider: "ovh", label: "example.test", autoRenew: true, registrantContact: { firstName: "Ada", lastName: "Lovelace", email: "ada@example.test", phone: "+49.30.555555", address: { line1: "Cloud Street 1", city: "Berlin", postalCode: "10115", countryCode: "DE" } } } const state = createStoredState({ providers: [createProviderConfig(), createOvhProviderConfig()], scheme: { servers: [], domains: [existingDomain], dnsHosts: [], dnsZones: [ { id: "dns-domain-1", zoneName: "example.test", provider: "ovh", records: [] } ] } }) const { runtime, materializeCalls, updateSchemeCalls } = createRuntime(state) const service = new NodeCliService(runtime) const result = await service.destroyDomain("example.test") assert.equal(updateSchemeCalls.count, 1) assert.equal(materializeCalls.count, 0) assert.equal(state.scheme.domains.length, 0) assert.equal(state.scheme.dnsZones.length, 1) assert.equal(result.domain.id, "domain-1") assert.equal(result.domain.zoneId, "dns-domain-1") }) test("getDomainNameServers returns authoritative apex NS records from the managed zone", async () => { const existingDomain: DomainPlan = { id: "domain-1", domain: "example.test", provider: "ovh", label: "example.test" } const state = createStoredState({ providers: [createProviderConfig(), createOvhProviderConfig()], scheme: { servers: [], domains: [existingDomain], dnsHosts: [], dnsZones: [ { id: "dns-domain-1", zoneName: "example.test", provider: "ovh", records: [] } ] } }) const { runtime } = createRuntime(state) const service = new NodeCliService(runtime) const result = await service.getDomainNameServers("example.test") assert.equal(result.zoneName, "example.test") assert.equal(result.source, "zone-records") assert.deepEqual(result.nameServers, ["dns10.ovh.net", "ns10.ovh.net"]) }) test("quoteDomain returns OVH registration offers for a concrete domain", async () => { const state = createStoredState({ providers: [createProviderConfig(), createOvhProviderConfig()] }) const { runtime } = createRuntime(state) const captured: Array> = [] runtime.quoteDomain = async (request) => { captured.push(request as unknown as Record) return { domain: "example.test", provider: "ovh", available: true, source: "ovh-order-api", offers: [ { months: 12, duration: "P1Y", currency: "EUR", totalGrossEur: 12, monthlyGrossEur: 1, planCode: "com", pricingMode: "create-default", offerId: undefined } ] } } const service = new NodeCliService(runtime) const result = await service.quoteDomain({ domain: "example.test", provider: "ovh" }) assert.deepEqual(captured, [ { domain: "example.test", provider: "ovh" } ]) assert.equal(result.provider, "ovh") assert.equal(result.source, "ovh-order-api") assert.equal(result.available, true) assert.deepEqual(result.offers, [ { months: 12, duration: "P1Y", currency: "EUR", totalGrossEur: 12, monthlyGrossEur: 1, planCode: "com", pricingMode: "create-default", offerId: undefined } ]) })