import * as fs from 'fs'; import * as crypto from 'crypto'; import * as os from 'os'; import * as path from 'path'; import { tap, expect } from '@git.zone/tstest/tapbundle'; import { BaseImageManager, VMConfig, SocketClient, NetworkManager, MicroVM, SmartVM, SmartVMError, } from '../ts/index.js'; import type { IBaseImageBundle, IBaseImageHostedManifest, IMicroVMConfig } from '../ts/index.js'; async function getRejectedError(promise: Promise): Promise { try { await promise; } catch (err) { return err; } return undefined; } function sha256Buffer(buffer: Buffer): string { return crypto.createHash('sha256').update(buffer).digest('hex'); } // ============================================================ // VMConfig Tests // ============================================================ const sampleConfig: IMicroVMConfig = { id: 'test-vm-1', bootSource: { kernelImagePath: '/path/to/vmlinux', bootArgs: 'console=ttyS0 reboot=k panic=1', }, machineConfig: { vcpuCount: 2, memSizeMib: 256, smt: false, }, drives: [ { driveId: 'rootfs', pathOnHost: '/path/to/rootfs.ext4', isRootDevice: true, isReadOnly: false, cacheType: 'Unsafe', }, ], networkInterfaces: [ { ifaceId: 'eth0', hostDevName: 'tap0', guestMac: '02:00:00:00:00:01', }, ], }; tap.test('VMConfig - validate() should pass for valid config', async () => { const vmConfig = new VMConfig(sampleConfig); const result = vmConfig.validate(); expect(result.valid).toBeTrue(); expect(result.errors).toHaveLength(0); }); tap.test('VMConfig - validate() should fail for missing bootSource', async () => { const vmConfig = new VMConfig({ machineConfig: { vcpuCount: 1, memSizeMib: 128 }, } as any); const result = vmConfig.validate(); expect(result.valid).toBeFalse(); expect(result.errors.length).toBeGreaterThan(0); }); tap.test('VMConfig - validate() should fail for invalid vcpuCount', async () => { const vmConfig = new VMConfig({ bootSource: { kernelImagePath: '/vmlinux' }, machineConfig: { vcpuCount: 0, memSizeMib: 128 }, }); const result = vmConfig.validate(); expect(result.valid).toBeFalse(); }); tap.test('VMConfig - validate() should fail for vcpuCount > 32', async () => { const vmConfig = new VMConfig({ bootSource: { kernelImagePath: '/vmlinux' }, machineConfig: { vcpuCount: 64, memSizeMib: 128 }, }); const result = vmConfig.validate(); expect(result.valid).toBeFalse(); }); tap.test('VMConfig - validate() should fail for multiple root drives', async () => { const vmConfig = new VMConfig({ bootSource: { kernelImagePath: '/vmlinux' }, machineConfig: { vcpuCount: 1, memSizeMib: 128 }, drives: [ { driveId: 'rootfs1', pathOnHost: '/rootfs1', isRootDevice: true }, { driveId: 'rootfs2', pathOnHost: '/rootfs2', isRootDevice: true }, ], }); const result = vmConfig.validate(); expect(result.valid).toBeFalse(); }); tap.test('VMConfig - validate() should fail for invalid vsock guestCid', async () => { const vmConfig = new VMConfig({ bootSource: { kernelImagePath: '/vmlinux' }, machineConfig: { vcpuCount: 1, memSizeMib: 128 }, vsock: { guestCid: 2, udsPath: '/tmp/vsock.sock' }, }); const result = vmConfig.validate(); expect(result.valid).toBeFalse(); expect(result.errors).toContain('vsock.guestCid must be >= 3'); }); tap.test('VMConfig - constructor should not retain caller references', async () => { const config: IMicroVMConfig = { ...sampleConfig, networkInterfaces: [{ ifaceId: 'eth0', guestMac: '02:00:00:00:00:01' }], mmds: { version: 'V2', networkInterfaces: ['eth0'] }, }; const vmConfig = new VMConfig(config); config.networkInterfaces![0].guestMac = '02:00:00:00:00:02'; config.mmds!.networkInterfaces.push('eth1'); expect(vmConfig.toNetworkInterfacePayload(vmConfig.config.networkInterfaces![0]).guest_mac) .toEqual('02:00:00:00:00:01'); expect(vmConfig.toMmdsConfigPayload()!.network_interfaces).toEqual(['eth0']); }); tap.test('VMConfig - toBootSourcePayload() should generate correct snake_case', async () => { const vmConfig = new VMConfig(sampleConfig); const payload = vmConfig.toBootSourcePayload(); expect(payload.kernel_image_path).toEqual('/path/to/vmlinux'); expect(payload.boot_args).toEqual('console=ttyS0 reboot=k panic=1'); expect(payload.initrd_path).toBeUndefined(); }); tap.test('VMConfig - toMachineConfigPayload() should generate correct payload', async () => { const vmConfig = new VMConfig(sampleConfig); const payload = vmConfig.toMachineConfigPayload(); expect(payload.vcpu_count).toEqual(2); expect(payload.mem_size_mib).toEqual(256); expect(payload.smt).toEqual(false); }); tap.test('VMConfig - toDrivePayload() should generate correct payload', async () => { const vmConfig = new VMConfig(sampleConfig); const payload = vmConfig.toDrivePayload(sampleConfig.drives![0]); expect(payload.drive_id).toEqual('rootfs'); expect(payload.path_on_host).toEqual('/path/to/rootfs.ext4'); expect(payload.is_root_device).toEqual(true); expect(payload.is_read_only).toEqual(false); expect(payload.cache_type).toEqual('Unsafe'); }); tap.test('VMConfig - toNetworkInterfacePayload() should generate correct payload', async () => { const vmConfig = new VMConfig(sampleConfig); const payload = vmConfig.toNetworkInterfacePayload(sampleConfig.networkInterfaces![0]); expect(payload.iface_id).toEqual('eth0'); expect(payload.host_dev_name).toEqual('tap0'); expect(payload.guest_mac).toEqual('02:00:00:00:00:01'); }); tap.test('VMConfig - toVsockPayload() should return null when no vsock configured', async () => { const vmConfig = new VMConfig(sampleConfig); expect(vmConfig.toVsockPayload()).toBeNull(); }); tap.test('VMConfig - toBalloonPayload() should return null when no balloon configured', async () => { const vmConfig = new VMConfig(sampleConfig); expect(vmConfig.toBalloonPayload()).toBeNull(); }); tap.test('VMConfig - toBalloonPayload() should generate correct payload', async () => { const config: IMicroVMConfig = { ...sampleConfig, balloon: { amountMib: 64, deflateOnOom: true, statsPollingIntervalS: 5 }, }; const vmConfig = new VMConfig(config); const payload = vmConfig.toBalloonPayload(); expect(payload).not.toBeNull(); expect(payload!.amount_mib).toEqual(64); expect(payload!.deflate_on_oom).toEqual(true); expect(payload!.stats_polling_interval_s).toEqual(5); }); tap.test('VMConfig - toVsockPayload() should generate correct payload', async () => { const config: IMicroVMConfig = { ...sampleConfig, vsock: { guestCid: 3, udsPath: '/tmp/vsock.sock' }, }; const vmConfig = new VMConfig(config); const payload = vmConfig.toVsockPayload(); expect(payload).not.toBeNull(); expect(payload!.guest_cid).toEqual(3); expect(payload!.uds_path).toEqual('/tmp/vsock.sock'); }); tap.test('VMConfig - toMmdsConfigPayload() should generate correct payload', async () => { const config: IMicroVMConfig = { ...sampleConfig, mmds: { version: 'V2', networkInterfaces: ['eth0'] }, }; const vmConfig = new VMConfig(config); const payload = vmConfig.toMmdsConfigPayload(); expect(payload).not.toBeNull(); expect(payload!.version).toEqual('V2'); expect(payload!.network_interfaces).toEqual(['eth0']); }); tap.test('VMConfig - toMetricsPayload() should generate correct payload', async () => { const config: IMicroVMConfig = { ...sampleConfig, metrics: { metricsPath: '/tmp/firecracker-metrics.fifo' }, }; const vmConfig = new VMConfig(config); const payload = vmConfig.toMetricsPayload(); expect(payload).not.toBeNull(); expect(payload!.metrics_path).toEqual('/tmp/firecracker-metrics.fifo'); }); tap.test('VMConfig - toDrivePayload() should include rate limiter payloads', async () => { const vmConfig = new VMConfig(sampleConfig); const payload = vmConfig.toDrivePayload({ driveId: 'data', pathOnHost: '/path/to/data.ext4', isRootDevice: false, rateLimiter: { bandwidth: { size: 1000, refillTime: 2000, oneTimeBurst: 3000 }, ops: { size: 10, refillTime: 20, oneTimeBurst: 30 }, }, }); expect(payload.rate_limiter.bandwidth).toEqual({ size: 1000, refill_time: 2000, one_time_burst: 3000, }); expect(payload.rate_limiter.ops).toEqual({ size: 10, refill_time: 20, one_time_burst: 30, }); }); tap.test('VMConfig - toLoggerPayload() should generate correct payload', async () => { const config: IMicroVMConfig = { ...sampleConfig, logger: { logPath: '/tmp/fc.log', level: 'Debug', showLogOrigin: true }, }; const vmConfig = new VMConfig(config); const payload = vmConfig.toLoggerPayload(); expect(payload).not.toBeNull(); expect(payload!.log_path).toEqual('/tmp/fc.log'); expect(payload!.level).toEqual('Debug'); expect(payload!.show_log_origin).toEqual(true); }); // ============================================================ // SocketClient Tests // ============================================================ tap.test('SocketClient - URL construction', async () => { const client = new SocketClient({ socketPath: '/tmp/test.sock' }); // We can't directly access buildUrl, but we can verify the client instantiates expect(client).toBeTruthy(); }); // ============================================================ // BaseImageManager Tests // ============================================================ tap.test('BaseImageManager - instantiation with defaults', async () => { const manager = new BaseImageManager(); expect(manager.getCacheDir()).toEqual(path.join(os.tmpdir(), '.smartvm', 'base-images')); expect(manager.getMaxStoredBaseImages()).toEqual(2); }); tap.test('BaseImageManager - rejects invalid maxStoredBaseImages', async () => { let error: unknown; try { new BaseImageManager({ maxStoredBaseImages: 0 }); } catch (err) { error = err; } expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('INVALID_BASE_IMAGE_CACHE_LIMIT'); }); tap.test('BaseImageManager - pruneBaseImageCache() should evict old bundles', async () => { const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-base-image-test-')); const manager = new BaseImageManager({ cacheDir, maxStoredBaseImages: 2 }); const originalWarn = console.warn; const warnings: string[] = []; console.warn = (message?: any) => { warnings.push(String(message)); }; const createManifest = async (bundleId: string, lastAccessedAt: string) => { const bundleDir = path.join(cacheDir, bundleId); await fs.promises.mkdir(bundleDir, { recursive: true }); const bundle: IBaseImageBundle = { preset: 'lts', arch: 'x86_64', ciVersion: 'v1.7', firecrackerVersion: 'v1.7.0', bundleId, bundleDir, kernelImagePath: path.join(bundleDir, 'vmlinux'), rootfsPath: path.join(bundleDir, 'rootfs.ext4'), rootfsType: 'ext4', rootfsIsReadOnly: false, bootArgs: 'console=ttyS0 reboot=k panic=1 pci=off', source: { bucketUrl: 'https://s3.amazonaws.com/spec.ccfc.min', kernelKey: 'kernel', rootfsKey: 'rootfs', }, createdAt: lastAccessedAt, lastAccessedAt, }; await fs.promises.writeFile(path.join(bundleDir, 'manifest.json'), `${JSON.stringify(bundle, null, 2)}\n`); }; try { await createManifest('old', '2024-01-01T00:00:00.000Z'); await createManifest('middle', '2024-01-02T00:00:00.000Z'); await createManifest('new', '2024-01-03T00:00:00.000Z'); const evicted = await manager.pruneBaseImageCache('new'); expect(evicted).toEqual(['old']); expect(warnings.length).toEqual(1); expect(warnings[0]).toInclude('Evicting old'); expect(fs.existsSync(path.join(cacheDir, 'old'))).toBeFalse(); expect(fs.existsSync(path.join(cacheDir, 'middle'))).toBeTrue(); expect(fs.existsSync(path.join(cacheDir, 'new'))).toBeTrue(); } finally { console.warn = originalWarn; await fs.promises.rm(cacheDir, { recursive: true, force: true }); } }); tap.test('BaseImageManager - ensureBaseImage() should copy hosted manifest artifacts', async () => { const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-test-')); const cacheDir = path.join(workDir, 'cache'); const assetsDir = path.join(workDir, 'assets'); await fs.promises.mkdir(assetsDir, { recursive: true }); const kernelBuffer = Buffer.from('fake-kernel'); const rootfsBuffer = Buffer.from('fake-rootfs'); const kernelPath = path.join(assetsDir, 'vmlinux-test'); const rootfsPath = path.join(assetsDir, 'rootfs-test.ext4'); await fs.promises.writeFile(kernelPath, kernelBuffer); await fs.promises.writeFile(rootfsPath, rootfsBuffer); const manifest: IBaseImageHostedManifest = { schemaVersion: 1, bundleId: 'smartvm-minimal-test', name: 'SmartVM minimal test bundle', arch: 'x86_64', firecrackerVersion: 'v1.15.1', rootfsType: 'ext4', rootfsIsReadOnly: false, bootArgs: 'console=ttyS0 reboot=k panic=1 pci=off', kernel: { path: kernelPath, fileName: 'vmlinux', sha256: sha256Buffer(kernelBuffer), sizeBytes: kernelBuffer.length, }, rootfs: { path: rootfsPath, fileName: 'rootfs.ext4', sha256: sha256Buffer(rootfsBuffer), sizeBytes: rootfsBuffer.length, }, }; const manifestPath = path.join(workDir, 'manifest.json'); await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); try { const manager = new BaseImageManager({ cacheDir }); const bundle = await manager.ensureBaseImage({ manifestPath }); expect(bundle.preset).toEqual('hosted'); expect(bundle.bundleId).toEqual('smartvm-minimal-test'); expect(bundle.firecrackerVersion).toEqual('v1.15.1'); expect(bundle.source.type).toEqual('hosted-manifest'); expect(bundle.source.manifestPath).toEqual(manifestPath); expect(fs.existsSync(bundle.kernelImagePath)).toBeTrue(); expect(fs.existsSync(bundle.rootfsPath)).toBeTrue(); expect(bundle.checksums!.kernelSha256).toEqual(sha256Buffer(kernelBuffer)); expect(bundle.checksums!.rootfsSha256).toEqual(sha256Buffer(rootfsBuffer)); } finally { await fs.promises.rm(workDir, { recursive: true, force: true }); } }); tap.test('BaseImageManager - ensureBaseImage() should redownload corrupted cached artifacts', async () => { const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-cache-test-')); const cacheDir = path.join(workDir, 'cache'); const assetsDir = path.join(workDir, 'assets'); await fs.promises.mkdir(assetsDir, { recursive: true }); const kernelBuffer = Buffer.from('fresh-kernel'); const rootfsBuffer = Buffer.from('fresh-rootfs'); const kernelPath = path.join(assetsDir, 'vmlinux-test'); const rootfsPath = path.join(assetsDir, 'rootfs-test.ext4'); await fs.promises.writeFile(kernelPath, kernelBuffer); await fs.promises.writeFile(rootfsPath, rootfsBuffer); const manifest: IBaseImageHostedManifest = { schemaVersion: 1, bundleId: 'smartvm-corruption-test', arch: 'x86_64', firecrackerVersion: 'v1.15.1', rootfsType: 'ext4', kernel: { path: kernelPath, fileName: 'vmlinux', sha256: sha256Buffer(kernelBuffer), sizeBytes: kernelBuffer.length, }, rootfs: { path: rootfsPath, fileName: 'rootfs.ext4', sha256: sha256Buffer(rootfsBuffer), sizeBytes: rootfsBuffer.length, }, }; const manifestPath = path.join(workDir, 'manifest.json'); await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); try { const manager = new BaseImageManager({ cacheDir }); const firstBundle = await manager.ensureBaseImage({ manifestPath }); await fs.promises.writeFile(firstBundle.kernelImagePath, 'tampered-kernel'); const secondBundle = await manager.ensureBaseImage({ manifestPath }); expect(await fs.promises.readFile(secondBundle.kernelImagePath, 'utf8')).toEqual('fresh-kernel'); expect(secondBundle.checksums!.kernelSha256).toEqual(sha256Buffer(kernelBuffer)); } finally { await fs.promises.rm(workDir, { recursive: true, force: true }); } }); tap.test('BaseImageManager - ensureBaseImage() should reject hosted manifest arch mismatch', async () => { const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-invalid-test-')); const manifestPath = path.join(workDir, 'manifest.json'); const manifest: IBaseImageHostedManifest = { schemaVersion: 1, bundleId: 'smartvm-invalid-arch-test', arch: 'aarch64', firecrackerVersion: 'v1.15.1', rootfsType: 'ext4', kernel: { path: path.join(workDir, 'vmlinux') }, rootfs: { path: path.join(workDir, 'rootfs.ext4') }, }; await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); try { const manager = new BaseImageManager({ cacheDir: path.join(workDir, 'cache'), arch: 'x86_64' }); const error = await getRejectedError(manager.ensureBaseImage({ manifestPath })); expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('INVALID_BASE_IMAGE_MANIFEST'); } finally { await fs.promises.rm(workDir, { recursive: true, force: true }); } }); tap.test('BaseImageManager - ensureBaseImage() should reject hosted manifest fileName traversal', async () => { const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-filename-test-')); const manifestPath = path.join(workDir, 'manifest.json'); const manifest: IBaseImageHostedManifest = { schemaVersion: 1, bundleId: 'smartvm-invalid-filename-test', arch: 'x86_64', firecrackerVersion: 'v1.15.1', rootfsType: 'ext4', kernel: { path: path.join(workDir, 'vmlinux'), fileName: '../vmlinux' }, rootfs: { path: path.join(workDir, 'rootfs.ext4'), fileName: 'rootfs.ext4' }, }; await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); try { const manager = new BaseImageManager({ cacheDir: path.join(workDir, 'cache') }); const error = await getRejectedError(manager.ensureBaseImage({ manifestPath })); expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('INVALID_BASE_IMAGE_MANIFEST'); } finally { await fs.promises.rm(workDir, { recursive: true, force: true }); } }); tap.test('BaseImageManager - ensureBaseImage() should reject hosted URL artifacts without sha256', async () => { const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-url-test-')); const manifestPath = path.join(workDir, 'manifest.json'); const manifest: IBaseImageHostedManifest = { schemaVersion: 1, bundleId: 'smartvm-invalid-url-test', arch: 'x86_64', firecrackerVersion: 'v1.15.1', rootfsType: 'ext4', kernel: { url: 'https://example.com/vmlinux' }, rootfs: { path: path.join(workDir, 'rootfs.ext4') }, }; await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); try { const manager = new BaseImageManager({ cacheDir: path.join(workDir, 'cache') }); const error = await getRejectedError(manager.ensureBaseImage({ manifestPath })); expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('INVALID_BASE_IMAGE_MANIFEST'); } finally { await fs.promises.rm(workDir, { recursive: true, force: true }); } }); tap.test('BaseImageManager - hosted preset should require a manifest', async () => { const workDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smartvm-hosted-image-missing-test-')); try { const manager = new BaseImageManager({ cacheDir: path.join(workDir, 'cache') }); const error = await getRejectedError(manager.ensureBaseImage({ preset: 'hosted' })); expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('BASE_IMAGE_MANIFEST_FAILED'); } finally { await fs.promises.rm(workDir, { recursive: true, force: true }); } }); // ============================================================ // NetworkManager Tests // ============================================================ tap.test('NetworkManager - allocateIp() should allocate sequential IPs', async () => { const nm = new NetworkManager({ subnet: '172.30.0.0/24' }); const ip1 = nm.allocateIp(); const ip2 = nm.allocateIp(); const ip3 = nm.allocateIp(); expect(ip1).toEqual('172.30.0.2'); expect(ip2).toEqual('172.30.0.3'); expect(ip3).toEqual('172.30.0.4'); }); tap.test('NetworkManager - allocateIp() should normalize non-network CIDR input', async () => { const nm = new NetworkManager({ subnet: '10.20.30.17/29' }); expect(nm.allocateIp()).toEqual('10.20.30.18'); expect(nm.allocateIp()).toEqual('10.20.30.19'); }); tap.test('NetworkManager - allocateIp() should fail when subnet is exhausted', async () => { const nm = new NetworkManager({ subnet: '192.168.100.0/30' }); expect(nm.allocateIp()).toEqual('192.168.100.2'); let error: unknown; try { nm.allocateIp(); } catch (err) { error = err; } expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('IP_EXHAUSTED'); }); tap.test('NetworkManager - constructor should reject invalid subnet input', async () => { let error: unknown; try { new NetworkManager({ subnet: '10.0.0.0/31' }); } catch (err) { error = err; } expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('INVALID_SUBNET'); }); tap.test('NetworkManager - constructor should reject malformed IPv4 octets', async () => { for (const subnet of ['10..0.1/24', '10.0x10.0.1/24', '10.0.0.1 /24']) { let error: unknown; try { new NetworkManager({ subnet }); } catch (err) { error = err; } expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('INVALID_SUBNET'); } }); tap.test('NetworkManager - constructor should reject invalid bridge names', async () => { let error: unknown; try { new NetworkManager({ bridgeName: 'bad bridge' }); } catch (err) { error = err; } expect(error).toBeInstanceOf(SmartVMError); expect((error as SmartVMError).code).toEqual('INVALID_INTERFACE_NAME'); }); tap.test('NetworkManager - generateMac() should generate locally-administered MACs', async () => { const nm = new NetworkManager(); const mac1 = nm.generateMac('vm1', 'eth0'); const mac2 = nm.generateMac('vm2', 'eth0'); const mac3 = nm.generateMac('vm1', 'eth0'); // Should start with 02: (locally administered) expect(mac1.startsWith('02:')).toBeTrue(); expect(mac2.startsWith('02:')).toBeTrue(); // Same inputs should produce same MAC (deterministic) expect(mac1).toEqual(mac3); // Different inputs should produce different MACs expect(mac1).not.toEqual(mac2); // Should be valid MAC format (6 hex pairs separated by colons) expect(mac1).toMatch(/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/); }); tap.test('NetworkManager - generateTapName() should respect IFNAMSIZ limit', async () => { const nm = new NetworkManager(); const name1 = nm.generateTapName('abcdefgh-1234-5678', 'eth0'); const name2 = nm.generateTapName('short', 'eth0'); // Should not exceed 15 characters expect(name1.length).toBeLessThanOrEqual(15); expect(name2.length).toBeLessThanOrEqual(15); // Should start with 'sv' prefix expect(name1.startsWith('sv')).toBeTrue(); expect(name2.startsWith('sv')).toBeTrue(); }); tap.test('NetworkManager - getGuestNetworkBootArgs() should format correctly', async () => { const nm = new NetworkManager(); const tap = { tapName: 'svtest0eth0', guestIp: '172.30.0.2', gatewayIp: '172.30.0.1', subnetMask: '255.255.255.0', mac: '02:00:00:00:00:01', }; const bootArgs = nm.getGuestNetworkBootArgs(tap); expect(bootArgs).toEqual('ip=172.30.0.2::172.30.0.1:255.255.255.0::eth0:off'); }); // ============================================================ // MicroVM Tests // ============================================================ tap.test('MicroVM - invalid lifecycle calls should throw SmartVMError', async () => { const vm = new MicroVM( 'lifecycle-vm', sampleConfig, '/bin/false', '/tmp/smartvm-lifecycle.sock', new NetworkManager(), ); const pauseError = await getRejectedError(vm.pause()); expect(pauseError).toBeInstanceOf(SmartVMError); expect((pauseError as SmartVMError).code).toEqual('INVALID_STATE'); const infoError = await getRejectedError(vm.getInfo()); expect(infoError).toBeInstanceOf(SmartVMError); expect((infoError as SmartVMError).code).toEqual('NO_CLIENT'); }); // ============================================================ // SmartVM Tests // ============================================================ tap.test('SmartVM - instantiation with defaults', async () => { const smartvm = new SmartVM(); expect(smartvm).toBeTruthy(); expect(smartvm.imageManager).toBeTruthy(); expect(smartvm.baseImageManager).toBeTruthy(); expect(smartvm.networkManager).toBeTruthy(); expect(smartvm.vmCount).toEqual(0); expect(smartvm.listVMs()).toHaveLength(0); }); tap.test('SmartVM - instantiation with custom options', async () => { const smartvm = new SmartVM({ dataDir: '/tmp/smartvm-test', arch: 'aarch64', bridgeName: 'testbr0', subnet: '10.0.0.0/24', }); expect(smartvm).toBeTruthy(); }); tap.test('SmartVM - createVM() should track created VMs', async () => { const smartvm = new SmartVM({ dataDir: '/tmp/smartvm-test', firecrackerBinaryPath: '/bin/false', }); const vm = await smartvm.createVM(sampleConfig); expect(smartvm.vmCount).toEqual(1); expect(smartvm.getVM(vm.id)).toEqual(vm); expect(smartvm.listVMs()).toEqual([vm.id]); await smartvm.cleanup(); expect(smartvm.vmCount).toEqual(0); }); export default tap.start();