import { describe, expect, test } from 'bun:test'; import type { ModuleManifest } from '../manifest/schema'; import { buildCapabilityData, promptForSecretValue } from './registration'; describe('Capability Registration', () => { describe('buildCapabilityData', () => { test('should return capability data unchanged', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { server: { port: 443, protocol: 'https', }, }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({ server: { port: 443, protocol: 'https', }, }); }); test('should preserve $self: variables (not resolved)', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { server: { ip: '$self:target_ip', port: 443, }, }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); // Variables are preserved, not resolved expect(result).toEqual({ server: { ip: '$self:target_ip', port: 443, }, }); }); test('should preserve nested $self: variables', () => { const capability = { name: 'dns_external', version: '1.0.0', data: { server: { ip: { primary: '$self:vps_ip', }, port: 53, }, zone: { primary_domain: '$self:primary_domain', }, }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'dns-external', name: 'DNS External', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({ server: { ip: { primary: '$self:vps_ip', }, port: 53, }, zone: { primary_domain: '$self:primary_domain', }, }); }); test('should preserve multiple variables in same object', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { url: '$self:base_url', api_version: '$self:api_version', timeout: 30, }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({ url: '$self:base_url', api_version: '$self:api_version', timeout: 30, }); }); test('should preserve variables in arrays', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { servers: ['$self:primary_server', '$self:backup_server'], }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({ servers: ['$self:primary_server', '$self:backup_server'], }); }); test('should preserve all string types including variables', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { variable: '$self:target_ip', literal: 'not-a-variable', with_dollar: '$100', empty: '', }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({ variable: '$self:target_ip', literal: 'not-a-variable', with_dollar: '$100', empty: '', }); }); test('should handle empty data object', () => { const capability = { name: 'test_capability', version: '1.0.0', data: {}, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({}); }); test('should preserve deeply nested structures', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { level1: { level2: { level3: { value: '$self:deep_value', }, }, }, }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({ level1: { level2: { level3: { value: '$self:deep_value', }, }, }, }); }); test('should preserve number and boolean types', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { port: 443, enabled: true, timeout: 0, disabled: false, }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({ port: 443, enabled: true, timeout: 0, disabled: false, }); }); test('should handle null and undefined values', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { nullable: null, optional: undefined, present: 'value', }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); expect(result).toEqual({ nullable: null, optional: undefined, present: 'value', }); }); test('should return cloned data (not mutate original)', () => { const capability = { name: 'test_capability', version: '1.0.0', data: { server: { ip: '$self:target_ip', }, }, }; const manifest: ModuleManifest = { celilo_contract: '1.0', id: 'test-module', name: 'Test Module', version: '1.0.0', description: 'Test', requires: { capabilities: [] }, provides: { capabilities: [] }, variables: { owns: [], imports: [] }, }; const result = buildCapabilityData(capability, manifest); // Modify result to test deep copy // biome-ignore lint/suspicious/noExplicitAny: intentionally mutating result to verify deep copy behavior (result as any).server.ip = 'modified'; // Original should be unchanged expect(capability.data.server.ip).toBe('$self:target_ip'); }); }); describe('promptForSecretValue', () => { test('should generate base64-encoded secret when auto-generate is true', async () => { const result = await promptForSecretValue('tsig', 'TSIG secret for DNS', true); // Should be base64-encoded 32-byte value expect(result).toMatch(/^[A-Za-z0-9+/]+=*$/); expect(result.length).toBeGreaterThan(40); // base64 of 32 bytes is ~44 chars }); test('should generate different secrets on each call', async () => { const secret1 = await promptForSecretValue('secret1', 'Test secret', true); const secret2 = await promptForSecretValue('secret2', 'Test secret', true); expect(secret1).not.toBe(secret2); }); test('should generate secrets of consistent length', async () => { const results = await Promise.all([ promptForSecretValue('s1', 'Test', true), promptForSecretValue('s2', 'Test', true), promptForSecretValue('s3', 'Test', true), ]); const lengths = results.map((r) => r.length); expect(lengths[0]).toBe(lengths[1]); expect(lengths[1]).toBe(lengths[2]); }); }); });