UNPKG

create-datadao

Version:

A CLI tool to generate and deploy DataDAOs on the Vana network

521 lines (392 loc) 18.9 kB
const TemplateEngine = require('../template-engine'); const { createMockFs, createMockTemplateVars } = require('../../__tests__/mocks/factories'); const path = require('path'); // Mock fs-extra const fs = require('fs-extra'); describe('TemplateEngine', () => { let templateEngine; let mockFs; beforeEach(() => { jest.clearAllMocks(); mockFs = createMockFs(); // Apply mocks Object.assign(fs, mockFs); templateEngine = new TemplateEngine('/mock/templates'); }); describe('constructor', () => { test('sets default templates directory', () => { const engine = new TemplateEngine(); expect(engine.templatesDir).toBe(path.join(__dirname, '..', 'templates')); }); test('sets custom templates directory', () => { const engine = new TemplateEngine('/custom/templates'); expect(engine.templatesDir).toBe('/custom/templates'); }); }); describe('processTemplate', () => { test('processes simple template with variables', () => { const templateContent = 'Hello {{name}}, welcome to {{project}}!'; const templatePath = 'greeting.txt'; const variables = { name: 'John', project: 'DataDAO' }; mockFs.__setMockFile('/mock/templates/greeting.txt', templateContent); fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.processTemplate(templatePath, variables); expect(result).toBe('Hello John, welcome to DataDAO!'); expect(fs.readFileSync).toHaveBeenCalledWith('/mock/templates/greeting.txt', 'utf8'); }); test('throws error if template file not found', () => { fs.existsSync.mockReturnValue(false); expect(() => { templateEngine.processTemplate('nonexistent.txt', {}); }).toThrow('Template file not found: /mock/templates/nonexistent.txt'); }); test('handles template with no placeholders', () => { const templateContent = 'This is a static template.'; const templatePath = 'static.txt'; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.processTemplate(templatePath, {}); expect(result).toBe('This is a static template.'); }); test('handles empty template', () => { const templateContent = ''; const templatePath = 'empty.txt'; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.processTemplate(templatePath, {}); expect(result).toBe(''); }); test('processes template with multiple occurrences of same variable', () => { const templateContent = '{{name}} is working on {{name}}\'s project called {{name}}-app'; const templatePath = 'repeated.txt'; const variables = { name: 'Alice' }; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.processTemplate(templatePath, variables); expect(result).toBe('Alice is working on Alice\'s project called Alice-app'); }); test('processes complex template with many variables', () => { const templateContent = ` Project: {{projectName}} Token: {{tokenName}} ({{tokenSymbol}}) Network: {{network}} Address: {{address}} `; const templatePath = 'complex.txt'; const variables = createMockTemplateVars(); fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.processTemplate(templatePath, variables); expect(result).toContain('Project: test-dao'); expect(result).toContain('Token: TestToken (TEST)'); expect(result).toContain('Network: moksha'); expect(result).toContain('Address: 0x1234567890123456789012345678901234567890'); }); }); describe('processTemplateToFile', () => { test('processes template and writes to file', () => { const templateContent = 'Hello {{name}}!'; const templatePath = 'greeting.txt'; const targetPath = '/output/result.txt'; const variables = { name: 'World' }; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(templateContent); templateEngine.processTemplateToFile(templatePath, targetPath, variables); expect(fs.ensureDirSync).toHaveBeenCalledWith('/output'); expect(fs.writeFileSync).toHaveBeenCalledWith(targetPath, 'Hello World!', 'utf8'); }); test('creates target directory if it doesn\'t exist', () => { const templateContent = 'Test content'; const templatePath = 'test.txt'; const targetPath = '/deep/nested/path/result.txt'; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(templateContent); templateEngine.processTemplateToFile(templatePath, targetPath, {}); expect(fs.ensureDirSync).toHaveBeenCalledWith('/deep/nested/path'); }); test('handles template processing errors', () => { fs.existsSync.mockReturnValue(false); expect(() => { templateEngine.processTemplateToFile('nonexistent.txt', '/output/result.txt', {}); }).toThrow('Template file not found'); }); }); describe('replacePlaceholders', () => { test('replaces single placeholder', () => { const content = 'Hello {{name}}!'; const variables = { name: 'Alice' }; const result = templateEngine.replacePlaceholders(content, variables); expect(result).toBe('Hello Alice!'); }); test('replaces multiple placeholders', () => { const content = '{{greeting}} {{name}}, you have {{count}} messages.'; const variables = { greeting: 'Hi', name: 'Bob', count: '5' }; const result = templateEngine.replacePlaceholders(content, variables); expect(result).toBe('Hi Bob, you have 5 messages.'); }); test('handles placeholders with whitespace', () => { const content = 'Value: {{ spaced }}, {{ padded }}'; const variables = { spaced: 'test1', padded: 'test2' }; const result = templateEngine.replacePlaceholders(content, variables); expect(result).toBe('Value: test1, test2'); }); test('warns about missing variables and keeps placeholder', () => { const content = 'Hello {{name}}, your {{status}} is {{unknown}}.'; const variables = { name: 'John', status: 'active' }; const result = templateEngine.replacePlaceholders(content, variables); expect(result).toBe('Hello John, your active is {{unknown}}.'); expect(console.warn).toHaveBeenCalledWith( 'Warning: Variable \'unknown\' not found, keeping placeholder' ); }); test('handles empty variables object', () => { const content = 'No variables here: {{missing}}'; const variables = {}; const result = templateEngine.replacePlaceholders(content, variables); expect(result).toBe('No variables here: {{missing}}'); expect(console.warn).toHaveBeenCalledWith( 'Warning: Variable \'missing\' not found, keeping placeholder' ); }); test('handles variables with special characters', () => { const content = 'Special: {{var-with-dash}}, {{var_with_underscore}}, {{var123}}'; const variables = { 'var-with-dash': 'dash-value', 'var_with_underscore': 'underscore-value', 'var123': 'numeric-value' }; const result = templateEngine.replacePlaceholders(content, variables); expect(result).toBe('Special: dash-value, underscore-value, numeric-value'); }); test('handles numeric and boolean values', () => { const content = 'Count: {{count}}, Price: {{price}}, Active: {{active}}'; const variables = { count: 42, price: 19.99, active: true }; const result = templateEngine.replacePlaceholders(content, variables); expect(result).toBe('Count: 42, Price: 19.99, Active: true'); }); test('handles null and undefined values', () => { const content = 'Null: {{nullValue}}, Undefined: {{undefinedValue}}'; const variables = { nullValue: null, undefinedValue: undefined }; const result = templateEngine.replacePlaceholders(content, variables); expect(result).toBe('Null: null, Undefined: undefined'); }); }); describe('processMultipleTemplates', () => { test('processes multiple templates successfully', () => { const templates = [ { template: 'template1.txt', target: '/output/file1.txt' }, { template: 'template2.txt', target: '/output/file2.txt' } ]; const globalVariables = { name: 'Global' }; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockImplementation((path) => { if (path.includes('template1.txt')) return 'Content 1: {{name}}'; if (path.includes('template2.txt')) return 'Content 2: {{name}}'; return ''; }); const results = templateEngine.processMultipleTemplates(templates, globalVariables); expect(results).toHaveLength(2); expect(results[0]).toEqual({ template: 'template1.txt', target: '/output/file1.txt', success: true }); expect(results[1]).toEqual({ template: 'template2.txt', target: '/output/file2.txt', success: true }); expect(fs.writeFileSync).toHaveBeenCalledWith('/output/file1.txt', 'Content 1: Global', 'utf8'); expect(fs.writeFileSync).toHaveBeenCalledWith('/output/file2.txt', 'Content 2: Global', 'utf8'); }); test('handles template processing errors gracefully', () => { const templates = [ { template: 'good.txt', target: '/output/good.txt' }, { template: 'bad.txt', target: '/output/bad.txt' } ]; fs.existsSync.mockImplementation((path) => !path.includes('bad.txt')); fs.readFileSync.mockImplementation((path) => { if (path.includes('good.txt')) return 'Good content'; throw new Error('File not found'); }); const results = templateEngine.processMultipleTemplates(templates, {}); expect(results).toHaveLength(2); expect(results[0].success).toBe(true); expect(results[1].success).toBe(false); expect(results[1].error).toContain('Template file not found'); }); test('merges global and local variables correctly', () => { const templates = [ { template: 'test.txt', target: '/output/test.txt', variables: { local: 'local-value', shared: 'local-override' } } ]; const globalVariables = { global: 'global-value', shared: 'global-value' }; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue('{{global}}, {{local}}, {{shared}}'); const results = templateEngine.processMultipleTemplates(templates, globalVariables); expect(results[0].success).toBe(true); expect(fs.writeFileSync).toHaveBeenCalledWith( '/output/test.txt', 'global-value, local-value, local-override', 'utf8' ); }); }); describe('getDefaultVanaConfig', () => { test('returns default Vana network configuration', () => { const config = templateEngine.getDefaultVanaConfig(); expect(config).toHaveProperty('DLP_REGISTRY_CONTRACT_ADDRESS'); expect(config).toHaveProperty('DATA_REGISTRY_CONTRACT_ADDRESS'); expect(config).toHaveProperty('TEE_POOL_CONTRACT_ADDRESS'); expect(config).toHaveProperty('DAT_FACTORY_CONTRACT_ADDRESS'); expect(config).toHaveProperty('MOKSHA_RPC_URL', 'https://rpc.moksha.vana.org'); expect(config).toHaveProperty('VANA_RPC_URL', 'http://rpc.vana.org'); // Check that addresses are valid Ethereum addresses expect(config.DLP_REGISTRY_CONTRACT_ADDRESS).toMatch(/^0x[a-fA-F0-9]{40}$/); expect(config.DATA_REGISTRY_CONTRACT_ADDRESS).toMatch(/^0x[a-fA-F0-9]{40}$/); }); test('includes all required network endpoints', () => { const config = templateEngine.getDefaultVanaConfig(); // Vana mainnet expect(config.VANA_RPC_URL).toBe('http://rpc.vana.org'); expect(config.VANA_API_URL).toBe('https://vanascan.io/api'); expect(config.VANA_BROWSER_URL).toBe('https://vanascan.io'); // Moksha testnet expect(config.MOKSHA_RPC_URL).toBe('https://rpc.moksha.vana.org'); expect(config.MOKSHA_API_URL).toBe('https://moksha.vanascan.io/api'); expect(config.MOKSHA_BROWSER_URL).toBe('https://moksha.vanascan.io'); }); }); describe('validateTemplate', () => { test('validates template with all required variables provided', () => { const templateContent = 'Hello {{name}}, your {{role}} is {{status}}.'; const templatePath = 'test.txt'; const variables = { name: 'John', role: 'admin', status: 'active' }; fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.validateTemplate(templatePath, variables); expect(result.valid).toBe(true); expect(result.missing).toEqual([]); expect(result.required).toEqual(['name', 'role', 'status']); }); test('identifies missing variables', () => { const templateContent = 'Hello {{name}}, your {{role}} is {{status}}.'; const templatePath = 'test.txt'; const variables = { name: 'John' }; // missing role and status fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.validateTemplate(templatePath, variables); expect(result.valid).toBe(false); expect(result.missing).toEqual(['role', 'status']); expect(result.required).toEqual(['name', 'role', 'status']); }); test('handles template with no placeholders', () => { const templateContent = 'Static content with no variables.'; const templatePath = 'static.txt'; const variables = {}; fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.validateTemplate(templatePath, variables); expect(result.valid).toBe(true); expect(result.missing).toEqual([]); expect(result.required).toEqual([]); }); test('handles duplicate placeholders correctly', () => { const templateContent = 'Hello {{name}}, {{name}} is your username. Welcome {{name}}!'; const templatePath = 'duplicate.txt'; const variables = { name: 'John' }; fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.validateTemplate(templatePath, variables); expect(result.valid).toBe(true); expect(result.missing).toEqual([]); expect(result.required).toEqual(['name']); // Should not duplicate }); }); describe('extractPlaceholders', () => { test('extracts placeholders from content', () => { const content = 'Hello {{name}}, your {{role}} is {{status}}.'; const placeholders = templateEngine.extractPlaceholders(content); expect(placeholders).toEqual(['name', 'role', 'status']); }); test('handles content with no placeholders', () => { const content = 'No placeholders here.'; const placeholders = templateEngine.extractPlaceholders(content); expect(placeholders).toEqual([]); }); test('handles duplicate placeholders', () => { const content = '{{name}} and {{name}} and {{age}}'; const placeholders = templateEngine.extractPlaceholders(content); expect(placeholders).toEqual(['name', 'age']); }); test('handles placeholders with whitespace', () => { const content = '{{ name }} and {{ age }} and {{role}}'; const placeholders = templateEngine.extractPlaceholders(content); expect(placeholders).toEqual(['name', 'age', 'role']); }); test('handles complex placeholder names', () => { const content = '{{user-name}} {{user_id}} {{var123}} {{CONSTANT}}'; const placeholders = templateEngine.extractPlaceholders(content); expect(placeholders).toEqual(['user-name', 'user_id', 'var123', 'CONSTANT']); }); test('handles malformed placeholders gracefully', () => { const content = '{{valid}} {invalid} {{also-valid}}'; const placeholders = templateEngine.extractPlaceholders(content); expect(placeholders).toEqual(['valid', 'also-valid']); }); }); describe('integration tests', () => { test('full workflow: process multiple templates with validation', () => { const templates = [ { template: 'config.json', target: '/output/config.json' }, { template: 'script.js', target: '/output/script.js' } ]; const configContent = '{"name": "{{projectName}}", "address": "{{address}}"}'; const scriptContent = 'const project = "{{projectName}}"; const addr = "{{address}}";'; const variables = { projectName: 'TestDAO', address: '0x123' }; fs.existsSync.mockReturnValue(true); fs.readFileSync.mockImplementation((path) => { if (path.includes('config.json')) return configContent; if (path.includes('script.js')) return scriptContent; return ''; }); // First validate templates const configValidation = templateEngine.validateTemplate('config.json', variables); const scriptValidation = templateEngine.validateTemplate('script.js', variables); expect(configValidation.valid).toBe(true); expect(scriptValidation.valid).toBe(true); // Then process templates const results = templateEngine.processMultipleTemplates(templates, variables); expect(results.every(r => r.success)).toBe(true); expect(fs.writeFileSync).toHaveBeenCalledWith( '/output/config.json', '{"name": "TestDAO", "address": "0x123"}', 'utf8' ); expect(fs.writeFileSync).toHaveBeenCalledWith( '/output/script.js', 'const project = "TestDAO"; const addr = "0x123";', 'utf8' ); }); test('handles real DataDAO template variables', () => { const templateContent = ` const config = { dlpName: "{{dlpName}}", tokenName: "{{tokenName}}", tokenSymbol: "{{tokenSymbol}}", network: "{{network}}", rpcUrl: "{{rpcUrl}}", chainId: {{chainId}}, contractAddress: "{{address}}" }; `; const variables = createMockTemplateVars({ dlpName: 'MyDataDAO', tokenName: 'MyToken', tokenSymbol: 'MTK', chainId: 14800 }); fs.existsSync.mockReturnValue(true); fs.readFileSync.mockReturnValue(templateContent); const result = templateEngine.processTemplate('config.js', variables); expect(result).toContain('dlpName: "MyDataDAO"'); expect(result).toContain('tokenName: "MyToken"'); expect(result).toContain('tokenSymbol: "MTK"'); expect(result).toContain('chainId: 14800'); expect(result).toContain('network: "moksha"'); }); }); });