Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/commands/studio/manager/package.manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class PackageManager extends BaseManager {
logger.error(
"You cannot overwrite a package and set a new key at the same time. Please use only one of the options."
);
process.exit();
process.exit(1);
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/core/command/module-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path = require("path");
import * as fs from "fs";
import { Command, CommandOptions, Option, OptionValues } from "commander";
import { Context } from "./cli-context";
import { logger } from "../utils/logger";
import { GracefulError, logger } from "../utils/logger";
import * as chalk from "chalk";

export abstract class IModule {
Expand Down Expand Up @@ -216,7 +216,12 @@ export class CommandConfig {
this.printDeprecationNoticeIfDeprecated();
await handler(this.ctx, this.cmd, this.cmd.optsWithGlobals());
} catch (error) {
if (error instanceof GracefulError) {
logger.error(error.message);
return;
}
logger.error(`An unexpected error occured executing a command: ${error}`);
process.exitCode = 1;
}
});
}
Expand Down
34 changes: 34 additions & 0 deletions tests/commands/studio/package.manager.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { PackageManager } from "../../../src/commands/studio/manager/package.manager";
import { loggingTestTransport } from "../../jest.setup";
import { testContext } from "../../utls/test-context";

describe("PackageManager", () => {
afterEach(() => {
jest.restoreAllMocks();
});

it("exits with code 1 when overwrite and new key are used together", () => {
const exitSignal = new Error("process.exit(1)");
const exitSpy = jest.spyOn(process, "exit").mockImplementation((() => {
throw exitSignal;
}) as never);
loggingTestTransport.logMessages = [];

const manager = new PackageManager(testContext);
manager.spaceKey = "space-id";
manager.key = "my-package";
manager.newKey = "renamed-package";
manager.overwrite = true;
manager.store = false;
manager.draft = false;

expect(() => manager.getConfig()).toThrow(exitSignal);

expect(exitSpy).toHaveBeenCalledWith(1);
expect(
loggingTestTransport.logMessages.some(entry =>
String(entry.message).includes("You cannot overwrite a package and set a new key at the same time")
)
).toBe(true);
});
});
62 changes: 62 additions & 0 deletions tests/core/command/module-handler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Command } from "commander";
import { Configurator } from "../../../src/core/command/module-handler";
import { GracefulError } from "../../../src/core/utils/logger";
import { loggingTestTransport } from "../../jest.setup";
import { testContext } from "../../utls/test-context";

describe("CommandConfig action error handling", () => {
let previousExitCode: number | undefined;

beforeEach(() => {
previousExitCode = process.exitCode;
process.exitCode = 0;
loggingTestTransport.logMessages = [];
});

afterEach(() => {
process.exitCode = previousExitCode;
});

async function runCommand(handler: () => Promise<void>): Promise<void> {
const program = new Command();
const configurator = new Configurator(program, testContext);

configurator.command("test-command").action(async () => {
await handler();
});

program.exitOverride();
await program.parseAsync(["node", "content-cli", "test-command"]);
}

it("logs a graceful error and keeps exitCode at zero", async () => {
await runCommand(async () => {
throw new GracefulError("graceful failure");
});

expect(process.exitCode ?? 0).toBe(0);
expect(
loggingTestTransport.logMessages.some(entry =>
String(entry.message).includes("graceful failure")
)
).toBe(true);
expect(
loggingTestTransport.logMessages.some(entry =>
String(entry.message).includes("An unexpected error occured executing a command")
)
).toBe(false);
});

it("logs unexpected errors and marks the process as failed", async () => {
await runCommand(async () => {
throw new Error("boom");
});

expect(process.exitCode).toBe(1);
expect(
loggingTestTransport.logMessages.some(entry =>
String(entry.message).includes("An unexpected error occured executing a command")
)
).toBe(true);
});
});
51 changes: 44 additions & 7 deletions tests/integration/commands/asset-registry.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import Module = require("../../../src/commands/asset-registry/module");
import { Command } from "commander";
import { AssetRegistryService } from "../../../src/commands/asset-registry/asset-registry.service";
import { buildTestProgram } from "../../utls/cli-program";
import { CliRunResult, runCli as runCliProcess } from "../../utls/cli-runner";
import { GracefulError } from "../../../src/core/utils/logger";

jest.mock("../../../src/commands/asset-registry/asset-registry.service");

describe("asset-registry command integration", () => {
let program: Command;
let mockService: jest.Mocked<AssetRegistryService>;

beforeEach(() => {
Expand All @@ -21,12 +20,10 @@ describe("asset-registry command integration", () => {

(AssetRegistryService as jest.MockedClass<typeof AssetRegistryService>)
.mockImplementation(() => mockService);

program = buildTestProgram([Module]);
});

function runCli(args: string[]): Promise<Command> {
return program.parseAsync(["node", "content-cli", ...args]);
async function runCli(args: string[]): Promise<CliRunResult> {
return runCliProcess(args, [Module]);
}

describe("asset-registry schema", () => {
Expand Down Expand Up @@ -141,4 +138,44 @@ describe("asset-registry command integration", () => {
expect(mockService.getType).toHaveBeenCalledWith("BOARD_V2", true);
});
});

describe("exit codes", () => {
it("exits with code 0 on a successful command", async () => {
const result = await runCli(["asset-registry", "list"]);

expect(result.exitCode).toBe(0);
});

it("exits non-zero and reports the error when the service fails", async () => {
mockService.listTypes.mockRejectedValueOnce(new Error("Asset registry feature is disabled"));

const result = await runCli(["asset-registry", "list"]);

expect(result.exitCode).toBe(1);
expect(result.output).toContain("Asset registry feature is disabled");
});

it("exits with code 0 when the service raises a GracefulError", async () => {
mockService.listTypes.mockRejectedValueOnce(new GracefulError("Nothing to list"));

const result = await runCli(["asset-registry", "list"]);

expect(result.exitCode).toBe(0);
expect(result.output).toContain("Nothing to list");
});

it("exits non-zero for an unknown command", async () => {
const result = await runCli(["this-command-does-not-exist"]);

expect(result.exitCode).not.toBe(0);
expect(mockService.listTypes).not.toHaveBeenCalled();
});

it("exits non-zero when a required option is missing", async () => {
const result = await runCli(["asset-registry", "get"]);

expect(result.exitCode).not.toBe(0);
expect(mockService.getType).not.toHaveBeenCalled();
});
});
});
Loading
Loading