UNPKG

@hashgraph/solo

Version:

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

233 lines 13.9 kB
// SPDX-License-Identifier: Apache-2.0 import { expect } from 'chai'; import { after, before, describe, it } from 'mocha'; import each from 'mocha-each'; import fs from 'node:fs'; import path from 'node:path'; import { KubectlDependencyManager } from '../../../../../src/core/dependency-managers/index.js'; import { getTestCacheDirectory, getTemporaryDirectory } from '../../../../test-utility.js'; import * as version from '../../../../../version.js'; import { PathEx } from '../../../../../src/business/utils/path-ex.js'; import sinon from 'sinon'; import { platform } from 'node:process'; import { OperatingSystem } from '../../../../../src/business/utils/operating-system.js'; import { container } from 'tsyringe-neo'; import { InjectTokens } from '../../../../../src/core/dependency-injection/inject-tokens.js'; import * as constants from '../../../../../src/core/constants.js'; import { resetForTest } from '../../../../test-container.js'; const mockVersionOutputValid = 'Client Version: v1.33.3\nKustomize Version: v5.6.0'; const mockVersionOutputMissingClient = 'Something Else: v1.10.0\nKustomize Version: v5.6.0'; const mockVersionOutputInvalid = 'invalid output'; describe('KubectlDependencyManager', () => { const originalPlatform = platform; const originalInstallationDirectory = container.resolve(InjectTokens.KubectlInstallationDirectory); const temporaryDirectory = PathEx.join(getTemporaryDirectory(), 'bin'); const localInstallationDirectory = temporaryDirectory; let sandbox; before(() => { resetForTest(); fs.mkdirSync(temporaryDirectory); sandbox = sinon.createSandbox(); }); after(() => { if (fs.existsSync(temporaryDirectory)) { fs.rmSync(temporaryDirectory, { recursive: true }); } }); afterEach(() => { resetForTest(); sandbox.restore(); }); it('should return kubectl version', () => { const kubectlDependencyManager = new KubectlDependencyManager(undefined, localInstallationDirectory, undefined, undefined); expect(kubectlDependencyManager.getRequiredVersion()).to.equal(version.KUBECTL_VERSION); }); it('should be able to check when kubectl not installed', () => { const kubectlDependencyManager = new KubectlDependencyManager(undefined, localInstallationDirectory, undefined, undefined); expect(kubectlDependencyManager.isInstalledLocally()).not.to.be.ok; }); it('should be able to check when kubectl is installed', async () => { const kubectlDependencyManager = new KubectlDependencyManager(undefined, localInstallationDirectory, undefined, undefined); // Create the local executable file for testing const localPath = PathEx.join(localInstallationDirectory, constants.KUBECTL); fs.writeFileSync(localPath, ''); expect(kubectlDependencyManager.isInstalledLocally()).to.be.ok; }); describe('when kubectl is installed globally', () => { let kubectlDependencyManager; let runStub; let existsSyncStub; let downloaderFetchPackageSpy; let packageDownloader; let cpSyncStub; let chmodSyncStub; let rmSyncStub; beforeEach(() => { packageDownloader = container.resolve(InjectTokens.PackageDownloader); downloaderFetchPackageSpy = sandbox.spy(packageDownloader, 'fetchPackage'); kubectlDependencyManager = new KubectlDependencyManager(undefined, temporaryDirectory, process.arch, undefined); kubectlDependencyManager.uninstallLocal(); runStub = sandbox.stub(kubectlDependencyManager, 'run'); cpSyncStub = sandbox.stub(fs, 'cpSync').returns(); chmodSyncStub = sandbox.stub(fs, 'chmodSync').returns(); existsSyncStub = sandbox.stub(fs, 'existsSync').returns(true); rmSyncStub = sandbox.stub(fs, 'rmSync').returns(); }); afterEach(() => { container.register(InjectTokens.OsPlatform, { useValue: originalPlatform }); container.register(InjectTokens.KubectlInstallationDirectory, { useValue: originalInstallationDirectory }); sandbox.restore(); resetForTest(); }); it('should prefer the global installation if it meets the requirements', async () => { const fakeGlobalBinDirectory = '/test-solo-global-bin'; const fakeGlobalKubectlPath = `${fakeGlobalBinDirectory}/kubectl`; const originalPath = process.env.PATH ?? ''; process.env.PATH = `${fakeGlobalBinDirectory}${path.delimiter}${originalPath}`; sandbox.stub(fs, 'accessSync').callsFake((filePath) => { if (String(filePath) === fakeGlobalKubectlPath) { return; } throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); }); runStub.withArgs(`"${fakeGlobalKubectlPath}" version --client`).resolves(mockVersionOutputValid.split('\n')); existsSyncStub.withArgs(`${localInstallationDirectory}/kubectl`).returns(false); try { // @ts-expect-error TS2341: Property isInstalledGloballyAndMeetsRequirements is private const result = await kubectlDependencyManager.isInstalledGloballyAndMeetsRequirements(); expect(result).to.be.true; expect(await kubectlDependencyManager.install(getTestCacheDirectory())).to.be.true; // Should return global path since it meets requirements expect(await kubectlDependencyManager.getExecutable()).to.equal(constants.KUBECTL); } finally { process.env.PATH = originalPath; } }); it('should install kubectl locally if the global installation does not meet the requirements', async () => { // Stub accessSync so the native PATH scan finds no global kubectl installation. sandbox.stub(fs, 'accessSync').throws(Object.assign(new Error('ENOENT'), { code: 'ENOENT' })); expect(await kubectlDependencyManager.install(getTestCacheDirectory())).to.be.true; expect(fs.existsSync(PathEx.join(localInstallationDirectory, constants.KUBECTL))).to.be.ok; expect(await kubectlDependencyManager.getExecutable()).to.equal(constants.KUBECTL); }); it('should be able to use local installation on repeated calls without reinstalling given global installation does not meet requirements', async () => { // Restore all beforeEach stubs so real fs/run operations work for the download. runStub.restore(); existsSyncStub.restore(); cpSyncStub.restore(); chmodSyncStub.restore(); rmSyncStub.restore(); // Stub accessSync so the native PATH scan finds no global kubectl installation. sandbox.stub(fs, 'accessSync').throws(Object.assign(new Error('ENOENT'), { code: 'ENOENT' })); const kubectlInstallationDirectory = getTemporaryDirectory(); container.register(InjectTokens.KubectlInstallationDirectory, { useValue: kubectlInstallationDirectory }); kubectlDependencyManager = new KubectlDependencyManager(undefined, undefined, process.arch, undefined); expect(await kubectlDependencyManager.install(getTemporaryDirectory())).to.be.true; expect(downloaderFetchPackageSpy.calledOnce).to.be.true; expect(fs.existsSync(PathEx.join(kubectlInstallationDirectory, constants.KUBECTL))).to.be.ok; // Call install again, should not trigger installation since local installation meets requirements downloaderFetchPackageSpy.resetHistory(); expect(downloaderFetchPackageSpy.notCalled).to.be.true; expect(await kubectlDependencyManager.install(getTemporaryDirectory())).to.be.true; expect(downloaderFetchPackageSpy.notCalled).to.be.true; }); }); describe('Kubectl Installation Tests', () => { afterEach(() => { container.register(InjectTokens.OsPlatform, { useValue: originalPlatform }); sandbox.restore(); }); each([ [OperatingSystem.OS_LINUX, 'x64'], [OperatingSystem.OS_LINUX, 'amd64'], [OperatingSystem.OS_WIN32, 'amd64'], ]).it('should be able to install kubectl base on %s and %s', async (osPlatform, osArch) => { container.register(InjectTokens.OsPlatform, { useValue: osPlatform }); const kubectlDependencyManager = new KubectlDependencyManager(undefined, localInstallationDirectory, osArch, undefined); if (fs.existsSync(temporaryDirectory)) { fs.rmSync(temporaryDirectory, { recursive: true }); } kubectlDependencyManager.uninstallLocal(); expect(kubectlDependencyManager.isInstalledLocally()).not.to.be.ok; // Stub accessSync so the native PATH scan finds no global kubectl installation. sandbox.stub(fs, 'accessSync').throws(Object.assign(new Error('ENOENT'), { code: 'ENOENT' })); expect(await kubectlDependencyManager.install(getTestCacheDirectory())).to.be.true; expect(kubectlDependencyManager.isInstalledLocally()).to.be.ok; fs.rmSync(temporaryDirectory, { recursive: true }); }); }); describe('KubectlDependencyManager system methods', () => { let kubectlDependencyManager; beforeEach(() => { kubectlDependencyManager = new KubectlDependencyManager(undefined, localInstallationDirectory, process.arch, undefined); }); afterEach(() => { container.register(InjectTokens.OsPlatform, { useValue: originalPlatform }); container.register(InjectTokens.KubectlInstallationDirectory, { useValue: originalInstallationDirectory }); sandbox.restore(); resetForTest(); }); it('getVersion should succeed with valid version output', async () => { sandbox.stub(kubectlDependencyManager, 'run').resolves(mockVersionOutputValid.split('\n')); expect(await kubectlDependencyManager.getVersion('/usr/local/bin/kubectl')).to.equal('1.33.3'); }); it('getVersion should handle error if kubectl version fails', async () => { sandbox.stub(kubectlDependencyManager, 'run').rejects(new Error('Command failed')); try { await kubectlDependencyManager.getVersion('/usr/local/bin/kubectl'); expect.fail('Should have thrown an error'); } catch (error) { expect(error).to.be.instanceOf(Error); expect(error.message).to.include('Failed to check kubectl version'); } }); it('getVersion should handle invalid output', async () => { sandbox.stub(kubectlDependencyManager, 'run').resolves(mockVersionOutputInvalid.split('\n')); try { await kubectlDependencyManager.getVersion('/usr/local/bin/kubectl'); expect.fail('Should have thrown an error'); } catch (error) { expect(error).to.be.instanceOf(Error); expect(error.message).to.include('Failed to get kubectl version'); } }); it('getVersion should handle missing client version in output', async () => { sandbox.stub(kubectlDependencyManager, 'run').resolves(mockVersionOutputMissingClient.split('\n')); try { await kubectlDependencyManager.getVersion('/usr/local/bin/kubectl'); expect.fail('Should have thrown an error'); } catch (error) { expect(error).to.be.instanceOf(Error); expect(error.message).to.include('Failed to get kubectl version'); } }); it('processDownloadedPackage should handle platform-specific executable names', async () => { container.register(InjectTokens.OsPlatform, { useValue: OperatingSystem.OS_LINUX }); // First test with non-Windows platform let kubectlDependencyManager = new KubectlDependencyManager(undefined, localInstallationDirectory, 'amd64', undefined); // @ts-expect-error TS2341: Property processDownloadedPackage is private const linuxResult = await kubectlDependencyManager.processDownloadedPackage('/tmp/kubectl', '/tmp'); expect(linuxResult).to.contain('/tmp/kubectl'); // Now test with Windows platform container.register(InjectTokens.OsPlatform, { useValue: OperatingSystem.OS_WIN32 }); kubectlDependencyManager = new KubectlDependencyManager(undefined, localInstallationDirectory, 'amd64', undefined); // @ts-expect-error TS2341: Property processDownloadedPackage is private const windowsResult = await kubectlDependencyManager.processDownloadedPackage('/tmp/kubectl.exe'); expect(windowsResult).to.contain('/tmp/kubectl.exe'); }); it('getArtifactName should generate correct URL format based on platform/arch', () => { container.register(InjectTokens.OsPlatform, { useValue: OperatingSystem.OS_LINUX }); const kubectlDependencyManager = new KubectlDependencyManager(undefined, localInstallationDirectory, 'amd64', '1.25.0'); // @ts-expect-error TS2341: Property getArtifactName is private const artifactName = kubectlDependencyManager.getArtifactName(); expect(artifactName).to.include('1.25.0'); expect(artifactName).to.include(OperatingSystem.OS_LINUX); expect(artifactName).to.include('amd64'); }); }); }); //# sourceMappingURL=kubectl-dependency-manager.test.js.map