UNPKG

@hashgraph/solo

Version:

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

147 lines 7.02 kB
// SPDX-License-Identifier: Apache-2.0 import { expect } from 'chai'; import { afterEach, beforeEach, describe, it } from 'mocha'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import sinon from 'sinon'; import { DiagnosticsReporter } from '../../../../src/commands/util/diagnostics-reporter.js'; import { ShellRunner } from '../../../../src/core/shell-runner.js'; import { SoloError } from '../../../../src/core/errors/solo-error.js'; function makeLoggerStub() { return { info: sinon.stub(), warn: sinon.stub(), error: sinon.stub(), debug: sinon.stub(), showUser: sinon.stub(), showUserError: sinon.stub(), showList: sinon.stub(), showJSON: sinon.stub(), addMessageGroup: sinon.stub(), getMessageGroup: sinon.stub().returns([]), addMessageGroupMessage: sinon.stub(), showMessageGroup: sinon.stub(), getMessageGroupKeys: sinon.stub().returns([]), showAllMessageGroups: sinon.stub(), setDevMode: sinon.stub(), isDevMode: sinon.stub().returns(false), nextTraceId: sinon.stub(), prepMeta: sinon.stub().callsFake((meta) => meta ?? {}), flush: sinon.stub().callsFake((callback) => callback()), }; } describe('DiagnosticsReporter', () => { let temporaryDirectory; let loggerStub; beforeEach(() => { temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'solo-diagnostics-reporter-')); loggerStub = makeLoggerStub(); }); afterEach(() => { fs.rmSync(temporaryDirectory, { recursive: true, force: true }); sinon.restore(); }); describe('isGhCliAvailable', () => { it('returns true when gh is on the PATH', async () => { sinon.stub(ShellRunner.prototype, 'run').resolves(['/usr/bin/gh']); expect(await DiagnosticsReporter.isGhCliAvailable(loggerStub)).to.equal(true); }); it('returns false when gh is not found', async () => { sinon.stub(ShellRunner.prototype, 'run').rejects(new Error('not found')); expect(await DiagnosticsReporter.isGhCliAvailable(loggerStub)).to.equal(false); }); }); describe('findLatestDebugZip', () => { it('returns undefined when directory does not exist', () => { expect(DiagnosticsReporter.findLatestDebugZip('/nonexistent-dir-xyz', 'test-deployment', Date.now())).to.equal(undefined); }); it('returns undefined when no matching zip exists', () => { expect(DiagnosticsReporter.findLatestDebugZip(temporaryDirectory, 'my-deployment', 0)).to.equal(undefined); }); it('finds the most recently modified zip created after the start time', () => { const deployment = 'my-deployment'; const beforeStart = Date.now() - 1000; const zipPath = path.join(temporaryDirectory, `solo-debug-${deployment}-2026-04-01T10-00-00.zip`); fs.writeFileSync(zipPath, 'fake zip content'); const result = DiagnosticsReporter.findLatestDebugZip(temporaryDirectory, deployment, beforeStart); expect(result).to.equal(zipPath); }); it('ignores zip files created before the start time', () => { const deployment = 'my-deployment'; const zipPath = path.join(temporaryDirectory, `solo-debug-${deployment}-old.zip`); fs.writeFileSync(zipPath, 'old fake content'); // Start time is in the future relative to the file's mtime const result = DiagnosticsReporter.findLatestDebugZip(temporaryDirectory, deployment, Date.now() + 5000); expect(result).to.equal(undefined); }); }); describe('readAnalysisContent', () => { it('returns empty string when the analysis file does not exist', () => { expect(DiagnosticsReporter.readAnalysisContent(temporaryDirectory)).to.equal(''); }); it('returns the file content when the analysis file exists', () => { const analysisPath = path.join(temporaryDirectory, 'diagnostics-analysis.txt'); fs.writeFileSync(analysisPath, 'some analysis content'); expect(DiagnosticsReporter.readAnalysisContent(temporaryDirectory)).to.equal('some analysis content'); }); }); describe('buildIssueBody', () => { it('includes all required metadata fields', () => { const body = DiagnosticsReporter.buildIssueBody({ soloVersion: '1.2.3', deployment: 'my-deploy', timestamp: '2026-04-01T10-00-00', analysisDirectory: '/tmp/analysis', zipFilePath: '/tmp/solo-debug.zip', }); expect(body).to.include('Solo Version**: 1.2.3'); expect(body).to.include('Deployment**: my-deploy'); expect(body).to.include('2026-04-01T10-00-00'); expect(body).to.include('/tmp/solo-debug.zip'); expect(body).to.include('Please attach it to this issue'); }); it('uses (not specified) when deployment is empty', () => { const body = DiagnosticsReporter.buildIssueBody({ soloVersion: '1.0.0', deployment: '', timestamp: '2026-04-01T10-00-00', analysisDirectory: '/tmp/analysis', }); expect(body).to.include('(not specified)'); }); }); describe('createGitHubIssue', () => { let executeGhCommandStub; const mockPid = 12_345; beforeEach(() => { executeGhCommandStub = sinon.stub(DiagnosticsReporter, 'executeGhCommand'); }); it('returns the issue URL on success', async () => { const expectedUrl = 'https://github.com/hiero-ledger/solo/issues/42'; executeGhCommandStub.returns({ status: 0, stdout: `${expectedUrl}\n`, stderr: '', output: [undefined, `${expectedUrl}\n`, ''], pid: mockPid, signal: undefined, }); const url = await DiagnosticsReporter.createGitHubIssue(loggerStub, 'Test Title', 'Test Body', '/tmp/analysis'); expect(url).to.equal(expectedUrl); expect(executeGhCommandStub).to.have.been.calledOnce; }); it('throws SoloError when gh command fails', async () => { executeGhCommandStub.returns({ status: 1, stdout: '', stderr: 'authentication required', output: [undefined, '', 'authentication required'], pid: mockPid, signal: undefined, }); await expect(DiagnosticsReporter.createGitHubIssue(loggerStub, 'Test Title', 'Test Body', '/tmp/analysis')).to.be.rejectedWith(SoloError, /Failed to create GitHub issue/); }); }); }); //# sourceMappingURL=diagnostics-reporter.test.js.map