UNPKG

@hashgraph/solo

Version:

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

193 lines 9.04 kB
// SPDX-License-Identifier: Apache-2.0 import { expect } from 'chai'; import { before, describe, it, after } from 'mocha'; import { DefaultKindClientBuilder } from '../../../../../src/integration/kind/impl/default-kind-client-builder.js'; import { ClusterCreateOptionsBuilder } from '../../../../../src/integration/kind/model/create-cluster/create-cluster-options-builder.js'; import { KindCluster } from '../../../../../src/integration/kind/model/kind-cluster.js'; import { KindDependencyManager } from '../../../../../src/core/dependency-managers/index.js'; import { container } from 'tsyringe-neo'; import fs from 'node:fs'; import * as os from 'node:os'; import { resetForTest } from '../../../../test-container.js'; import { Duration } from '../../../../../src/core/time/duration.js'; import { exec } from 'node:child_process'; import { promisify } from 'node:util'; import { PathEx } from '../../../../../src/business/utils/path-ex.js'; import * as constants from '../../../../../src/core/constants.js'; import { InjectTokens } from '../../../../../src/core/dependency-injection/inject-tokens.js'; import path from 'node:path'; const execAsync = promisify(exec); describe('KindClient Integration Tests', function () { this.timeout(Duration.ofMinutes(1).toMillis()); let kindClient; let kindPath; const testClusterName = 'test-kind-client'; const temporaryDirectory = fs.mkdtempSync(PathEx.join(os.tmpdir(), 'kind-test-')); let originalKubeConfigContext; before(async () => { resetForTest(); // Save original kubectl context if it exists try { const { stdout } = await execAsync('kubectl config current-context', { env: { ...process.env, PATH: `${constants.SOLO_HOME_DIR}/bin${path.delimiter}${process.env.PATH}` }, }); originalKubeConfigContext = stdout.trim(); console.log(`Saved original kubectl context: ${originalKubeConfigContext}`); } catch { console.log('No kubectl context found or kubectl not available'); originalKubeConfigContext = undefined; } // Download and install Kind container.register(InjectTokens.KindInstallationDirectory, { useValue: temporaryDirectory }); const kindManager = container.resolve(KindDependencyManager); try { await kindManager.install(); } catch (error) { console.error('Error checking if Kind is installed locally:', error); throw error; } kindPath = kindManager.isInstalledLocally() ? PathEx.join(temporaryDirectory, constants.KIND) : await kindManager.getExecutable(); console.log(`Using Kind at: ${kindPath}`); // Create Kind client const clientBuilder = new DefaultKindClientBuilder(); try { kindClient = await clientBuilder.executable(kindPath).build(); } catch (error) { console.error('Error building Kind client:', error); throw error; } }).timeout(Duration.ofMinutes(2).toMillis()); after(async () => { if (kindClient) { try { // Clean up test cluster if it exists const clusters = await kindClient.getClusters(); if (clusters.some((cluster) => cluster.name === testClusterName)) { console.log(`Deleting test cluster: ${testClusterName}`); await kindClient.deleteCluster(testClusterName); } } catch (error) { console.error('Error during cleanup:', error); } } // Restore original kubectl context if it existed if (originalKubeConfigContext) { try { console.log(`Restoring original kubectl context: ${originalKubeConfigContext}`); await execAsync(`kubectl config use-context ${originalKubeConfigContext}`, { env: { ...process.env, PATH: `${constants.SOLO_HOME_DIR}/bin${path.delimiter}${process.env.PATH}` }, }); } catch (error) { console.error('Error restoring kubectl context:', error); } } // Clean up temp directory try { fs.rmSync(temporaryDirectory, { recursive: true, force: true }); } catch (error) { console.error('Error cleaning up temp directory:', error); } }).timeout(Duration.ofMinutes(2).toMillis()); it('should get Kind version', async () => { const version = await kindClient.version(); expect(version).to.not.be.undefined; expect(version.major).to.be.a('number'); expect(version.minor).to.be.a('number'); expect(version.patch).to.be.a('number'); console.log(`Kind version: ${version.toString()}`); }); it('should create a cluster', async () => { // after the Kubernetes upgrade in CI, kind commands sometimes fail initially due to a timeout when creating clusters const maxRetries = 3; let attempt = 0; let lastError; while (attempt < maxRetries) { try { const controller = new AbortController(); const onTimeoutCallback = setTimeout(() => { controller.abort(); }, Duration.ofSeconds(20).toMillis()); console.log(`deleting cluster if it exists before creation attempt ${attempt + 1}`); await kindClient.deleteCluster(testClusterName); const options = ClusterCreateOptionsBuilder.builder().build(); console.log(`creating cluster, attempt ${attempt + 1}`); const response = await kindClient.createCluster(testClusterName, options); expect(response).to.not.be.undefined; expect(response.name).to.equal(testClusterName); clearTimeout(onTimeoutCallback); return; } catch (error) { lastError = error; console.warn(`Attempt ${attempt + 1} to create cluster failed: ${error}`); attempt++; if (attempt < maxRetries) { console.log('Retrying cluster creation...'); } else { console.error('Max retries reached. Failing test.'); throw lastError; } } } }).timeout(Duration.ofMinutes(4).toMillis()); it('should list clusters', async () => { const clusters = await kindClient.getClusters(); expect(clusters).to.be.an('array'); expect(clusters.length).to.be.greaterThan(0); const testCluster = clusters.find((c) => c.name === testClusterName); expect(testCluster).to.not.be.undefined; expect(testCluster).to.be.instanceOf(KindCluster); expect(testCluster.name).to.equal(testClusterName); }); it('should get cluster nodes', async () => { const response = await kindClient.getNodes(testClusterName); expect(response).to.not.be.undefined; expect(response.nodes).to.be.an('array'); expect(response.nodes.length).to.be.greaterThan(0); // Verify node naming pattern (should have the cluster name in it) const nodes = response.nodes; for (const node of nodes) { expect(node).to.include(testClusterName); } }); it('should get kubeconfig', async () => { const response = await kindClient.getKubeConfig(testClusterName); expect(response).to.not.be.undefined; expect(response.config).to.exist; expect(response.config.apiVersion).to.eq('v1'); expect(response.config.clusters).to.exist; expect(response.config.clusters.length).to.be.greaterThan(0); expect(response.config.contexts).to.exist; }); it('should export kubeconfig', async () => { const response = await kindClient.exportKubeConfig(testClusterName); expect(response).to.not.be.undefined; expect(response.kubeConfigContext).to.be.a('string'); }); it('should export logs', async () => { const response = await kindClient.exportLogs(testClusterName); expect(response).to.not.be.undefined; expect(response.exportPath).to.be.a('string'); // Verify logs directory exists const logsExist = fs.existsSync(response.exportPath); expect(logsExist).to.be.true; }); it('should delete a cluster', async () => { const response = await kindClient.deleteCluster(testClusterName); expect(response).to.not.be.undefined; // Verify cluster was deleted const clusters = await kindClient.getClusters(); const deletedCluster = clusters.find((c) => c.name === testClusterName); expect(deletedCluster).to.be.undefined; }); }); //# sourceMappingURL=kind-client.test.js.map