UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

191 lines 9.62 kB
// SPDX-License-Identifier: Apache-2.0 import { expect } from 'chai'; import { describe, it } from 'mocha'; import each from 'mocha-each'; import { Flags as flags } from '../../../src/commands/flags.js'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { NamespaceName } from '../../../src/types/namespace/namespace-name.js'; import yaml from 'yaml'; import * as helpers from '../../../src/core/helpers.js'; import { helmValuesHelper } from '../../../src/core/helm-values-helper.js'; import { ConsensusNode } from '../../../src/core/model/consensus-node.js'; function makeConsensusNode(name, nodeId) { return new ConsensusNode(name, nodeId, 'solo', 'cluster', 'ctx', 'cluster.local', 'network-{0}-svc', 'network-node1-svc.solo.svc.cluster.local', [], []); } function generateAndParse(nodes, options) { const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'test-gen-env-')); try { const filePath = helmValuesHelper.generateExtraEnvironmentValuesFile(nodes, options, temporaryDirectory); const content = fs.readFileSync(filePath, 'utf8'); return yaml.parse(content); } finally { fs.rmSync(temporaryDirectory, { recursive: true, force: true }); } } describe('Helpers', () => { each([ { input: '', output: [] }, { input: 'node1', output: ['node1'] }, { input: 'node1,node3', output: ['node1', 'node3'] }, ]).it('should parse node aliases for input', ({ input, output }) => { expect(helpers.parseNodeAliases(input)).to.deep.equal(output); }); each([ { input: [], output: [] }, { input: [1, 2, 3], output: [1, 2, 3] }, { input: ['a', '2', '3'], output: ['a', '2', '3'] }, ]).it('should clone array for input', ({ input, output }) => { const clonedArray = helpers.cloneArray(input); expect(clonedArray).to.deep.equal(output); expect(clonedArray).not.to.equal(input); // ensure cloning creates a new array }); it('Should parse argv to args with boolean flag correctly', () => { const argv = { [flags.quiet.name]: true }; const result = flags.stringifyArgv(argv); expect(result).to.equal(`--${flags.quiet.name}`); }); it('Should parse argv to args with flag correctly', () => { const argv = { [flags.namespace.name]: 'VALUE' }; const result = flags.stringifyArgv(argv); expect(result).to.equal(`--${flags.namespace.name} VALUE`); }); it('Should ipv4ToByteArray convert IPv4 address to string', () => { const ipV4Address = '192.168.0.1'; const byteString = helpers.ipV4ToBase64(ipV4Address); expect(byteString).to.equal('wKgAAQ=='); }); describe('generateExtraEnvironmentValuesFile', () => { it('should sanitize -Xms/-Xmx from JAVA_OPTS coming from baseExtraEnvironmentVariables', () => { const node = makeConsensusNode('node1', 0); const result = generateAndParse([node], { baseExtraEnvironmentVariables: { node1: [{ name: 'JAVA_OPTS', value: '-Xms256m -Xmx2g -Dfoo=bar' }], }, }); const javaOptions = result.hedera.nodes[0].root?.extraEnv.find((environmentEntry) => environmentEntry.name === 'JAVA_OPTS')?.value; expect(javaOptions).to.equal('-Dfoo=bar'); }); it('should sanitize -Xms/-Xmx from JAVA_OPTS after debug-node prepend adds base value with heap flags', () => { const node = makeConsensusNode('node1', 0); const result = generateAndParse([node], { debugNodeAlias: 'node1', baseExtraEnvironmentVariables: { node1: [{ name: 'JAVA_OPTS', value: '-Xms512m -Xmx4g -Dfoo=bar' }], }, }); const javaOptions = result.hedera.nodes[0].root?.extraEnv.find((environmentEntry) => environmentEntry.name === 'JAVA_OPTS')?.value; // debug jdwp prefix should be present, heap flags should be gone expect(javaOptions).to.include('-agentlib:jdwp='); expect(javaOptions).to.not.include('-Xms'); expect(javaOptions).to.not.include('-Xmx'); expect(javaOptions).to.include('-Dfoo=bar'); }); it('should sanitize -Xms/-Xmx from JAVA_OPTS coming from additionalEnvironmentVariables', () => { const node = makeConsensusNode('node1', 0); const result = generateAndParse([node], { additionalEnvironmentVariables: { node1: [{ name: 'JAVA_OPTS', value: '-Xms128m -Xmx1g -Dbaz=qux' }], }, }); const javaOptions = result.hedera.nodes[0].root?.extraEnv.find((environmentEntry) => environmentEntry.name === 'JAVA_OPTS')?.value; expect(javaOptions).to.equal('-Dbaz=qux'); }); it('should preserve blockNodesJson from additionalNodeValues in the output structure', () => { const node = makeConsensusNode('node1', 0); const blockNodesJsonContent = JSON.stringify({ blockNodes: [{ host: 'localhost', port: 8080 }] }); const result = generateAndParse([node], { additionalNodeValues: { node1: { name: 'node1', nodeId: 0, accountId: '0.0.3', blockNodesJson: blockNodesJsonContent }, }, }); expect(result.hedera.nodes[0].blockNodesJson).to.equal(blockNodesJsonContent); }); it('should not include blockNodesJson in output when not provided', () => { const node = makeConsensusNode('node1', 0); const result = generateAndParse([node], { additionalNodeValues: { node1: { name: 'node1', nodeId: 0, accountId: '0.0.3' }, }, }); expect(result.hedera.nodes[0].blockNodesJson).to.be.undefined; }); }); describe('extractPerNodeBlockNodesJsonFromValuesFile', () => { it('should extract blockNodesJson per node from a YAML values file', () => { const node = makeConsensusNode('node1', 0); const blockNodesJsonContent = JSON.stringify({ nodes: [{ host: 'block-node', port: 8080 }] }); const valuesContent = [ 'hedera:', ' nodes:', ' - name: node1', ' nodeId: 0', ` blockNodesJson: '${blockNodesJsonContent}'`, ].join('\n'); const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'test-helpers-')); const temporaryFile = path.join(temporaryDirectory, 'values.yaml'); fs.writeFileSync(temporaryFile, valuesContent, 'utf8'); try { const result = helmValuesHelper.extractPerNodeBlockNodesJsonFromValuesFile(temporaryFile, [node]); expect(result['node1']).to.equal(blockNodesJsonContent); } finally { fs.rmSync(temporaryDirectory, { recursive: true, force: true }); } }); it('should return empty record when file does not exist', () => { const node = makeConsensusNode('node1', 0); const result = helmValuesHelper.extractPerNodeBlockNodesJsonFromValuesFile('/nonexistent/file.yaml', [node]); expect(result).to.deep.equal({}); }); it('should return empty record when hedera.nodes is absent from the file', () => { const node = makeConsensusNode('node1', 0); const valuesContent = 'hedera:\n configMaps:\n configTxt: "foo"\n'; const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'test-helpers-')); const temporaryFile = path.join(temporaryDirectory, 'values.yaml'); fs.writeFileSync(temporaryFile, valuesContent, 'utf8'); try { const result = helmValuesHelper.extractPerNodeBlockNodesJsonFromValuesFile(temporaryFile, [node]); expect(result).to.deep.equal({}); } finally { fs.rmSync(temporaryDirectory, { recursive: true, force: true }); } }); }); describe('remoteConfigsToDeploymentsTable', () => { it('should support clusters as an object map', () => { const remoteConfigs = [ { namespace: NamespaceName.of('default'), name: 'remote-config', data: { 'remote-config-data': yaml.stringify({ clusters: { clusterA: { deployment: 'deployment-a' }, }, }), }, }, ]; const rows = helpers.remoteConfigsToDeploymentsTable(remoteConfigs); expect(rows).to.deep.equal(['Namespace : deployment', 'default : deployment-a']); }); it('should return header only when clusters is missing', () => { const remoteConfigs = [ { namespace: NamespaceName.of('default'), name: 'remote-config', data: { 'remote-config-data': yaml.stringify({}), }, }, ]; const rows = helpers.remoteConfigsToDeploymentsTable(remoteConfigs); expect(rows).to.deep.equal(['Namespace : deployment']); }); }); }); //# sourceMappingURL=helpers.test.js.map