import { describe, expect, test } from 'bun:test'; import type { Machine } from '@/types/infrastructure'; import { type ProxmoxProviderConfig, extractMachineProperties, extractProxmoxProperties, extractTerraformProperties, } from './property-extractor'; // Test fixture for Proxmox provider config const mockProxmoxConfig: ProxmoxProviderConfig = { default_target_node: 'node2', lxc_template: 'local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst', storage: 'local-lvm', }; describe('extractMachineProperties', () => { test('extracts properties from machine record', () => { const machine: Machine = { id: 'machine-123', hostname: 'dns-ext', ipAddress: '203.0.113.42', sshUser: 'root', sshKeyEncrypted: 'encrypted-key', hardware: { cpu_cores: 2, memory_mb: 2048, disk_gb: 20 }, zone: 'external', role: 'host', interfaces: [], createdAt: new Date(), updatedAt: new Date(), }; const properties = extractMachineProperties(machine); expect(properties).toEqual({ 'ip.primary': '203.0.113.42', hostname: 'dns-ext', id: 'machine-123', }); }); test('translates from camelCase to dot notation', () => { const machine: Machine = { id: 'machine-456', hostname: 'test-host', ipAddress: '192.168.1.100', // camelCase in code sshUser: 'ubuntu', sshKeyEncrypted: 'encrypted', hardware: { cpu_cores: 1, memory_mb: 1024, disk_gb: 10 }, zone: 'internal', role: 'host', interfaces: [], createdAt: new Date(), updatedAt: new Date(), }; const properties = extractMachineProperties(machine); // Verify camelCase → dot notation translation expect(properties['ip.primary']).toBe('192.168.1.100'); expect(properties.ipAddress).toBeUndefined(); // Should not have camelCase key }); }); describe('extractProxmoxProperties', () => { test('extracts properties from IPAM allocation', () => { const properties = extractProxmoxProperties(100, '10.0.10.5', 'homebridge', mockProxmoxConfig); expect(properties).toEqual({ 'ip.primary': '10.0.10.5', hostname: 'homebridge', id: '100', target_node: 'node2', lxc_template: 'local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst', storage: 'local-lvm', }); }); test('omits vm_template when the service config has none (LXC services)', () => { const properties = extractProxmoxProperties(100, '10.0.10.5', 'caddy', mockProxmoxConfig); // Omitted, not empty — so a required-infrastructure `vm_template` var fails // loudly rather than resolving to "". expect('vm_template' in properties).toBe(false); }); test('includes vm_template when the service config provides one (VM services)', () => { const properties = extractProxmoxProperties(100, '10.0.10.5', 'builder', { ...mockProxmoxConfig, vm_template: 'ubuntu-2204-cloudinit', }); expect(properties.vm_template).toBe('ubuntu-2204-cloudinit'); }); test('converts vmid number to string', () => { const properties = extractProxmoxProperties(12345, '10.0.20.15', 'caddy', mockProxmoxConfig); expect(properties.id).toBe('12345'); expect(typeof properties.id).toBe('string'); }); test('uses container IP as primary IP', () => { const properties = extractProxmoxProperties(100, '10.0.10.5', 'test', mockProxmoxConfig); // Container IP (from IPAM) becomes the primary IP expect(properties['ip.primary']).toBe('10.0.10.5'); }); test('includes provider config properties', () => { const properties = extractProxmoxProperties(100, '10.0.10.5', 'test', mockProxmoxConfig); expect(properties.target_node).toBe('node2'); expect(properties.lxc_template).toBe( 'local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst', ); expect(properties.storage).toBe('local-lvm'); }); }); describe('extractTerraformProperties', () => { test('extracts properties from unwrapped Terraform output', () => { const terraformOutputs = { droplet_ip: '203.0.113.42', droplet_id: '123456789', }; const properties = extractTerraformProperties(terraformOutputs, 'dns-external'); expect(properties).toEqual({ 'ip.primary': '203.0.113.42', hostname: 'dns-external', id: '123456789', }); }); test('extracts properties from wrapped Terraform output format', () => { // Terraform output -json format wraps values in objects const terraformOutputs = { droplet_ip: { value: '198.51.100.50', type: 'string', sensitive: false, }, droplet_id: { value: '987654321', type: 'string', sensitive: false, }, }; const properties = extractTerraformProperties(terraformOutputs, 'web-server'); expect(properties).toEqual({ 'ip.primary': '198.51.100.50', hostname: 'web-server', id: '987654321', }); }); test('handles numeric droplet ID', () => { const terraformOutputs = { droplet_ip: '203.0.113.42', droplet_id: { value: 123456789, // Number instead of string type: 'number', sensitive: false, }, }; const properties = extractTerraformProperties(terraformOutputs, 'test'); expect(properties.id).toBe('123456789'); expect(typeof properties.id).toBe('string'); }); test('throws on missing droplet_ip', () => { const terraformOutputs = { droplet_id: '123456789', // droplet_ip missing }; expect(() => extractTerraformProperties(terraformOutputs, 'test')).toThrow( 'Missing required Terraform outputs', ); }); test('throws on missing droplet_id', () => { const terraformOutputs = { droplet_ip: '203.0.113.42', // droplet_id missing }; expect(() => extractTerraformProperties(terraformOutputs, 'test')).toThrow( 'Missing required Terraform outputs', ); }); test('throws on invalid output format', () => { const terraformOutputs = { droplet_ip: { invalid: 'format' }, // Missing 'value' key droplet_id: '123456789', }; expect(() => extractTerraformProperties(terraformOutputs, 'test')).toThrow( 'Invalid Terraform output format', ); }); });