import { tap, expect } from '@git.zone/tstest/tapbundle'; import { VMConfig, SocketClient, NetworkManager, SmartVM, } from '../ts/index.js'; import type { IMicroVMConfig } from '../ts/index.js'; // ============================================================ // 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 - 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 - 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(); }); // ============================================================ // 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 - 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'); }); // ============================================================ // SmartVM Tests // ============================================================ tap.test('SmartVM - instantiation with defaults', async () => { const smartvm = new SmartVM(); expect(smartvm).toBeTruthy(); expect(smartvm.imageManager).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(); }); export default tap.start();