This commit is contained in:
2026-06-25 18:02:16 -05:00
parent 402bf6782a
commit da16e5ff56
17 changed files with 1475 additions and 69 deletions

View File

@@ -6,7 +6,9 @@ import { load } from './setup.mjs';
const {
buildVmListPath,
buildHostPayloadFromVm,
buildStandaloneHostCandidate,
filterSummaryVms,
getVCenterVmPowerState,
normalizeBaseUrl,
normalizeVCenterVm,
previewVCenterHosts,
@@ -14,29 +16,124 @@ const {
vmMatchesFilter
} = await load('../services/vcenterService.js');
const originalFetch = globalThis.fetch;
function soapResponse(body) {
return new Response(`<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body>${body}</soapenv:Body></soapenv:Envelope>`, {
status: 200,
headers: { 'content-type': 'text/xml' }
});
}
function installStandaloneEsxiSoapStub() {
globalThis.fetch = async (_url, options = {}) => {
const body = String(options.body || '');
if (body.includes('<RetrieveServiceContent')) {
return soapResponse(`<RetrieveServiceContentResponse xmlns="urn:vim25"><returnval><rootFolder type="Folder">ha-folder-root</rootFolder><propertyCollector type="PropertyCollector">ha-property-collector</propertyCollector><viewManager type="ViewManager">ViewManager</viewManager><sessionManager type="SessionManager">ha-sessionmgr</sessionManager></returnval></RetrieveServiceContentResponse>`);
}
if (body.includes('<Login')) {
return soapResponse(`<LoginResponse xmlns="urn:vim25"><returnval type="UserSession">session-1</returnval></LoginResponse>`);
}
if (body.includes('<CreateContainerView')) {
return soapResponse(`<CreateContainerViewResponse xmlns="urn:vim25"><returnval type="ContainerView">session[52b6]-vm-view</returnval></CreateContainerViewResponse>`);
}
if (body.includes('<RetrievePropertiesEx')) {
return soapResponse(`<RetrievePropertiesExResponse xmlns="urn:vim25"><returnval><objects><obj type="VirtualMachine">vim.VirtualMachine:101</obj><propSet><name>name</name><val>ENG-ENT-APP01</val></propSet><propSet><name>guest.hostName</name><val>eng-ent-app01.contoso.local</val></propSet><propSet><name>guest.ipAddress</name><val>10.42.8.21</val></propSet><propSet><name>guest.guestFullName</name><val>Microsoft Windows Server 2022</val></propSet><propSet><name>runtime.powerState</name><val>poweredOn</val></propSet></objects></returnval></RetrievePropertiesExResponse>`);
}
return soapResponse('');
};
return () => { globalThis.fetch = originalFetch; };
}
test('normalizeBaseUrl trims trailing slashes', () => {
assert.equal(normalizeBaseUrl('https://vcenter.lab.local///'), 'https://vcenter.lab.local');
});
test('standalone VMware host connections use the Web Services profile', async () => {
const settings = settingsFromVCenterConnection({
const restoreFetch = installStandaloneEsxiSoapStub();
const connection = {
id: 'vc_host',
name: 'ESXi 01',
target_type: 'host',
base_url: 'https://esxi01.lab.local/sdk',
username: 'root',
password: 'secret',
enabled: 1,
api_mode: 'api'
});
};
const settings = settingsFromVCenterConnection(connection);
assert.equal(settings.targetType, 'host');
assert.equal(settings.apiMode, 'web-services');
assert.equal(settings.baseUrl, 'https://esxi01.lab.local/sdk');
await assert.rejects(
() => previewVCenterHosts({ connection: { ...settings, name: 'ESXi host' } }),
/Standalone VMware host connections/
const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'ENG-ENT' } });
restoreFetch();
assert.equal(preview.count, 1);
assert.equal(preview.diagnostics.standaloneHost, true);
assert.equal(preview.candidates[0].source, 'standalone-vm');
assert.equal(preview.candidates[0].fqdn, 'eng-ent-app01.contoso.local');
assert.equal(preview.candidates[0].ipAddress, '10.42.8.21');
assert.equal(preview.candidates[0].powerState, 'POWERED_ON');
});
test('standalone VMware VM candidates build managed host payloads', async () => {
const restoreFetch = installStandaloneEsxiSoapStub();
const connection = {
id: 'vc_host',
name: 'ESXi 01',
target_type: 'host',
base_url: 'https://esxi01.lab.local',
username: 'root',
password: 'secret',
enabled: 1
};
const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'eng-ent' } });
const payload = buildHostPayloadFromVm(preview.candidates[0], {
connectionId: 'vc_host',
credentialId: 'cred_1',
transport: 'winrm',
tags: ['lab'],
visibility: 'shared'
});
const power = await getVCenterVmPowerState(connection, preview.candidates[0].id);
restoreFetch();
assert.equal(payload.name, 'ENG-ENT-APP01');
assert.equal(payload.address, 'eng-ent-app01.contoso.local');
assert.equal(payload.transport, 'winrm');
assert.equal(payload.sourceType, 'vmware');
assert.equal(payload.sourceConnectionId, 'vc_host');
assert.equal(payload.sourceRef, 'vim.VirtualMachine:101');
assert.ok(payload.tags.includes('standalone-esxi'));
assert.ok(payload.tags.includes('esxi-vm:vim.VirtualMachine:101'));
assert.equal(power.checked, true);
assert.equal(power.state, 'POWERED_ON');
});
test('standalone VMware host candidates build managed host payloads', () => {
const candidate = buildStandaloneHostCandidate(
{ id: 'vc_host', name: 'ESXi 01' },
{ baseUrl: 'https://esxi01.lab.local', targetType: 'host' }
);
const payload = buildHostPayloadFromVm(candidate, {
connectionId: 'vc_host',
credentialId: 'cred_1',
transport: 'ssh',
tags: ['lab'],
visibility: 'shared'
});
assert.equal(payload.name, 'ESXi 01');
assert.equal(payload.address, 'esxi01.lab.local');
assert.equal(payload.transport, 'ssh');
assert.equal(payload.credentialId, 'cred_1');
assert.equal(payload.sourceType, 'vmware');
assert.equal(payload.sourceConnectionId, 'vc_host');
assert.equal(payload.sourceRef, 'standalone-esxi-host');
assert.ok(payload.tags.includes('standalone-esxi'));
assert.ok(payload.tags.includes('lab'));
});
test('normalizeVCenterVm maps guest identity and networking into a host candidate', () => {