@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
191 lines • 9.62 kB
JavaScript
// 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