@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
101 lines • 4.98 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import 'sinon-chai';
import sinon from 'sinon';
import { expect } from 'chai';
import { describe, it, beforeEach, afterEach } from 'mocha';
import { ShellRunner } from '../../../src/core/shell-runner.js';
import { ChildProcess } from 'node:child_process';
import { Readable } from 'node:stream';
import { Duration } from '../../../src/core/time/duration.js';
import { SoloPinoLogger } from '../../../src/core/logging/solo-pino-logger.js';
import { OperatingSystem } from '../../../src/business/utils/operating-system.js';
describe('ShellRunner', () => {
let shellRunner, loggerDebugStub, loggerInfoStub, childProcessSpy, readableSpy;
beforeEach(() => {
shellRunner = new ShellRunner();
// Spy on methods
loggerDebugStub = sinon.stub(SoloPinoLogger.prototype, 'debug');
loggerInfoStub = sinon.stub(SoloPinoLogger.prototype, 'info');
childProcessSpy = sinon.spy(ChildProcess.prototype, 'on');
readableSpy = sinon.spy(Readable.prototype, 'on');
});
afterEach(() => sinon.restore());
it('should run command', async () => {
const commandToRun = OperatingSystem.isWin32() ? 'dir' : 'ls -l';
await shellRunner.run(commandToRun);
loggerInfoStub.withArgs(`Executing command: '${commandToRun}'`).onFirstCall();
loggerDebugStub.withArgs(`Finished executing: '${commandToRun}'`, sinon.match.any).onFirstCall();
expect(loggerDebugStub).to.have.been.calledOnce;
expect(loggerInfoStub).to.have.been.calledOnce;
expect(readableSpy).to.have.been.calledWith('data', sinon.match.any);
expect(childProcessSpy).to.have.been.calledWith('exit', sinon.match.any);
}).timeout(Duration.ofSeconds(10).toMillis());
it('should complete successfully within timeout', async () => {
const result = await shellRunner.run('echo hello', [], false, false, {}, 10_000);
expect(result).to.include('hello');
}).timeout(Duration.ofSeconds(15).toMillis());
it('should reject with timeout error when command exceeds timeoutMs', async () => {
// Use a node one-liner as a cross-platform long-running command: works on all platforms
// (Windows `timeout /t N` exits immediately when stdin is non-interactive in CI environments)
const commandToRun = 'node -e "setTimeout(()=>{}, 10000)"';
const timeoutMs = 500;
await expect(shellRunner.run(commandToRun, [], false, false, {}, timeoutMs)).to.be.rejectedWith(`Command timed out after ${timeoutMs}ms`);
}).timeout(Duration.ofSeconds(10).toMillis());
describe('redactArguments', () => {
it('should redact --password and its value', () => {
const arguments_ = ['--password', 'mySecret'];
const redacted = ShellRunner.redactArguments(arguments_);
expect(redacted).to.deep.equal(['--password', '******']);
});
it('should redact -p and its value', () => {
const arguments_ = ['-p', 'mySecret'];
const redacted = ShellRunner.redactArguments(arguments_);
expect(redacted).to.deep.equal(['-p', '******']);
});
it('should redact sensitive key=value pairs', () => {
const arguments_ = [
'--set',
'global.password=mySecret',
'some-token=abc',
'my_key=123',
'normal=value',
];
const redacted = ShellRunner.redactArguments(arguments_);
expect(redacted).to.deep.equal([
'--set',
'global.password=******',
'some-token=******',
'my_key=******',
'normal=value',
]);
});
it('should not modify unrelated arguments', () => {
const arguments_ = ['--set', 'global.name=myApp', '--values', 'values.yaml'];
const redacted = ShellRunner.redactArguments(arguments_);
expect(redacted).to.deep.equal(['--set', 'global.name=myApp', '--values', 'values.yaml']);
});
it('should redact composite arguments', () => {
const arguments_ = [
'helm',
'upgrade',
'--values values.yaml --set foo.bar=false --set foo.privateKey=0x123456 --set foo.bar.ALL_CAPS_KEY=0x123456 --set foo.bar.foo.bar.password=123456',
];
const redacted = ShellRunner.redactArguments(arguments_);
expect(redacted).to.deep.equal([
'helm',
'upgrade',
'--values',
'values.yaml',
'--set',
'foo.bar=false',
'--set',
'foo.privateKey=******',
'--set',
'foo.bar.ALL_CAPS_KEY=******',
'--set',
'foo.bar.foo.bar.password=******',
]);
});
});
});
//# sourceMappingURL=shell-runner.test.js.map