diff --git a/integ-tests/status.test.ts b/integ-tests/status.test.ts index 55890847f..5381ce5cd 100644 --- a/integ-tests/status.test.ts +++ b/integ-tests/status.test.ts @@ -69,7 +69,7 @@ describe('status command', () => { it('emits failure telemetry for invalid --type', async () => { const result = await runCLI(['status', '--type', 'bogus'], projectDir, { env: telemetry.env }); - expect(result.exitCode).toBe(0); + expect(result.exitCode).toBe(1); telemetry.assertMetricEmitted({ command: 'status', exit_reason: 'failure', @@ -80,7 +80,7 @@ describe('status command', () => { it('emits failure telemetry for invalid --state', async () => { const result = await runCLI(['status', '--state', 'bogus'], projectDir, { env: telemetry.env }); - expect(result.exitCode).toBe(0); + expect(result.exitCode).toBe(1); telemetry.assertMetricEmitted({ command: 'status', exit_reason: 'failure', diff --git a/src/cli/commands/status/__tests__/command.test.ts b/src/cli/commands/status/__tests__/command.test.ts new file mode 100644 index 000000000..55521cc56 --- /dev/null +++ b/src/cli/commands/status/__tests__/command.test.ts @@ -0,0 +1,77 @@ +import { handleProjectStatus, loadStatusConfig } from '../action.js'; +import { registerStatus } from '../command.js'; +import { Command } from '@commander-js/extra-typings'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +const { mockRender } = vi.hoisted(() => ({ + mockRender: vi.fn(), +})); + +vi.mock('../../../tui/guards', () => ({ + requireProject: vi.fn(), +})); + +vi.mock('../../../telemetry/cli-command-run.js', () => ({ + withCommandRunTelemetry: vi.fn((_command, _attrs, run) => run()), +})); + +vi.mock('../../../operations/dataset', () => ({ + getDatasetStatus: vi.fn(), +})); + +vi.mock('../action.js', () => ({ + handleProjectStatus: vi.fn(), + handleRuntimeLookup: vi.fn(), + loadStatusConfig: vi.fn(), +})); + +vi.mock('../../../feature-flags', () => ({ + isPreviewEnabled: () => false, +})); + +vi.mock('ink', () => ({ + Box: ({ children }: { children?: unknown }) => children, + Text: ({ children }: { children?: unknown }) => children, + render: mockRender, +})); + +describe('status command validation', () => { + let program: Command; + let originalExitCode: typeof process.exitCode; + + beforeEach(() => { + originalExitCode = process.exitCode; + process.exitCode = undefined; + program = new Command(); + program.exitOverride(); + registerStatus(program); + }); + + afterEach(() => { + process.exitCode = originalExitCode; + vi.clearAllMocks(); + }); + + it('sets a non-zero exit code for invalid resource type', async () => { + await program.parseAsync(['status', '--type', 'bogus'], { from: 'user' }); + + expect(process.exitCode).toBe(1); + expect(mockRender).toHaveBeenCalled(); + }); + + it('sets a non-zero exit code for invalid state', async () => { + await program.parseAsync(['status', '--state', 'bogus'], { from: 'user' }); + + expect(process.exitCode).toBe(1); + expect(mockRender).toHaveBeenCalled(); + }); + + it('leaves exit code unset for a valid filter', async () => { + vi.mocked(loadStatusConfig).mockResolvedValue({} as never); + vi.mocked(handleProjectStatus).mockResolvedValue({ success: true, resources: [] } as never); + + await program.parseAsync(['status', '--type', 'agent'], { from: 'user' }); + + expect(process.exitCode).toBeUndefined(); + }); +}); diff --git a/src/cli/commands/status/command.tsx b/src/cli/commands/status/command.tsx index dda4e226f..bafccb023 100644 --- a/src/cli/commands/status/command.tsx +++ b/src/cli/commands/status/command.tsx @@ -96,6 +96,7 @@ export const registerStatus = (program: Command) => { error: new ValidationError(msg), })); render({msg}); + process.exitCode = 1; return; } @@ -107,6 +108,7 @@ export const registerStatus = (program: Command) => { error: new ValidationError(msg), })); render({msg}); + process.exitCode = 1; return; }