UNPKG

@hashgraph/solo

Version:

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

324 lines 16.5 kB
// SPDX-License-Identifier: Apache-2.0 import { expect } from 'chai'; import { after, before, beforeEach, afterEach, describe, it } from 'mocha'; import fs from 'node:fs'; import path from 'node:path'; import sinon from 'sinon'; import { PodmanDependencyManager } 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 * as constants from '../../../../../src/core/constants.js'; import { OperatingSystem } from '../../../../../src/business/utils/operating-system.js'; import { InjectTokens } from '../../../../../src/core/dependency-injection/inject-tokens.js'; import { container } from 'tsyringe-neo'; import { platform } from 'node:process'; import { ShellRunner } from '../../../../../src/core/shell-runner.js'; // Test data constants const PODMAN_VERSION = version.PODMAN_VERSION.replace('v', ''); const PODMAN_LOW_VERSION = '0.1.0'; const MOCK_RELEASE_TAG = `v${PODMAN_VERSION}`; const MOCK_RELEASE_URL = `https://github.com/containers/podman/releases/tag/${MOCK_RELEASE_TAG}`; const MOCK_DOWNLOAD_URL_BASE = `https://github.com/containers/podman/releases/download/${MOCK_RELEASE_TAG}`; const MOCK_LINUX_ASSET_NAME = 'podman-remote-static-linux_amd64.tar.gz'; const MOCK_WINDOWS_ASSET_NAME = 'podman-remote-release-windows_amd64.zip'; const MOCK_DARWIN_ARM64_ASSET_NAME = 'podman-remote-release-darwin_arm64.zip'; const MOCK_LINUX_DOWNLOAD_URL = `${MOCK_DOWNLOAD_URL_BASE}/${MOCK_LINUX_ASSET_NAME}`; const MOCK_WINDOWS_DOWNLOAD_URL = `${MOCK_DOWNLOAD_URL_BASE}/${MOCK_WINDOWS_ASSET_NAME}`; const MOCK_DARWIN_ARM64_DOWNLOAD_URL = `${MOCK_DOWNLOAD_URL_BASE}/${MOCK_DARWIN_ARM64_ASSET_NAME}`; const MOCK_CHECKSUM = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; const MOCK_CHECKSUM_WITH_PREFIX = `sha256:${MOCK_CHECKSUM}`; // Mock GitHub API response for fetchReleaseInfo const MOCK_GITHUB_RELEASES_RESPONSE = { ok: true, json: async () => [ { tag_name: MOCK_RELEASE_TAG, html_url: MOCK_RELEASE_URL, assets: [ { name: MOCK_LINUX_ASSET_NAME, browser_download_url: MOCK_LINUX_DOWNLOAD_URL, content_type: 'application/gzip', size: 12_345, digest: MOCK_CHECKSUM_WITH_PREFIX, }, { name: MOCK_WINDOWS_ASSET_NAME, browser_download_url: MOCK_WINDOWS_DOWNLOAD_URL, content_type: 'application/zip', size: 12_345, digest: MOCK_CHECKSUM_WITH_PREFIX, }, { name: MOCK_DARWIN_ARM64_ASSET_NAME, browser_download_url: MOCK_DARWIN_ARM64_DOWNLOAD_URL, content_type: 'application/zip', size: 12_345, digest: MOCK_CHECKSUM_WITH_PREFIX, }, ], }, ], }; // Mock GitHub API response with no matching assets const MOCK_GITHUB_RELEASES_NO_MATCHING_ASSET = { ok: true, json: async () => [ { tag_name: MOCK_RELEASE_TAG, html_url: MOCK_RELEASE_URL, assets: [ { name: 'some-other-asset.tar.gz', browser_download_url: `${MOCK_DOWNLOAD_URL_BASE}/some-other-asset.tar.gz`, content_type: 'application/gzip', size: 12_345, digest: MOCK_CHECKSUM_WITH_PREFIX, }, ], }, ], }; // Mock GitHub API error response const MOCK_GITHUB_ERROR_RESPONSE = { ok: false, status: 404, }; // Mock GitHub API empty releases response const MOCK_GITHUB_EMPTY_RELEASES = { ok: true, json: async () => [], }; describe('PodmanDependencyManager', () => { const temporaryDirectory = PathEx.join(getTemporaryDirectory(), 'bin'); const originalPlatform = platform; let sandbox; before(() => { fs.mkdirSync(temporaryDirectory, { recursive: true }); sandbox = sinon.createSandbox(); }); after(() => { if (fs.existsSync(temporaryDirectory)) { fs.rmSync(temporaryDirectory, { recursive: true }); } }); afterEach(() => { sandbox.restore(); }); it('should return podman version', () => { const podmanDependencyManager = new PodmanDependencyManager(undefined, temporaryDirectory, undefined, undefined, undefined, undefined); expect(podmanDependencyManager.getRequiredVersion()).to.equal(version.PODMAN_VERSION); }); it('should be able to check when podman not installed', () => { const podmanDependencyManager = new PodmanDependencyManager(undefined, temporaryDirectory, undefined, undefined, undefined, undefined); expect(podmanDependencyManager.isInstalledLocally()).not.to.be.ok; }); it('should be able to check when podman is installed', async () => { const podmanDependencyManager = new PodmanDependencyManager(undefined, temporaryDirectory, undefined, undefined, undefined, undefined); fs.writeFileSync(PathEx.join(temporaryDirectory, constants.PODMAN), ''); expect(podmanDependencyManager.isInstalledLocally()).to.be.ok; }); describe('PodmanDependencyManager system methods', () => { let podmanDependencyManager; let fetchStub; let originalFetch; beforeEach(() => { podmanDependencyManager = new PodmanDependencyManager(undefined, temporaryDirectory, process.arch, undefined, undefined, undefined); // Mock fetch for fetchReleaseInfo originalFetch = globalThis.fetch; globalThis.fetch = sandbox.stub(); fetchStub = globalThis.fetch; }); afterEach(() => { globalThis.fetch = originalFetch; container.register(InjectTokens.OsPlatform, { useValue: originalPlatform }); sandbox.restore(); }); it('getVersion should return version from podman --version output', async () => { const executableWithPath = '/usr/local/bin/podman'; sandbox .stub(ShellRunner.prototype, 'run') .withArgs(`"${executableWithPath}" --version`) .resolves([`podman version ${PODMAN_VERSION}`]); const version = await podmanDependencyManager.getVersion(executableWithPath); expect(version).to.equal(PODMAN_VERSION); }); it('getVersion should throw error when command fails', async () => { sandbox.stub(ShellRunner.prototype, 'run').rejects(new Error('Command failed')); try { await podmanDependencyManager.getVersion('/usr/local/bin/podman'); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.include('Failed to check podman version'); } }); it('getVersion should throw error when version pattern not found', async () => { sandbox.stub(ShellRunner.prototype, 'run').resolves(['Invalid output']); try { await podmanDependencyManager.getVersion('/usr/local/bin/podman'); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.include('Failed to check podman version'); } }); it('shouldInstall should return false when Docker is installed', async () => { sandbox .stub(ShellRunner.prototype, 'run') .withArgs(`"${constants.DOCKER}" --version`) .resolves(['Docker version 20.10.8']); const result = await podmanDependencyManager.shouldInstall(); expect(result).to.be.false; }); it('shouldInstall should return true when Docker is not installed', async () => { sandbox .stub(ShellRunner.prototype, 'run') .withArgs(`"${constants.DOCKER}" --version`) .rejects(new Error('Docker not found')); const result = await podmanDependencyManager.shouldInstall(); expect(result).to.be.true; }); it('getArch should normalize architecture names', () => { // Test x64 to amd64 conversion let manager = new PodmanDependencyManager(undefined, temporaryDirectory, 'x64', undefined, undefined, undefined); // @ts-expect-error TS2341: Property getArch is protected expect(manager.getArch()).to.equal('amd64'); // Test arm64 conversion manager = new PodmanDependencyManager(undefined, temporaryDirectory, 'arm64', undefined, undefined, undefined); // @ts-expect-error TS2341: Property getArch is protected expect(manager.getArch()).to.equal('arm64'); // Test aarch64 to arm64 conversion manager = new PodmanDependencyManager(undefined, temporaryDirectory, 'aarch64', undefined, undefined, undefined); // @ts-expect-error TS2341: Property getArch is protected expect(manager.getArch()).to.equal('arm64'); }); it('fetchReleaseInfo should parse GitHub API response correctly', async () => { fetchStub.resolves(MOCK_GITHUB_RELEASES_RESPONSE); container.register(InjectTokens.OsPlatform, { useValue: OperatingSystem.OS_LINUX }); podmanDependencyManager = new PodmanDependencyManager(undefined, temporaryDirectory, 'x64', undefined, undefined, undefined); // @ts-expect-error TS2341: Property fetchReleaseInfo is private const releaseInfo = await podmanDependencyManager.fetchReleaseInfo(MOCK_RELEASE_TAG); expect(releaseInfo.downloadUrl).to.equal(MOCK_DOWNLOAD_URL_BASE); expect(releaseInfo.assetName).to.equal(MOCK_LINUX_ASSET_NAME); expect(releaseInfo.checksum).to.equal(MOCK_CHECKSUM); expect(releaseInfo.version).to.equal(PODMAN_VERSION); }); it('fetchReleaseInfo should handle API error', async () => { fetchStub.resolves(MOCK_GITHUB_ERROR_RESPONSE); try { // @ts-expect-error TS2341: Property fetchReleaseInfo is private await podmanDependencyManager.fetchReleaseInfo(MOCK_RELEASE_TAG); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.include('GitHub API request failed with status 404'); } }); it('fetchReleaseInfo should handle empty releases array', async () => { fetchStub.resolves(MOCK_GITHUB_EMPTY_RELEASES); try { // @ts-expect-error TS2341: Property fetchReleaseInfo is private await podmanDependencyManager.fetchReleaseInfo(MOCK_RELEASE_TAG); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.include('No releases found'); } }); it('fetchReleaseInfo should handle no matching asset', async () => { fetchStub.resolves(MOCK_GITHUB_RELEASES_NO_MATCHING_ASSET); try { // @ts-expect-error TS2341: Property fetchReleaseInfo is private await podmanDependencyManager.fetchReleaseInfo(MOCK_RELEASE_TAG); expect.fail('Should have thrown an error'); } catch (error) { expect(error.message).to.include('No matching asset found for'); } }); }); describe('when podman is installed globally', () => { let podmanDependencyManager; let runStub; let existsSyncStub; let fetchStub; let originalFetch; beforeEach(() => { podmanDependencyManager = new PodmanDependencyManager(undefined, temporaryDirectory, process.arch, undefined, undefined, undefined); podmanDependencyManager.uninstallLocal(); runStub = sandbox.stub(podmanDependencyManager, 'run'); // Mock fetch for fetchReleaseInfo originalFetch = globalThis.fetch; globalThis.fetch = sandbox.stub(); fetchStub = globalThis.fetch; // Configure fetch to return valid mock response fetchStub.resolves(MOCK_GITHUB_RELEASES_RESPONSE); // Add stubs for file system operations sandbox.stub(fs, 'cpSync').returns(); sandbox.stub(fs, 'chmodSync').returns(); existsSyncStub = sandbox.stub(fs, 'existsSync').returns(true); sandbox.stub(fs, 'rmSync').returns(); }); afterEach(() => { globalThis.fetch = originalFetch; sandbox.restore(); }); it('should prefer the global installation if it meets the requirements', async () => { sandbox.stub(podmanDependencyManager, 'shouldInstall').resolves(true); const fakeGlobalBinDirectory = '/test-solo-global-bin'; const fakeGlobalPodmanPath = `${fakeGlobalBinDirectory}/podman`; const originalPath = process.env.PATH ?? ''; process.env.PATH = `${fakeGlobalBinDirectory}${path.delimiter}${originalPath}`; sandbox.stub(fs, 'accessSync').callsFake((filePath) => { if (String(filePath) === fakeGlobalPodmanPath) { return; } throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); }); runStub.withArgs(`"${fakeGlobalPodmanPath}" --version`).resolves([`podman version ${version.PODMAN_VERSION}`]); existsSyncStub.withArgs(`${temporaryDirectory}/podman`).returns(false); try { // @ts-expect-error TS2341: Property isInstalledGloballyAndMeetsRequirements is private const result = await podmanDependencyManager.isInstalledGloballyAndMeetsRequirements(); expect(result).to.be.true; expect(await podmanDependencyManager.install(getTestCacheDirectory())).to.be.true; // Should return global path since it meets requirements expect(await podmanDependencyManager.getExecutable()).to.equal(constants.PODMAN); } finally { process.env.PATH = originalPath; } }); it('should install podman locally if the global installation does not meet the requirements', async () => { const fakeGlobalBinDirectory = '/test-solo-global-bin'; const fakeGlobalPodmanPath = `${fakeGlobalBinDirectory}/podman`; const originalPath = process.env.PATH ?? ''; process.env.PATH = `${fakeGlobalBinDirectory}${path.delimiter}${originalPath}`; sandbox.stub(fs, 'accessSync').callsFake((filePath) => { if (String(filePath) === fakeGlobalPodmanPath) { return; } throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); }); runStub.withArgs(`"${fakeGlobalPodmanPath}" --version`).resolves([`podman version ${PODMAN_LOW_VERSION}`]); runStub .withArgs(`"${PathEx.join(temporaryDirectory, 'podman')}" --version`) .resolves([`podman version ${PODMAN_LOW_VERSION}`]); existsSyncStub.withArgs(PathEx.join(temporaryDirectory, 'podman')).returns(true); try { // @ts-expect-error TS2341: Property isInstalledGloballyAndMeetsRequirements is private const result = await podmanDependencyManager.isInstalledGloballyAndMeetsRequirements(); expect(result).to.be.false; expect(await podmanDependencyManager.install(getTestCacheDirectory())).to.be.true; expect(fs.existsSync(PathEx.join(temporaryDirectory, 'podman'))).to.be.ok; expect(await podmanDependencyManager.getExecutable()).to.equal(constants.PODMAN); } finally { process.env.PATH = originalPath; } }); }); }); //# sourceMappingURL=podman-dependency-manager.test.js.map