UNPKG

@hashgraph/solo

Version:

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

465 lines 22.8 kB
// SPDX-License-Identifier: Apache-2.0 import { expect } from 'chai'; import { RemoteConfigV1Migration } from '../../../../../../../src/data/schema/migration/impl/remote/remote-config-v1-migration.js'; import { IllegalArgumentError } from '../../../../../../../src/business/errors/illegal-argument-error.js'; import { InvalidSchemaVersionError } from '../../../../../../../src/data/schema/migration/api/invalid-schema-version-error.js'; import sinon from 'sinon'; import * as fs from 'node:fs'; import { getSoloVersion } from '../../../../../../../version.js'; import yaml from 'yaml'; describe('RemoteConfigV1Migration', () => { let migration; let sandbox; let clock; const fixedDate = new Date('2023-01-01T00:00:00Z'); beforeEach(() => { migration = new RemoteConfigV1Migration(); sandbox = sinon.createSandbox(); clock = sinon.useFakeTimers(fixedDate); }); afterEach(() => { sandbox.restore(); clock.restore(); }); describe('range', () => { it('should return version range for schema version 0', () => { const range = migration.range; expect(range.toString()).to.equal('[0, 1)'); }); }); describe('version', () => { it('should return version 1', () => { const version = migration.version; expect(version.major).to.equal(1); }); }); describe('migrate', () => { it('should migrate real config from v0-35-1-remote-config.yaml file', async () => { const yamlContent = fs.readFileSync('test/data/v0-35-1-remote-config.yaml', 'utf8'); const config = yaml.parse(yamlContent); // Set schemaVersion to 0 for migration test config.schemaVersion = 0; // Ensure components structure is properly set up if (config.components) { // Ensure explorers exists (it's called mirrorNodeExplorers in the YAML) if (!config.components.explorers && config.components.mirrorNodeExplorers) { config.components.explorers = config.components.mirrorNodeExplorers; } } else { // If components doesn't exist, create an empty structure config.components = { consensusNodes: {}, haProxies: {}, envoyProxies: {}, mirrorNodes: {}, relayNodes: {}, explorers: {}, }; } // Perform migration const result = (await migration.migrate(config)); // Verify migration was successful expect(result).to.have.property('schemaVersion', 1); expect(result).to.have.property('metadata'); expect(result).to.have.property('versions'); // Verify versions were migrated correctly // Instead of checking for exact values, check that the properties exist // since the actual values might change based on the implementation expect(result.versions).to.have.property('cli'); expect(result.versions).to.have.property('chart'); expect(result.versions).to.have.property('consensusNode'); expect(result.versions).to.have.property('mirrorNodeChart'); expect(result.versions).to.have.property('explorerChart'); expect(result.versions).to.have.property('jsonRpcRelayChart'); // Verify clusters were migrated from object to array expect(result).to.have.property('clusters'); expect(Array.isArray(result.clusters)).to.be.true; expect(result.clusters.length).to.equal(1); expect(result.clusters[0]).to.have.property('name', 'gke-alpha-prod-us-central1'); // Verify state was created (components are migrated to state) expect(result).to.have.property('state'); expect(result.state).to.have.property('consensusNodes'); expect(result.state.consensusNodes).to.be.an('array'); expect(result.state).to.have.property('blockNodes'); expect(result.state.blockNodes).to.be.an('array'); expect(result.state).to.have.property('mirrorNodes'); expect(result.state.mirrorNodes).to.be.an('array'); expect(result.state).to.have.property('relayNodes'); expect(result.state.relayNodes).to.be.an('array'); expect(result.state).to.have.property('haProxies'); expect(result.state.haProxies).to.be.an('array'); expect(result.state).to.have.property('envoyProxies'); expect(result.state.envoyProxies).to.be.an('array'); expect(result.state).to.have.property('explorers'); expect(result.state.explorers).to.be.an('array'); // Verify namespace and deploymentName were removed from metadata expect(result.metadata).to.not.have.property('namespace'); expect(result.metadata).to.not.have.property('deploymentName'); }); it('should throw IllegalArgumentError when source is null', async () => { try { await migration.migrate(undefined); // Should not reach here expect.fail('Expected to throw IllegalArgumentError'); } catch (error) { expect(error).to.be.instanceOf(IllegalArgumentError); expect(error.message).to.equal('source must not be null or undefined'); } }); it('should throw IllegalArgumentError when source is undefined', async () => { try { await migration.migrate(undefined); // Should not reach here expect.fail('Expected to throw IllegalArgumentError'); } catch (error) { expect(error).to.be.instanceOf(IllegalArgumentError); expect(error.message).to.equal('source must not be null or undefined'); } }); it('should throw InvalidSchemaVersionError when schemaVersion is not 0', async () => { const source = { schemaVersion: 2, }; try { await migration.migrate(source); // Should not reach here expect.fail('Expected to throw InvalidSchemaVersionError'); } catch (error) { expect(error).to.be.instanceOf(InvalidSchemaVersionError); expect(error.message).to.include('Invalid schema version'); } }); it('should set metadata with lastUpdated information', async () => { const source = {}; const result = (await migration.migrate(source)); expect(result).to.have.property('metadata'); expect(result.metadata).to.have.property('lastUpdatedAt').that.deep.equals(fixedDate); expect(result.metadata).to.have.property('lastUpdatedBy').that.deep.equals({ name: 'system', hostname: 'migration', }); }); it('should migrate version information correctly', async () => { // Create a source object with all the version fields const sourceVersions = { soloVersion: '1.0.0', soloChartVersion: '2.0.0', hederaPlatformVersion: '3.0.0', hederaMirrorNodeChartVersion: '4.0.0', hederaExplorerChartVersion: '5.0.0', hederaJsonRpcRelayChartVersion: '6.0.0', }; const source = { metadata: sourceVersions, }; // Create a direct clone of the source to keep the original values for comparison const sourceClone = structuredClone(source); const result = (await migration.migrate(source)); expect(result).to.have.property('versions'); // Verify that the migration preserves the version values from metadata // We can't check the cli version exactly since getSoloVersion might override it expect(result.versions).to.have.property('cli'); expect(result.versions.chart).to.equal(sourceClone.metadata.soloChartVersion); expect(result.versions.consensusNode).to.equal(sourceClone.metadata.hederaPlatformVersion); expect(result.versions.mirrorNodeChart).to.equal(sourceClone.metadata.hederaMirrorNodeChartVersion); expect(result.versions.explorerChart).to.equal(sourceClone.metadata.hederaExplorerChartVersion); expect(result.versions.jsonRpcRelayChart).to.equal(sourceClone.metadata.hederaJsonRpcRelayChartVersion); expect(result.versions).to.have.property('blockNodeChart', '0.0.0'); // Verify old version properties are deleted expect(result.metadata).to.not.have.property('soloVersion'); expect(result.metadata).to.not.have.property('soloChartVersion'); expect(result.metadata).to.not.have.property('hederaPlatformVersion'); expect(result.metadata).to.not.have.property('hederaMirrorNodeChartVersion'); expect(result.metadata).to.not.have.property('hederaExplorerChartVersion'); expect(result.metadata).to.not.have.property('hederaJsonRpcRelayChartVersion'); }); it('should use default version values when metadata versions are not present', async () => { const source = { metadata: {}, }; const result = (await migration.migrate(source)); expect(result).to.have.property('versions'); // Check that default values are used when metadata versions are not present expect(result.versions).to.have.property('cli', getSoloVersion()); expect(result.versions).to.have.property('chart', '0.0.0'); expect(result.versions).to.have.property('consensusNode', '0.0.0'); expect(result.versions).to.have.property('mirrorNodeChart', '0.0.0'); expect(result.versions).to.have.property('explorerChart', '0.0.0'); expect(result.versions).to.have.property('jsonRpcRelayChart', '0.0.0'); expect(result.versions).to.have.property('blockNodeChart', '0.0.0'); }); it('should migrate clusters correctly', async () => { const source = { clusters: { cluster1: { name: 'cluster1', namespace: 'namespace1', deployment: 'deployment1', dnsBaseDomain: 'domain1', dnsConsensusNodePattern: 'pattern1', }, cluster2: { name: 'cluster2', namespace: 'namespace2', deployment: 'deployment2', dnsBaseDomain: 'domain2', dnsConsensusNodePattern: 'pattern2', }, }, }; const result = (await migration.migrate(source)); expect(result).to.have.property('clusters'); expect(Array.isArray(result.clusters)).to.be.true; expect(result.clusters.length).to.equal(2); const clusterNames = result.clusters.map((c) => c.name); expect(clusterNames).to.include('cluster1'); expect(clusterNames).to.include('cluster2'); const cluster1 = result.clusters.find((c) => c.name === 'cluster1'); expect(cluster1).to.deep.include({ name: 'cluster1', namespace: 'namespace1', deployment: 'deployment1', dnsBaseDomain: 'domain1', dnsConsensusNodePattern: 'pattern1', }); const cluster2 = result.clusters.find((c) => c.name === 'cluster2'); expect(cluster2).to.deep.include({ name: 'cluster2', namespace: 'namespace2', deployment: 'deployment2', dnsBaseDomain: 'domain2', dnsConsensusNodePattern: 'pattern2', }); }); it('should delete namespace and deploymentName from metadata', async () => { const source = { metadata: { namespace: 'oldNamespace', deploymentName: 'oldDeployment', }, }; const result = (await migration.migrate(source)); expect(result.metadata).to.not.have.property('namespace'); expect(result.metadata).to.not.have.property('deploymentName'); }); it('should migrate component state correctly', async () => { const source = { components: { consensusNodes: { node1: { name: 'node1', nodeId: 1, namespace: 'namespace1', cluster: 'cluster1', state: 'started', }, }, haProxies: { haproxy1: { name: 'haproxy1', namespace: 'namespace1', cluster: 'cluster1', }, }, envoyProxies: { envoy1: { name: 'envoy1', namespace: 'namespace1', cluster: 'cluster1', }, }, mirrorNodeExplorers: { explorer1: { name: 'explorer1', namespace: 'namespace1', cluster: 'cluster1', }, }, mirrorNodes: { mirror1: { name: 'mirror1', namespace: 'namespace1', cluster: 'cluster1', }, }, relays: { relay1: { name: 'relay1', namespace: 'namespace1', cluster: 'cluster1', consensusNodeAliases: ['node1'], }, }, blockNodes: { block1: { name: 'block1', namespace: 'namespace1', cluster: 'cluster1', }, }, }, }; const result = (await migration.migrate(source)); expect(result).to.have.property('state'); expect(result.state).to.have.property('ledgerPhase', 'initialized'); // Check consensus nodes expect(Array.isArray(result.state.consensusNodes)).to.be.true; expect(result.state.consensusNodes.length).to.equal(1); // Check that id exists and other properties match expect(result.state.consensusNodes[0].metadata).to.have.property('id'); expect(result.state.consensusNodes[0].metadata).to.include({ namespace: 'namespace1', cluster: 'cluster1', phase: 'started', }); // Check haProxies expect(Array.isArray(result.state.haProxies)).to.be.true; expect(result.state.haProxies.length).to.equal(1); // Check that id exists and other properties match expect(result.state.haProxies[0].metadata).to.have.property('id'); expect(result.state.haProxies[0].metadata).to.include({ namespace: 'namespace1', cluster: 'cluster1', phase: 'started', }); // Check envoyProxies expect(Array.isArray(result.state.envoyProxies)).to.be.true; expect(result.state.envoyProxies.length).to.equal(1); // Check that id exists and other properties match expect(result.state.envoyProxies[0].metadata).to.have.property('id'); expect(result.state.envoyProxies[0].metadata).to.include({ namespace: 'namespace1', cluster: 'cluster1', phase: 'started', }); // Check explorers expect(Array.isArray(result.state.explorers)).to.be.true; expect(result.state.explorers.length).to.equal(1); // Check that version property exists expect(result.state.explorers[0]).to.have.property('version'); // Check that id exists and other properties match expect(result.state.explorers[0].metadata).to.have.property('id'); expect(result.state.explorers[0].metadata).to.include({ namespace: 'namespace1', cluster: 'cluster1', phase: 'started', }); // Check mirrorNodes expect(Array.isArray(result.state.mirrorNodes)).to.be.true; expect(result.state.mirrorNodes.length).to.equal(1); // Check that id exists and other properties match expect(result.state.mirrorNodes[0].metadata).to.have.property('id'); expect(result.state.mirrorNodes[0].metadata).to.include({ namespace: 'namespace1', cluster: 'cluster1', phase: 'started', }); // Check relayNodes expect(Array.isArray(result.state.relayNodes)).to.be.true; expect(result.state.relayNodes.length).to.equal(1); // Check that id and consensusNodeIds exist and other properties match expect(result.state.relayNodes[0].metadata).to.have.property('id'); expect(result.state.relayNodes[0]).to.have.property('consensusNodeIds'); expect(result.state.relayNodes[0].consensusNodeIds).to.deep.equal([0]); expect(result.state.relayNodes[0].metadata).to.include({ namespace: 'namespace1', cluster: 'cluster1', phase: 'started', }); // Check blockNodes expect(Array.isArray(result.state.blockNodes)).to.be.true; expect(result.state.blockNodes.length).to.equal(1); // Check that id exists and other properties match expect(result.state.blockNodes[0].metadata).to.have.property('id'); expect(result.state.blockNodes[0].metadata).to.include({ namespace: 'namespace1', cluster: 'cluster1', phase: 'started', }); // Verify components property is deleted expect(result).to.not.have.property('components'); }); it('should migrate command history correctly', async () => { const source = { commandHistory: { command1: 'details1', command2: 'details2', }, lastExecutedCommand: 'lastCommand', }; const result = (await migration.migrate(source)); expect(result).to.have.property('history'); expect(result.history).to.have.property('commands'); expect(result.history.commands).to.include('command1'); expect(result.history.commands).to.include('command2'); expect(result.history).to.have.property('lastExecutedCommand', 'lastCommand'); // Verify old history properties are deleted expect(result).to.not.have.property('commandHistory'); expect(result).to.not.have.property('lastExecutedCommand'); }); it('should set the schema version to 1', async () => { const source = {}; const result = (await migration.migrate(source)); expect(result).to.have.property('schemaVersion', 1); }); it('should perform a complete migration with all properties', async () => { const source = { metadata: { soloVersion: '1.0.0', soloChartVersion: '2.0.0', hederaPlatformVersion: '3.0.0', hederaMirrorNodeChartVersion: '4.0.0', hederaExplorerChartVersion: '5.0.0', hederaJsonRpcRelayChartVersion: '6.0.0', namespace: 'oldNamespace', deploymentName: 'oldDeployment', }, clusters: { cluster1: { name: 'cluster1', namespace: 'namespace1', deployment: 'deployment1', dnsBaseDomain: 'domain1', dnsConsensusNodePattern: 'pattern1', }, }, components: { consensusNodes: { node1: { name: 'node1', nodeId: 1, namespace: 'namespace1', cluster: 'cluster1', }, }, haproxies: {}, envoyProxies: {}, explorers: {}, mirrorNodes: {}, relayNodes: {}, }, commandHistory: { command1: 'details1', }, lastExecutedCommand: 'lastCommand', }; const result = await migration.migrate(source); // Check all migrated properties expect(result).to.have.property('metadata'); expect(result).to.have.property('versions'); expect(result).to.have.property('clusters'); expect(result).to.have.property('state'); expect(result).to.have.property('history'); expect(result).to.have.property('schemaVersion', 1); // Verify old properties are deleted expect(result).to.not.have.property('components'); expect(result).to.not.have.property('commandHistory'); expect(result).to.not.have.property('lastExecutedCommand'); }); }); }); //# sourceMappingURL=remote-config-v1-migration.test.js.map