Files
legion_kk/tests/unit/node-cli-service.test.ts
self 53b96da272 feat: derive domain DNS and certificates
Replace the node-level DNS-name model with relative DNS hostnames and derived domain state. Derive apex and node-alias DNS hosts, public hosts, certificate subjects, challenge config, and certificate reconfigure operations while keeping IP-only deployments first-class.
2026-06-27 00:17:16 +02:00

1358 lines
38 KiB
TypeScript

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(providerKind: "hetzner" | "ovh" | "scaleway"): ProductCatalog {
return {
providerKind,
disclaimer: "",
isDefaultProvider: false,
generatedAt: "2026-04-07T00:00:00.000Z",
defaultProductId: `${providerKind}-dev1-m`,
products: [
{
id: `${providerKind}-dev1-m`,
providerKind,
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: providerKind === "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-e2e",
kind: "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",
kind: "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> = {}): 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> = {}): VpsRecord {
return {
id: "node-a",
providerId: "scaleway-e2e",
providerKind: "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> = {}): ServerPlan {
return {
id: "node-a",
label: "node-a",
providerKind: "scaleway",
providerId: "scaleway-e2e",
source: "catalog",
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> = {}): DomainPlan {
return {
id: "domain-1",
domain: "example.test",
providerKind: "ovh",
providerId: "ovh",
source: "managed",
delegatedZoneId: "dns-domain-1",
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",
providerKind: "scaleway",
providerId: "scaleway-e2e",
source: "catalog",
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,
providerKind: "scaleway",
providerId: "scaleway-e2e",
source: "catalog",
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",
providerKind: "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",
providerId: "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,
providerKind: server.providerKind === "manual" ? "scaleway" : server.providerKind,
providerId: server.providerId ?? "scaleway-e2e",
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",
providerId: "scaleway-e2e",
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",
providerId: "scaleway-e2e",
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",
providerKind: "ovh",
providerId: "ovh",
source: "external",
delegatedZoneId: "zone-1",
label: "example.test"
}
],
dnsHosts: [],
dnsZones: [
{
id: "zone-1",
zoneName: "example.test",
providerKind: "ovh",
providerId: "ovh",
records: []
}
]
},
providers: [createProviderConfig(), createOvhProviderConfig()]
})
const { runtime } = createRuntime(state)
const service = new NodeCliService(runtime)
await service.addNode({
name: "node-a",
providerId: "scaleway-e2e",
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",
providerKind: "scaleway",
providerId: "scaleway-e2e",
source: "catalog",
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",
providerKind: "scaleway",
providerId: "scaleway-e2e",
source: "catalog",
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({
providers: [createProviderConfig(), createOvhProviderConfig()]
})
const { runtime, materializeCalls, updateSchemeCalls } = createRuntime(state)
const service = new NodeCliService(runtime)
const result = await service.addDomain({
domain: "example.test",
provider: "ovh",
source: "managed",
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, 0)
assert.equal(state.scheme.domains[0]?.domain, "example.test")
assert.equal(state.scheme.domains[0]?.providerKind, "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, undefined)
})
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",
providerKind: "ovh",
providerId: "ovh",
source: "managed",
delegatedZoneId: "zone-1",
label: "example.test",
autoRenew: true
}
],
dnsHosts: [],
dnsZones: [
{
id: "zone-1",
zoneName: "example.test",
providerKind: "ovh",
providerId: "ovh",
records: []
}
]
},
bindings: [
{
resourceType: "domain",
localId: "domain-1",
providerKind: "ovh",
providerId: "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",
providerKind: "ovh",
providerId: "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: [
{
providerKind: "ovh",
providerId: "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: [
{
providerKind: "ovh",
providerId: "ovh",
remoteId: "example.test",
remoteName: "example.test",
zoneName: "example.test",
records: [
{
id: "ns-root",
providerKind: "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",
providerKind: "ovh",
providerId: "ovh",
source: "managed",
delegatedZoneId: "dns-domain-1",
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",
providerKind: "ovh",
providerId: "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",
providerKind: "ovh",
providerId: "ovh",
source: "external",
delegatedZoneId: "dns-domain-1",
label: "example.test"
}
const state = createStoredState({
providers: [createProviderConfig(), createOvhProviderConfig()],
scheme: {
servers: [],
domains: [existingDomain],
dnsHosts: [],
dnsZones: [
{
id: "dns-domain-1",
zoneName: "example.test",
providerKind: "ovh",
providerId: "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<Record<string, unknown>> = []
runtime.quoteDomain = async (request) => {
captured.push(request as unknown as Record<string, unknown>)
return {
domain: "example.test",
provider: "ovh",
providerId: "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",
providerId: "ovh"
})
assert.deepEqual(captured, [
{
domain: "example.test",
provider: "ovh",
providerId: "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
}
])
})