Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
DiagnosticCheck,
DiagnosticReport,
DiagnosticResult,
DiagnosticSummary,
} from '../diagnostics/types.js';
import { EXIT_CODES } from '../errors/errors.js';
import { TerminalOutput } from '../output/terminal.js';
Expand All @@ -26,6 +27,22 @@ function formatResult(result: DiagnosticResult): string {
return `[${result.status.toUpperCase()}] ${result.label}: ${result.message}`;
}

function formatOverallHealth(summary: DiagnosticSummary): string {
if (summary.total === 0) {
return 'Overall health: no diagnostics registered.';
}

if (summary.fail > 0) {
return 'Overall health: action required.';
}

if (summary.warn > 0) {
return 'Overall health: usable with warnings.';
}

return 'Overall health: ready.';
}

function createDefaultDiagnostics(): readonly DiagnosticCheck[] {
return [
...createRuntimeDiagnostics(),
Expand Down Expand Up @@ -56,6 +73,7 @@ export function renderDiagnosticReport(
output.info(
`Summary: ${report.summary.pass} passed, ${report.summary.warn} warnings, ${report.summary.fail} failed.`,
);
output.info(formatOverallHealth(report.summary));
}

export function createDoctorCommand(
Expand Down
68 changes: 66 additions & 2 deletions tests/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,25 @@ describe('doctor command', () => {
expect(captured.stdoutText()).toContain(
'Summary: 0 passed, 0 warnings, 0 failed.',
);
expect(captured.stdoutText()).toContain(
'Overall health: no diagnostics registered.',
);
expect(captured.stderrText()).toBe('');
});

it('aggregates pass, warning, and failure results', () => {
it('aggregates pass, warning, and failure results with actionable health output', () => {
const captured = createCapturedTerminalOutput();
let exitCode = -1;
const command = createDoctorCommand({
checks: [
check('node', 'Node.js', 'pass', 'Supported version detected.'),
check('docker', 'Docker', 'warn', 'Docker is not available.'),
check(
'docker',
'Docker',
'warn',
'Docker is not available.',
'Install Docker only if the selected workflow requires it.',
),
check(
'stellar',
'Stellar CLI',
Expand All @@ -78,6 +87,9 @@ describe('doctor command', () => {
expect(captured.stdoutText()).toContain(
'[WARN] Docker: Docker is not available.',
);
expect(captured.stdoutText()).toContain(
'Remediation: Install Docker only if the selected workflow requires it.',
);
expect(captured.stdoutText()).toContain(
'[FAIL] Stellar CLI: Required CLI is missing.',
);
Expand All @@ -87,6 +99,55 @@ describe('doctor command', () => {
expect(captured.stdoutText()).toContain(
'Summary: 1 passed, 1 warnings, 1 failed.',
);
expect(captured.stdoutText()).toContain('Overall health: action required.');
});

it('keeps warning-only diagnostics non-fatal', () => {
const captured = createCapturedTerminalOutput();
let exitCode = -1;
const command = createDoctorCommand({
checks: [
check('node', 'Node.js', 'pass', 'Supported version detected.'),
check(
'docker',
'Docker',
'warn',
'Docker daemon is unavailable.',
'Start Docker only when a container-based workflow needs it.',
),
],
output: captured.output,
setExitCode: (code) => {
exitCode = code;
},
});

command.parse(['node', 'doctor']);

expect(exitCode).toBe(EXIT_CODES.success);
expect(captured.stdoutText()).toContain(
'Summary: 1 passed, 1 warnings, 0 failed.',
);
expect(captured.stdoutText()).toContain(
'Overall health: usable with warnings.',
);
});

it('reports a fully healthy diagnostic run clearly', () => {
const captured = createCapturedTerminalOutput();
let exitCode = -1;
const command = createDoctorCommand({
checks: [check('node', 'Node.js', 'pass', 'Supported version detected.')],
output: captured.output,
setExitCode: (code) => {
exitCode = code;
},
});

command.parse(['node', 'doctor']);

expect(exitCode).toBe(EXIT_CODES.success);
expect(captured.stdoutText()).toContain('Overall health: ready.');
});

it('converts an unexpected check exception into a safe failed diagnostic', () => {
Expand Down Expand Up @@ -114,6 +175,9 @@ describe('doctor command', () => {
expect(captured.stdoutText()).toContain(
'[FAIL] Unsafe check: Diagnostic check could not complete.',
);
expect(captured.stdoutText()).toContain(
'Remediation: Retry the command or inspect the relevant tool configuration.',
);
expect(captured.stdoutText()).not.toContain('SECRET_TOKEN');
});
});