Skip to content

Commit 2493c9d

Browse files
authored
refactor: use asserts instead of process.exit (#1290)
1 parent 2169c1d commit 2493c9d

File tree

7 files changed

+188
-28
lines changed

7 files changed

+188
-28
lines changed

src/parser.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ export class Parser {
8080
if (parser.argv.jsonSchemaValidation) {
8181
const time = process.hrtime();
8282
Validator.jsonSchemaValidation({
83-
writeStreams,
8483
pathToExpandedGitLabCi,
8584
gitLabCiConfig: parser.gitlabData,
8685
});

src/validator.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import assert from "assert";
44
import chalk from "chalk";
55
import schema from "./schema";
66
import {betterAjvErrors} from "./schema-error";
7-
import {WriteStreams} from "./write-streams";
87
import terminalLink from "terminal-link";
98

109
const MAX_ERRORS = 5;
1110

1211
export class Validator {
13-
static jsonSchemaValidation ({writeStreams, pathToExpandedGitLabCi, gitLabCiConfig}: {
14-
writeStreams: WriteStreams;
12+
static jsonSchemaValidation ({pathToExpandedGitLabCi, gitLabCiConfig}: {
1513
pathToExpandedGitLabCi: string;
1614
gitLabCiConfig: object;
1715
}) {
@@ -26,24 +24,28 @@ export class Validator {
2624
const validate = ajv.compile(schema);
2725
const valid = validate(gitLabCiConfig);
2826
if (!valid) {
29-
writeStreams.stderr(chalk`Invalid .gitlab-ci.yml configuration!\n`);
30-
3127
const betterErrors = betterAjvErrors({
3228
data: gitLabCiConfig,
3329
errors: validate.errors,
3430
});
3531

36-
const errors = betterErrors.map((e => (chalk`\t• {redBright ${e.message}} at {blueBright ${e.path}}`)));
37-
writeStreams.stderr(errors.splice(0, MAX_ERRORS).join("\n"));
38-
if (errors.length > MAX_ERRORS) {
39-
writeStreams.stderr(`\t... and ${errors.length - MAX_ERRORS} more`);
32+
let e: string = "";
33+
for (let i = 0, len = betterErrors.length; i < len; i++) {
34+
if (i + 1 > MAX_ERRORS) {
35+
e += `\t... and ${len - MAX_ERRORS} more`;
36+
break;
37+
}
38+
e += chalk`\t• {redBright ${betterErrors[i].message}} at {blueBright ${betterErrors[i].path}}\n`;
4039
}
4140

42-
writeStreams.stderr(chalk`\n\nFor further troubleshooting, consider either of the following:
41+
assert(valid, chalk`
42+
{reset Invalid .gitlab-ci.yml configuration!
43+
${e.trimEnd()}
44+
45+
For further troubleshooting, consider either of the following:
4346
\t• Copy the content of {blueBright ${terminalLink(".gitlab-ci-local/expanded-gitlab-ci.yml", pathToExpandedGitLabCi)}} to the ${terminalLink("pipeline editor", "https://docs.gitlab.com/ee/ci/pipeline_editor/")} to debug it
44-
\t• Use --json-schema-validation=false to disable schema validation (not recommended)
47+
\t• Use --json-schema-validation=false to disable schema validation (not recommended)}
4548
`);
46-
process.exit(1);
4749
}
4850
}
4951

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
error1:
3+
variables:
4+
true
5+
script:
6+
- echo hello
7+
error2:
8+
variables:
9+
true
10+
script:
11+
- echo hello
12+
error3:
13+
variables:
14+
true
15+
script:
16+
- echo hello
17+
error4:
18+
variables:
19+
true
20+
script:
21+
- echo hello
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
error1:
3+
variables:
4+
true
5+
script:
6+
- echo hello
7+
error2:
8+
variables:
9+
true
10+
script:
11+
- echo hello
12+
error3:
13+
variables:
14+
true
15+
script:
16+
- echo hello
17+
error4:
18+
variables:
19+
true
20+
script:
21+
- echo hello
22+
error5:
23+
variables:
24+
true
25+
script:
26+
- echo hello
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
error1:
3+
variables:
4+
true
5+
script:
6+
- echo hello
7+
error2:
8+
variables:
9+
true
10+
script:
11+
- echo hello
12+
error3:
13+
variables:
14+
true
15+
script:
16+
- echo hello
17+
error4:
18+
variables:
19+
true
20+
script:
21+
- echo hello
22+
error5:
23+
variables:
24+
true
25+
script:
26+
- echo hello
27+
error6:
28+
variables:
29+
true
30+
script:
31+
- echo hello

tests/test-cases/schema-validation/integration.schema-validation.test.ts

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,29 @@ import {WriteStreamsMock} from "../../../src/write-streams";
22
import {handler} from "../../../src/handler";
33
import {initSpawnSpy} from "../../mocks/utils.mock";
44
import {WhenStatics} from "../../mocks/when-statics";
5+
import assert, {AssertionError} from "assert";
6+
import {stripAnsi} from "../../utils";
7+
58

69
beforeAll(() => {
710
initSpawnSpy(WhenStatics.all);
811
});
912

1013
test("schema validation <test-job>", async () => {
1114
const writeStreams = new WriteStreamsMock();
12-
const mockExit = jest.spyOn(process, "exit").mockImplementation(() => {
13-
// Do nothing
14-
return undefined as never;
15-
});
16-
17-
await handler({
18-
cwd: "tests/test-cases/schema-validation",
19-
}, writeStreams);
20-
expect(writeStreams.stderrLines.join("\n")).toContain("Invalid .gitlab-ci.yml configuration!");
21-
expect(writeStreams.stderrLines.join("\n")).toContain("property 'script' must not have fewer than 1 characters");
22-
expect(writeStreams.stderrLines.join("\n")).toContain("'when' property must be one of [on_success, on_failure, always, never, manual, delayed] (found manual2)");
23-
expect(writeStreams.stderrLines.join("\n")).toContain("'junit' property type must be string");
24-
expect(writeStreams.stderrLines.join("\n")).toContain("'data' property is not expected to be here");
25-
26-
expect(mockExit).toHaveBeenCalledWith(1);
15+
try {
16+
await handler({
17+
cwd: "tests/test-cases/schema-validation",
18+
}, writeStreams);
19+
expect(true).toBe(false);
20+
} catch (e: any) {
21+
assert(e instanceof AssertionError, "e is not instanceof AssertionError");
22+
expect(e.message).toContain("Invalid .gitlab-ci.yml configuration!");
23+
expect(e.message).toContain("property 'script' must not have fewer than 1 characters");
24+
expect(e.message).toContain("'when' property must be one of [on_success, on_failure, always, never, manual, delayed] (found manual2)");
25+
expect(e.message).toContain("'junit' property type must be string");
26+
expect(e.message).toContain("'data' property is not expected to be here");
27+
}
2728
});
2829

2930
test("schema validation - default <test-job>", async () => {
@@ -57,3 +58,73 @@ my-job:
5758
expect(writeStreams.stdoutLines[0]).toEqual(expected);
5859
expect(writeStreams.stderrLines.join("\n")).toContain("my-job.artifacts is null, ignoring.");
5960
});
61+
62+
test("schema validation 4 errors", async () => {
63+
const writeStreams = new WriteStreamsMock();
64+
try {
65+
await handler({
66+
file: ".gitlab-ci-4-errors.yml",
67+
cwd: "tests/test-cases/schema-validation",
68+
69+
}, writeStreams);
70+
expect(true).toBe(false);
71+
} catch (e: any) {
72+
const expected = `
73+
Invalid .gitlab-ci.yml configuration!
74+
\t• 'variables' property type must be object at error1.variables
75+
\t• 'variables' property type must be object at error2.variables
76+
\t• 'variables' property type must be object at error3.variables
77+
\t• 'variables' property type must be object at error4.variables
78+
79+
`;
80+
assert(e instanceof AssertionError, "e is not instanceof AssertionError");
81+
expect(stripAnsi(e.message)).toContain(expected);
82+
}
83+
});
84+
85+
test("schema validation 5 errors", async () => {
86+
const writeStreams = new WriteStreamsMock();
87+
try {
88+
await handler({
89+
file: ".gitlab-ci-5-errors.yml",
90+
cwd: "tests/test-cases/schema-validation",
91+
92+
}, writeStreams);
93+
expect(true).toBe(false);
94+
} catch (e: any) {
95+
const expected = `Invalid .gitlab-ci.yml configuration!
96+
\t• 'variables' property type must be object at error1.variables
97+
\t• 'variables' property type must be object at error2.variables
98+
\t• 'variables' property type must be object at error3.variables
99+
\t• 'variables' property type must be object at error4.variables
100+
\t• 'variables' property type must be object at error5.variables
101+
102+
For further troubleshooting, consider either of the following:`;
103+
assert(e instanceof AssertionError, "e is not instanceof AssertionError");
104+
expect(stripAnsi(e.message)).toContain(expected);
105+
}
106+
});
107+
108+
test("schema validation 6 errors", async () => {
109+
const writeStreams = new WriteStreamsMock();
110+
try {
111+
await handler({
112+
file: ".gitlab-ci-6-errors.yml",
113+
cwd: "tests/test-cases/schema-validation",
114+
115+
}, writeStreams);
116+
expect(true).toBe(false);
117+
} catch (e: any) {
118+
const expected = `Invalid .gitlab-ci.yml configuration!
119+
\t• 'variables' property type must be object at error1.variables
120+
\t• 'variables' property type must be object at error2.variables
121+
\t• 'variables' property type must be object at error3.variables
122+
\t• 'variables' property type must be object at error4.variables
123+
\t• 'variables' property type must be object at error5.variables
124+
\t... and 1 more
125+
126+
`;
127+
assert(e instanceof AssertionError, "e is not instanceof AssertionError");
128+
expect(stripAnsi(e.message)).toContain(expected);
129+
}
130+
});

tests/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,13 @@ export function isSshDirFound () {
88
return false;
99
}
1010
}
11+
12+
13+
export function stripAnsi (str: string) {
14+
// Copied from https://github.com/chalk/ansi-regex/blob/main/index.js#L1-L8
15+
const pattern = [
16+
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
17+
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
18+
].join("|");
19+
return str.replace(new RegExp(pattern, "g"), "");
20+
}

0 commit comments

Comments
 (0)