import test from 'node:test'; import assert from 'node:assert/strict'; import http from 'node:http'; import { load, makeUser } from './setup.mjs'; const { buildVmListPath, buildHostPayloadFromVm, buildStandaloneHostCandidate, createCredential, createVCenterConnection, filterSummaryVms, getVCenterConnection, getVCenterVmPowerState, normalizeBaseUrl, normalizeVCenterVm, previewVCenterHosts, settingsFromVCenterConnection, vmMatchesFilter } = { ...(await load('../services/vcenterService.js')), ...(await load('../models/credentialModel.js')), ...(await load('../models/vcenterConnectionModel.js')) }; const originalFetch = globalThis.fetch; function soapResponse(body, init = {}) { return new Response(`${body}`, { status: 200, headers: { 'content-type': 'text/xml', ...(init.headers || {}) } }); } function installStandaloneEsxiSoapStub({ sdk404 = false } = {}) { const calls = []; globalThis.fetch = async (url, options = {}) => { calls.push({ url: String(url), method: options.method || 'GET', body: String(options.body || ''), headers: options.headers || {} }); const body = String(options.body || ''); if (options.method === 'GET') return new Response('vimService', { status: 200, headers: { 'content-type': 'text/xml' } }); if (sdk404 && String(url).endsWith('/sdk')) return new Response('not found', { status: 404, statusText: 'Not Found', headers: { 'content-type': 'text/plain' } }); if (body.includes('ha-folder-rootha-property-collectorViewManagerha-sessionmgr`); } if (body.includes('session-1`, { headers: { 'set-cookie': 'vmware_soap_session=session-1; Path=/sdk; Secure' } }); } if (body.includes('session[52b6]-vm-view`); } if (body.includes('vim.VirtualMachine:101nameENG-ENT-APP01guest.hostNameeng-ent-app01.contoso.localguest.ipAddress10.42.8.21guest.guestFullNameMicrosoft Windows Server 2022runtime.powerStatepoweredOn`); } return soapResponse(''); }; return { calls, restore: () => { globalThis.fetch = originalFetch; } }; } function startStandaloneEsxiSoapServer() { const requests = []; const server = http.createServer((req, res) => { let body = ''; req.on('data', (chunk) => { body += chunk; }); req.on('end', () => { requests.push({ url: req.url, method: req.method, body, cookie: req.headers.cookie || '' }); res.setHeader('content-type', 'text/xml'); if (req.method === 'GET') { res.end('vimService'); } else if (body.includes('session-http`); } else if (body.includes('ha-folder-rootha-property-collectorViewManagerha-sessionmgr`); } else if (body.includes('session-http-vm-view`); } else if (body.includes('vim.VirtualMachine:201nameHTTP-ESXI-VM01guest.hostNamehttp-esxi-vm01.lab.localguest.ipAddress10.42.9.21guest.guestFullNameMicrosoft Windows Server 2022runtime.powerStatepoweredOn`); } else { res.end(``); } }); }); return new Promise((resolve) => { server.listen(0, '127.0.0.1', () => { resolve({ baseUrl: `http://127.0.0.1:${server.address().port}`, requests, close: () => new Promise((done) => server.close(done)) }); }); }); } test('normalizeBaseUrl trims trailing slashes', () => { assert.equal(normalizeBaseUrl('https://vcenter.lab.local///'), 'https://vcenter.lab.local'); }); test('VMware connections resolve API credentials from Credential Vault', () => { const owner = makeUser({ email: 'vmware-vault@test.local' }); const credential = createCredential({ name: 'vCenter API', kind: 'username_password', username: 'administrator@vsphere.local', secret: 'vault-secret', visibility: 'personal' }, owner); const connection = createVCenterConnection({ name: 'Vault Backed vCenter', targetType: 'vcenter', baseUrl: 'https://vcenter.lab.local', credentialId: credential.id, apiMode: 'auto', requestTimeoutMs: 20000, allowUntrustedTls: true, enabled: true, visibility: 'personal' }, owner); const secretConnection = getVCenterConnection(connection.id, owner, true); assert.equal(connection.credentialId, credential.id); assert.equal(connection.credentialName, 'vCenter API'); assert.equal(connection.username, 'administrator@vsphere.local'); assert.equal(secretConnection.username, 'administrator@vsphere.local'); assert.equal(secretConnection.password, 'vault-secret'); }); test('standalone VMware host connections use the Web Services profile', async () => { const { calls, restore } = 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'); const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'ENG-ENT' } }); restore(); assert.equal(preview.count, 1); assert.ok(calls.some((call) => call.url.endsWith('/sdk/vimService.wsdl'))); assert.ok(calls.some((call) => call.headers.Cookie?.includes('vmware_soap_session'))); 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 { restore } = 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); restore(); 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 SOAP retries alternate endpoint paths after /sdk 404', async () => { const { calls, restore } = installStandaloneEsxiSoapStub({ sdk404: true }); const connection = { id: 'vc_host_retry', name: 'ESXi Retry', target_type: 'host', base_url: 'https://esxi01.lab.local/sdk/vimService.wsdl', username: 'root', password: 'secret', enabled: 1 }; const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'eng-ent' } }); restore(); assert.equal(preview.count, 1); assert.ok(calls.some((call) => call.url === 'https://esxi01.lab.local/sdk')); assert.ok(calls.some((call) => call.url === 'https://esxi01.lab.local/sdk/')); assert.ok(preview.trace.some((entry) => String(entry.message || '').includes('Retrying VMware SOAP action RetrieveServiceContent'))); }); test('standalone VMware custom dispatcher path works when untrusted TLS is enabled', async () => { const server = await startStandaloneEsxiSoapServer(); try { const connection = { id: 'vc_host_dispatcher', name: 'ESXi Dispatcher', target_type: 'host', base_url: server.baseUrl, username: 'root', password: 'secret', enabled: 1, allow_untrusted_tls: 1 }; const preview = await previewVCenterHosts({ connection, filter: { field: 'hostname', operator: 'contains', value: 'HTTP-ESXI' } }); assert.equal(preview.count, 1); assert.equal(preview.candidates[0].name, 'HTTP-ESXI-VM01'); assert.ok(server.requests.some((request) => request.url === '/sdk/vimService.wsdl')); assert.ok(server.requests.some((request) => request.cookie.includes('vmware_soap_session'))); } finally { await server.close(); } }); 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', () => { const candidate = normalizeVCenterVm( { vm: 'vm-42', name: 'ENG-ENT-APP01', power_state: 'POWERED_ON' }, { host_name: 'eng-ent-app01.contoso.local', ip_address: '10.42.1.20', full_name: { default_message: 'Microsoft Windows Server 2022' } }, [{ ip: { ip_addresses: [{ ip_address: 'fe80::1' }, { ip_address: '10.42.1.21' }] } }] ); assert.equal(candidate.id, 'vm-42'); assert.equal(candidate.name, 'ENG-ENT-APP01'); assert.equal(candidate.fqdn, 'eng-ent-app01.contoso.local'); assert.equal(candidate.address, 'eng-ent-app01.contoso.local'); assert.deepEqual(candidate.ipAddresses, ['10.42.1.20', '10.42.1.21']); assert.equal(candidate.osFamily, 'windows'); assert.equal(candidate.guestFullName, 'Microsoft Windows Server 2022'); }); test('normalizeVCenterVm marks unknown guest operating systems as other', () => { const candidate = normalizeVCenterVm( { vm: 'vm-unknown', name: 'MYSTERY-01', power_state: 'POWERED_ON' }, null, [] ); assert.equal(candidate.osFamily, 'other'); }); test('vmMatchesFilter supports hostname contains and IP equals matching', () => { const candidate = { name: 'ENG-ENT-APP01', fqdn: 'app01.contoso.local', address: 'app01.contoso.local', ipAddress: '10.42.1.20', ipAddresses: ['10.42.1.20'] }; assert.equal(vmMatchesFilter(candidate, { field: 'hostname', operator: 'contains', value: 'eng-ent' }), true); assert.equal(vmMatchesFilter(candidate, { field: 'hostname', operator: 'contains', value: 'contoso' }), true); assert.equal(vmMatchesFilter(candidate, { field: 'ip', operator: 'equals', value: '10.42.1.20' }), true); assert.equal(vmMatchesFilter(candidate, { field: 'fqdn', operator: 'startsWith', value: 'db' }), false); }); test('vmMatchesFilter matches guest-only host names after enrichment', () => { const candidate = normalizeVCenterVm( { vm: 'vm-200', name: 'KSUS01PAPP01', power_state: 'POWERED_ON' }, { host_name: 'MCS-WIN-APP01.ent.mitekindustries.com', ip_address: '10.42.5.44', family: 'WINDOWS' }, [] ); assert.equal(vmMatchesFilter(candidate, { field: 'hostname', operator: 'contains', value: 'MCS' }), true); assert.equal(vmMatchesFilter(candidate, { field: 'name', operator: 'contains', value: 'MCS' }), false); }); test('filterSummaryVms scans the full VM summary list for VM-name filters', () => { const vms = [ ...Array.from({ length: 900 }, (_, index) => ({ vm: `vm-${index}`, name: `ZZZ-${index}` })), { vm: 'vm-901', name: 'MCS-WIN-APP01' } ]; const result = filterSummaryVms(vms, { field: 'name', operator: 'contains', value: 'MCS' }); assert.equal(result.summaryFilterApplied, true); assert.equal(result.summaryMatched, 1); assert.equal(result.vms.length, 1); assert.equal(result.vms[0].name, 'MCS-WIN-APP01'); }); test('filterSummaryVms prefilters hostname searches by VM name and falls back when needed', () => { const vms = [{ vm: 'vm-1', name: 'MCS-WIN-APP01' }, { vm: 'vm-2', name: 'SQL-01' }]; const matched = filterSummaryVms(vms, { field: 'hostname', operator: 'contains', value: 'MCS' }); const fallback = filterSummaryVms(vms, { field: 'hostname', operator: 'contains', value: 'guest-only-name' }); assert.equal(matched.summaryMatched, 1); assert.equal(matched.summaryFilterFallback, false); assert.deepEqual(matched.vms.map((vm) => vm.vm), ['vm-1', 'vm-2']); assert.equal(fallback.summaryMatched, 0); assert.equal(fallback.summaryFilterFallback, true); assert.equal(fallback.vms.length, 2); }); test('buildVmListPath uses documented exact-name filters for api and rest profiles', () => { const filter = { field: 'name', operator: 'equals', value: 'MCS-WIN-APP01' }; assert.equal( buildVmListPath({ vmListPath: '/api/vcenter/vm', vmNameParam: 'names' }, filter), '/api/vcenter/vm?names=MCS-WIN-APP01' ); assert.equal( buildVmListPath({ vmListPath: '/rest/vcenter/vm', vmNameParam: 'filter.names' }, filter), '/rest/vcenter/vm?filter.names=MCS-WIN-APP01' ); assert.equal( buildVmListPath({ vmListPath: '/api/vcenter/vm', vmNameParam: 'names' }, { field: 'name', operator: 'contains', value: 'MCS' }), '/api/vcenter/vm' ); }); test('buildHostPayloadFromVm attaches credential, tags, transport, and import notes', () => { const payload = buildHostPayloadFromVm( { id: 'vm-42', name: 'ENG-ENT-APP01', fqdn: 'eng-ent-app01.contoso.local', address: 'eng-ent-app01.contoso.local', osFamily: 'windows', powerState: 'POWERED_ON', guestFullName: 'Microsoft Windows Server 2022', ipAddresses: ['10.42.1.20'] }, { credentialId: 'cred_1', connectionId: 'vc_1', transport: 'winrm', tags: ['engineering'], visibility: 'group', groupId: 'grp_1' } ); assert.equal(payload.credentialId, 'cred_1'); assert.equal(payload.transport, 'winrm'); assert.equal(payload.visibility, 'group'); assert.equal(payload.groupId, 'grp_1'); assert.equal(payload.sourceType, 'vmware'); assert.equal(payload.sourceConnectionId, 'vc_1'); assert.equal(payload.sourceRef, 'vm-42'); assert.ok(payload.tags.includes('vcenter:vm-42')); assert.ok(payload.tags.includes('engineering')); assert.match(payload.notes, /Imported from VMware vCenter VM vm-42/); });