Skip to content

Commit 20f1970

Browse files
fix: remove usages of logger.out in CLI code
`logger.out` has been removed before 6.0 release, but it has still been used in CLI's code. Remove its usages and add tests for `errors.beginCommand` (the one where the method has been used until now.
1 parent 784cf72 commit 20f1970

File tree

4 files changed

+199
-7
lines changed

4 files changed

+199
-7
lines changed

lib/common/errors.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,15 @@ export class Errors implements IErrors {
175175
try {
176176
return await action();
177177
} catch (ex) {
178-
const loggerLevel: string = $injector.resolve("logger").getLevel().toUpperCase();
178+
const logger = this.$injector.resolve("logger");
179+
const loggerLevel: string = logger.getLevel().toUpperCase();
179180
const printCallStack = this.printCallStack || loggerLevel === "TRACE" || loggerLevel === "DEBUG";
180181
const message = printCallStack ? resolveCallStack(ex) : isInteractive() ? `\x1B[31;1m${ex.message}\x1B[0m` : ex.message;
181182

182183
if (ex.printOnStdout) {
183-
this.$injector.resolve("logger").out(message);
184+
logger.info(message);
184185
} else {
185-
console.error(message);
186+
logger.error(message);
186187
}
187188

188189
if (ex.suggestCommandHelp) {

lib/common/test/unit-tests/errors.ts

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { Yok } from "../../yok";
2+
import { Errors } from "../../errors";
3+
import { CommonLoggerStub } from "./stubs";
4+
import { assert } from "chai";
5+
import * as path from "path";
6+
import { LoggerLevel } from "../../../constants";
7+
const helpers = require("../../helpers");
8+
const originalIsInteractive = helpers.isInteractive;
9+
const originalProcessExit = process.exit;
10+
11+
describe("errors", () => {
12+
let isInteractive = false;
13+
let processExitCode = 0;
14+
15+
before(() => {
16+
helpers.isInteractive = () => isInteractive;
17+
});
18+
19+
after(() => {
20+
helpers.isInteractive = originalIsInteractive;
21+
});
22+
23+
const getTestInjector = (): IInjector => {
24+
const testInjector = new Yok();
25+
testInjector.register("errors", Errors);
26+
testInjector.register("logger", CommonLoggerStub);
27+
return testInjector;
28+
};
29+
30+
describe("beginCommand", () => {
31+
let testInjector: IInjector;
32+
let errors: IErrors;
33+
let logger: CommonLoggerStub;
34+
const errMsg = "Error Message";
35+
let isProcessExitCalled = false;
36+
let isPrintCommandHelpSuggestionExecuted = false;
37+
let actionResult = true;
38+
39+
beforeEach(() => {
40+
(<any>process).exit = (code?: number): any => {
41+
isProcessExitCalled = true;
42+
processExitCode = code;
43+
};
44+
testInjector = getTestInjector();
45+
errors = testInjector.resolve("errors");
46+
logger = testInjector.resolve("logger");
47+
isProcessExitCalled = false;
48+
isPrintCommandHelpSuggestionExecuted = false;
49+
actionResult = true;
50+
isInteractive = false;
51+
});
52+
53+
afterEach(() => {
54+
process.exit = originalProcessExit;
55+
});
56+
57+
const setupTest = (opts?: { err?: Error }): { action: () => Promise<boolean>, printCommandHelpSuggestion: () => Promise<void> } => {
58+
const action = async () => {
59+
if (opts && opts.err) {
60+
throw opts.err;
61+
}
62+
63+
return actionResult;
64+
};
65+
66+
const printCommandHelpSuggestion = async () => {
67+
isPrintCommandHelpSuggestionExecuted = true;
68+
};
69+
70+
return { action, printCommandHelpSuggestion };
71+
};
72+
73+
const assertProcessExited = () => {
74+
assert.isTrue(isProcessExitCalled, "When the action fails, process.exit must be called.");
75+
assert.equal(processExitCode, 127, "When the action fails, process.exit must be called with 127 by default.");
76+
};
77+
78+
const assertPrintCommandHelpSuggestionIsNotCalled = () => {
79+
assert.isFalse(isPrintCommandHelpSuggestionExecuted, "printCommandHelpSuggestion should not be called when the suggestCommandHelp is not set to the exception.");
80+
};
81+
82+
it("executes the passed action and does not print anything when it is successful", async () => {
83+
const { action, printCommandHelpSuggestion } = setupTest();
84+
let result = await errors.beginCommand(action, printCommandHelpSuggestion);
85+
assert.isTrue(result, "beginCommand must return the result of the passed action.");
86+
87+
actionResult = false;
88+
result = await errors.beginCommand(action, printCommandHelpSuggestion);
89+
assert.isFalse(result, "beginCommand must return the result of the passed action.");
90+
assert.equal(logger.errorOutput, "");
91+
assert.equal(logger.output, "");
92+
assert.equal(logger.traceOutput, "");
93+
assertPrintCommandHelpSuggestionIsNotCalled();
94+
});
95+
96+
it("exits the process when the action fails", async () => {
97+
const { action, printCommandHelpSuggestion } = setupTest({ err: new Error(errMsg) });
98+
await errors.beginCommand(action, printCommandHelpSuggestion);
99+
assertProcessExited();
100+
assert.equal(logger.errorOutput, errMsg + '\n');
101+
assertPrintCommandHelpSuggestionIsNotCalled();
102+
});
103+
104+
describe("prints the stack trace of the error when", () => {
105+
const assertCallStack = () => {
106+
assert.isTrue(logger.errorOutput.indexOf(errMsg) !== -1, "The error output must contain the error message");
107+
assert.isTrue(logger.errorOutput.indexOf("at Generator.next") !== -1, "The error output must contain callstack");
108+
assert.isTrue(logger.errorOutput.indexOf(path.join("lib", "common")) !== -1, "The error output must contain path to lib/common, as this is the location of the file");
109+
};
110+
111+
it("printCallStack property is set to true", async () => {
112+
const { action, printCommandHelpSuggestion } = setupTest({ err: new Error(errMsg) });
113+
errors.printCallStack = true;
114+
await errors.beginCommand(action, printCommandHelpSuggestion);
115+
assertCallStack();
116+
assertProcessExited();
117+
assertPrintCommandHelpSuggestionIsNotCalled();
118+
});
119+
120+
it("loggerLevel is TRACE", async () => {
121+
const { action, printCommandHelpSuggestion } = setupTest({ err: new Error(errMsg) });
122+
logger.loggerLevel = LoggerLevel.TRACE;
123+
await errors.beginCommand(action, printCommandHelpSuggestion);
124+
assertCallStack();
125+
assertProcessExited();
126+
assertPrintCommandHelpSuggestionIsNotCalled();
127+
});
128+
129+
it("loggerLevel is DEBUG", async () => {
130+
const { action, printCommandHelpSuggestion } = setupTest({ err: new Error(errMsg) });
131+
logger.loggerLevel = LoggerLevel.DEBUG;
132+
await errors.beginCommand(action, printCommandHelpSuggestion);
133+
assertCallStack();
134+
assertProcessExited();
135+
assertPrintCommandHelpSuggestionIsNotCalled();
136+
});
137+
});
138+
139+
it("colorizes the error message to stderr when the terminal is interactive", async () => {
140+
const { action, printCommandHelpSuggestion } = setupTest({ err: new Error(errMsg) });
141+
isInteractive = true;
142+
await errors.beginCommand(action, printCommandHelpSuggestion);
143+
assert.equal(logger.errorOutput, `\x1B[31;1m${errMsg}\x1B[0m\n`);
144+
assertProcessExited();
145+
assertPrintCommandHelpSuggestionIsNotCalled();
146+
});
147+
148+
it("prints message on stdout instead of stderr when printOnStdout is set in the Error", async () => {
149+
const errObj: any = new Error(errMsg);
150+
errObj.printOnStdout = true;
151+
const { action, printCommandHelpSuggestion } = setupTest({ err: errObj });
152+
await errors.beginCommand(action, printCommandHelpSuggestion);
153+
assert.equal(logger.errorOutput, "");
154+
assert.equal(logger.output, `${errMsg}\n`);
155+
assertProcessExited();
156+
assertPrintCommandHelpSuggestionIsNotCalled();
157+
});
158+
159+
it("suggests how to show command help when error's suggestCommandHelp is set", async () => {
160+
const errObj: any = new Error(errMsg);
161+
errObj.suggestCommandHelp = true;
162+
const { action, printCommandHelpSuggestion } = setupTest({ err: errObj });
163+
await errors.beginCommand(action, printCommandHelpSuggestion);
164+
assert.equal(logger.errorOutput, `${errMsg}\n`);
165+
assertProcessExited();
166+
assert.isTrue(isPrintCommandHelpSuggestionExecuted, "printCommandHelpSuggestion should be called when the action fails with an error object for which suggestCommandHelp is true.");
167+
});
168+
169+
it("exits with passed exit code when the error has errorCode set to number", async () => {
170+
const errObj: any = new Error(errMsg);
171+
errObj.errorCode = 222;
172+
const { action, printCommandHelpSuggestion } = setupTest({ err: errObj });
173+
await errors.beginCommand(action, printCommandHelpSuggestion);
174+
assert.equal(logger.errorOutput, `${errMsg}\n`);
175+
assert.isTrue(isProcessExitCalled, "When the action fails, process.exit must be called.");
176+
assert.equal(processExitCode, errObj.errorCode, `When the action fails, process.exit must be called with ${errObj.errorCode}.`);
177+
});
178+
179+
it("exits with default exit code code when the error has errorCode set to string", async () => {
180+
const errObj: any = new Error(errMsg);
181+
errObj.errorCode = "222";
182+
const { action, printCommandHelpSuggestion } = setupTest({ err: errObj });
183+
await errors.beginCommand(action, printCommandHelpSuggestion);
184+
assert.equal(logger.errorOutput, `${errMsg}\n`);
185+
assert.isTrue(isProcessExitCalled, "When the action fails, process.exit must be called.");
186+
assert.equal(processExitCode, 127, "When the action fails, process.exit must be called with 127 by default.");
187+
});
188+
});
189+
});

lib/common/test/unit-tests/stubs.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ export class LockServiceStub implements ILockService {
1919
}
2020

2121
export class CommonLoggerStub implements ILogger {
22+
loggerLevel: string = "";
2223
initialize(opts?: ILoggerOptions): void { }
2324
initializeCliLogger(): void { }
24-
getLevel(): string { return undefined; }
25+
getLevel(): string { return this.loggerLevel; }
2526
fatal(...args: any[]): void { }
26-
error(...args: any[]): void { }
27+
error(...args: any[]): void {
28+
this.errorOutput += util.format.apply(null, args) + "\n";
29+
}
2730
warn(...args: any[]): void {
2831
this.output += util.format.apply(null, args) + "\n";
2932
}
@@ -37,6 +40,7 @@ export class CommonLoggerStub implements ILogger {
3740

3841
public output = "";
3942
public traceOutput = "";
43+
public errorOutput = "";
4044

4145
prepare(item: any): string {
4246
return "";
@@ -46,7 +50,6 @@ export class CommonLoggerStub implements ILogger {
4650
this.output += message;
4751
}
4852

49-
out(formatStr?: any, ...args: any[]): void { }
5053
write(...args: any[]): void { }
5154
printInfoMessageOnSameLine(message: string): void { }
5255
async printMsgWithTimeout(message: string, timeout: number): Promise<void> { }

test/stubs.ts

-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export class LoggerStub implements ILogger {
3636

3737
printMarkdown(message: string): void { }
3838

39-
out(formatStr?: any, ...args: any[]): void { }
4039
write(...args: any[]): void { }
4140
printInfoMessageOnSameLine(message: string): void { }
4241
async printMsgWithTimeout(message: string, timeout: number): Promise<void> { }

0 commit comments

Comments
 (0)