Skip to content
This repository was archived by the owner on Apr 13, 2020. It is now read-only.

Commit b6c4c22

Browse files
dennisseahNathanielRoseandrebriggs
authored
Handle exception - first implementation (#446)
* stage * fix tests * adding tests * extending of Error class * change err numbering to err human readable words * jsdoc fixes * code cleanup * Update generate.test.ts * change error status code to enum Co-authored-by: Nate <[email protected]> Co-authored-by: Andre Briggs <[email protected]>
1 parent cc8c111 commit b6c4c22

9 files changed

+370
-97
lines changed

src/commands/infra/generate.test.ts

+23-57
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,9 @@ describe("test gitClone function", () => {
197197

198198
describe("Validate remote git source", () => {
199199
test("Validating that a git source is cloned to .spk/templates", async () => {
200-
jest
201-
.spyOn(generate, "checkRemoteGitExist")
202-
.mockReturnValueOnce(Promise.resolve());
203-
jest.spyOn(generate, "gitFetchPull").mockReturnValueOnce(Promise.resolve());
204-
jest.spyOn(generate, "gitCheckout").mockReturnValueOnce(Promise.resolve());
200+
jest.spyOn(generate, "checkRemoteGitExist").mockResolvedValueOnce();
201+
jest.spyOn(generate, "gitFetchPull").mockResolvedValueOnce();
202+
jest.spyOn(generate, "gitCheckout").mockResolvedValueOnce();
205203

206204
const mockParentPath = "src/commands/infra/mocks/discovery-service";
207205
const mockProjectPath = "src/commands/infra/mocks/discovery-service/west";
@@ -272,13 +270,11 @@ describe("fetch execute function", () => {
272270
.spyOn(generate, "validateDefinition")
273271
.mockReturnValueOnce(DefinitionYAMLExistence.PARENT_ONLY);
274272
jest.spyOn(generate, "validateTemplateSources").mockReturnValueOnce({});
275-
jest
276-
.spyOn(generate, "validateRemoteSource")
277-
.mockReturnValueOnce(Promise.resolve());
273+
jest.spyOn(generate, "validateRemoteSource").mockResolvedValueOnce();
278274
jest
279275
.spyOn(infraCommon, "getSourceFolderNameFromURL")
280276
.mockImplementationOnce(() => {
281-
throw new Error("Fake");
277+
throw Error("Fake");
282278
});
283279
const exitFn = jest.fn();
284280
await execute(
@@ -296,9 +292,7 @@ describe("fetch execute function", () => {
296292
jest
297293
.spyOn(generate, "validateDefinition")
298294
.mockReturnValueOnce(DefinitionYAMLExistence.BOTH_EXIST);
299-
jest
300-
.spyOn(generate, "validateRemoteSource")
301-
.mockReturnValueOnce(Promise.resolve());
295+
jest.spyOn(generate, "validateRemoteSource").mockResolvedValueOnce();
302296
jest.spyOn(generate, "validateTemplateSources").mockReturnValueOnce({});
303297
jest
304298
.spyOn(generate, "generateConfig")
@@ -324,11 +318,9 @@ describe("test validateRemoteSource function", () => {
324318
jest
325319
.spyOn(infraCommon, "getSourceFolderNameFromURL")
326320
.mockReturnValueOnce("sourceFolder");
327-
jest
328-
.spyOn(generate, "checkRemoteGitExist")
329-
.mockReturnValueOnce(Promise.resolve());
330-
jest.spyOn(generate, "gitClone").mockReturnValueOnce(Promise.resolve());
331-
jest.spyOn(generate, "gitCheckout").mockReturnValueOnce(Promise.resolve());
321+
jest.spyOn(generate, "checkRemoteGitExist").mockResolvedValueOnce();
322+
jest.spyOn(generate, "gitClone").mockResolvedValueOnce();
323+
jest.spyOn(generate, "gitCheckout").mockResolvedValueOnce();
332324

333325
await validateRemoteSource({
334326
source: "source",
@@ -339,17 +331,11 @@ describe("test validateRemoteSource function", () => {
339331
jest
340332
.spyOn(infraCommon, "getSourceFolderNameFromURL")
341333
.mockReturnValueOnce("sourceFolder");
342-
jest
343-
.spyOn(generate, "checkRemoteGitExist")
344-
.mockReturnValueOnce(Promise.resolve());
334+
jest.spyOn(generate, "checkRemoteGitExist").mockResolvedValueOnce();
345335
jest
346336
.spyOn(generate, "gitClone")
347-
.mockReturnValueOnce(
348-
Promise.reject(new Error("refusing to merge unrelated histories"))
349-
);
350-
jest
351-
.spyOn(generate, "retryRemoteValidate")
352-
.mockReturnValueOnce(Promise.resolve());
337+
.mockRejectedValueOnce(Error("refusing to merge unrelated histories"));
338+
jest.spyOn(generate, "retryRemoteValidate").mockResolvedValueOnce();
353339

354340
await validateRemoteSource({
355341
source: "source",
@@ -360,15 +346,11 @@ describe("test validateRemoteSource function", () => {
360346
jest
361347
.spyOn(infraCommon, "getSourceFolderNameFromURL")
362348
.mockReturnValueOnce("sourceFolder");
363-
jest
364-
.spyOn(generate, "checkRemoteGitExist")
365-
.mockReturnValueOnce(Promise.resolve());
349+
jest.spyOn(generate, "checkRemoteGitExist").mockResolvedValueOnce();
366350
jest
367351
.spyOn(generate, "gitClone")
368-
.mockReturnValueOnce(Promise.reject(new Error("Authentication failed")));
369-
jest
370-
.spyOn(generate, "retryRemoteValidate")
371-
.mockReturnValueOnce(Promise.resolve());
352+
.mockRejectedValueOnce(Error("Authentication failed"));
353+
jest.spyOn(generate, "retryRemoteValidate").mockResolvedValueOnce();
372354

373355
await validateRemoteSource({
374356
source: "source",
@@ -379,15 +361,11 @@ describe("test validateRemoteSource function", () => {
379361
jest
380362
.spyOn(infraCommon, "getSourceFolderNameFromURL")
381363
.mockReturnValueOnce("sourceFolder");
382-
jest
383-
.spyOn(generate, "checkRemoteGitExist")
384-
.mockReturnValueOnce(Promise.resolve());
364+
jest.spyOn(generate, "checkRemoteGitExist").mockResolvedValueOnce();
385365
jest
386366
.spyOn(generate, "gitClone")
387-
.mockReturnValueOnce(Promise.reject(new Error("other error")));
388-
jest
389-
.spyOn(generate, "retryRemoteValidate")
390-
.mockReturnValueOnce(Promise.resolve());
367+
.mockRejectedValueOnce(Error("other error"));
368+
jest.spyOn(generate, "retryRemoteValidate").mockResolvedValueOnce();
391369

392370
try {
393371
await validateRemoteSource({
@@ -396,9 +374,7 @@ describe("test validateRemoteSource function", () => {
396374
});
397375
expect(true).toBe(false);
398376
} catch (err) {
399-
expect(err.message).toBe(
400-
"Failure error thrown during retry Error: Unable to determine error from supported retry cases other error"
401-
);
377+
expect(err.errorCode).toBe(1100);
402378
}
403379
});
404380
});
@@ -413,21 +389,11 @@ describe("test retryRemoteValidate function", () => {
413389
});
414390
it("negative test", async () => {
415391
jest.spyOn(fsExtra, "removeSync").mockReturnValueOnce();
416-
jest
417-
.spyOn(generate, "gitClone")
418-
.mockReturnValueOnce(Promise.reject(new Error("error")));
392+
jest.spyOn(generate, "gitClone").mockRejectedValueOnce(Error("error"));
419393

420-
try {
421-
await retryRemoteValidate(
422-
"source",
423-
"sourcePath",
424-
"safeLoggingUrl",
425-
"0.1"
426-
);
427-
expect(true).toBe(false);
428-
} catch (err) {
429-
expect(err).toBeDefined();
430-
}
394+
await expect(
395+
retryRemoteValidate("source", "sourcePath", "safeLoggingUrl", "0.1")
396+
).rejects.toThrow();
431397
});
432398
});
433399

src/commands/infra/generate.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
spkTemplatesPath,
2323
} from "./infra_common";
2424
import { copyTfTemplate } from "./scaffold";
25+
import { build as buildError } from "../../lib/errorBuilder";
26+
import { errorStatusCode } from "../../lib/errorStatusCode";
2527

2628
interface CommandOptions {
2729
project: string | undefined;
@@ -182,14 +184,16 @@ export const checkRemoteGitExist = async (
182184
): Promise<void> => {
183185
// Checking for git remote
184186
if (!fs.existsSync(sourcePath)) {
185-
throw new Error(`${sourcePath} does not exist`);
187+
throw buildError(errorStatusCode.GIT_OPS_ERR, {
188+
errorKey: "infra-git-source-no-exist",
189+
values: [sourcePath],
190+
});
186191
}
187192

188193
const result = await simpleGit(sourcePath).listRemote([source]);
189194
if (!result) {
190195
logger.error(result);
191-
throw new Error(`Unable to clone the source remote repository. \
192-
The remote repo may not exist or you do not have the rights to access it`);
196+
throw buildError(errorStatusCode.GIT_OPS_ERR, "infra-err-git-clone-failed");
193197
}
194198

195199
logger.info(`Remote source repo: ${safeLoggingUrl} exists.`);
@@ -287,13 +291,21 @@ export const validateRemoteSource = async (
287291
version
288292
);
289293
} else {
290-
throw new Error(
291-
`Unable to determine error from supported retry cases ${err.message}`
294+
throw buildError(
295+
errorStatusCode.GIT_OPS_ERR,
296+
"infra-err-validating-remote-git",
297+
err
292298
);
293299
}
294300
} catch (retryError) {
295-
throw new Error(`Failure error thrown during retry ${retryError}`);
301+
throw buildError(
302+
errorStatusCode.GIT_OPS_ERR,
303+
"infra-err-retry-validating-remote-git",
304+
err
305+
);
296306
}
307+
} else {
308+
throw err;
297309
}
298310
}
299311
};

src/commands/infra/scaffold.test.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,12 @@ describe("test validate function", () => {
170170
);
171171
expect(true).toBe(false);
172172
} catch (err) {
173-
expect(err.message).toBe("Value for source is missing.");
173+
expect(err.errorCode).toBe(1001);
174174
}
175175
});
176176
it("name, template, version is missing", () => {
177177
["name", "template", "version"].forEach((key) => {
178-
try {
178+
expect(() => {
179179
validateValues(
180180
{},
181181
{
@@ -185,12 +185,7 @@ describe("test validate function", () => {
185185
version: key === "version" ? "" : uuid(),
186186
}
187187
);
188-
expect(true).toBe(false);
189-
} catch (err) {
190-
expect(err.message).toBe(
191-
"Values for name, version and/or 'template are missing."
192-
);
193-
}
188+
}).toThrow();
194189
});
195190
});
196191
});

src/commands/infra/scaffold.ts

+38-24
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
21
import commander from "commander";
32
import fs from "fs";
43
import fsextra from "fs-extra";
@@ -19,6 +18,8 @@ import {
1918
VARIABLES_TF,
2019
} from "./infra_common";
2120
import decorator from "./scaffold.decorator.json";
21+
import { build as buildError, log as logError } from "../../lib/errorBuilder";
22+
import { errorStatusCode } from "../../lib/errorStatusCode";
2223

2324
export interface CommandOptions {
2425
name: string;
@@ -47,11 +48,17 @@ template repo and access token was not specified in spk-config.yml. Checking pas
4748

4849
if (!opts.source) {
4950
// since access_token and infra_repository are missing, we cannot construct source for them
50-
throw new Error("Value for source is missing.");
51+
throw buildError(
52+
errorStatusCode.VALIDATION_ERR,
53+
"infra-scaffold-cmd-src-missing"
54+
);
5155
}
5256
}
5357
if (!opts.name || !opts.version || !opts.template) {
54-
throw new Error("Values for name, version and/or 'template are missing.");
58+
throw buildError(
59+
errorStatusCode.VALIDATION_ERR,
60+
"infra-scaffold-cmd-values-missing"
61+
);
5562
}
5663
logger.info(`All required options are configured via command line for \
5764
scaffolding, expecting public remote repository for terraform templates \
@@ -60,6 +67,8 @@ or PAT embedded in source URL.`);
6067

6168
// Construct the source based on the the passed configurations of spk-config.yaml
6269
export const constructSource = (config: ConfigYaml): string => {
70+
// config.azure_devops exists because validateValues function checks it
71+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
6372
const devops = config.azure_devops!;
6473
const source = `https://spk:${devops.access_token}@${devops.infra_repository}`;
6574
logger.info(
@@ -93,10 +102,14 @@ export const copyTfTemplate = async (
93102
}
94103
logger.info(`Terraform template files copied from ${templatePath}`);
95104
} catch (err) {
96-
logger.error(
97-
`Unable to find Terraform environment. Please check template path.`
105+
throw buildError(
106+
errorStatusCode.ENV_SETTING_ERR,
107+
{
108+
errorKey: "infra-err-locate-tf-env",
109+
values: [templatePath],
110+
},
111+
err
98112
);
99-
throw err;
100113
}
101114
};
102115

@@ -106,18 +119,15 @@ export const copyTfTemplate = async (
106119
* @param templatePath Path to the variables.tf file
107120
*/
108121
export const validateVariablesTf = (templatePath: string): void => {
109-
try {
110-
if (!fs.existsSync(templatePath)) {
111-
throw new Error(
112-
`Provided Terraform ${VARIABLES_TF} path is invalid or cannot be found: ${templatePath}`
113-
);
114-
}
115-
logger.info(
116-
`Terraform ${VARIABLES_TF} file found. Attempting to generate ${DEFINITION_YAML} file.`
117-
);
118-
} catch (_) {
119-
throw new Error(`Unable to validate Terraform ${VARIABLES_TF}.`);
122+
if (!fs.existsSync(templatePath)) {
123+
throw buildError(errorStatusCode.ENV_SETTING_ERR, {
124+
errorKey: "infra-err-tf-path-not-found",
125+
values: [VARIABLES_TF, templatePath],
126+
});
120127
}
128+
logger.info(
129+
`Terraform ${VARIABLES_TF} file found. Attempting to generate ${DEFINITION_YAML} file.`
130+
);
121131
};
122132

123133
/**
@@ -281,15 +291,18 @@ export const scaffold = (values: CommandOptions): void => {
281291
});
282292
fs.writeFileSync(confPath, definitionYaml, "utf8");
283293
} else {
284-
logger.error(`Unable to generate cluster definition.`);
294+
throw Error(`Unable to generate cluster definition.`);
285295
}
286296
} else {
287-
logger.error(`Unable to read variable file: ${tfVariableFile}.`);
297+
throw Error(`Unable to read variable file: ${tfVariableFile}.`);
288298
}
289299
}
290300
} catch (err) {
291-
logger.warn("Unable to create scaffold");
292-
throw err;
301+
throw buildError(
302+
errorStatusCode.EXE_FLOW_ERR,
303+
"infra-err-create-scaffold",
304+
err
305+
);
293306
}
294307
};
295308

@@ -308,7 +321,7 @@ export const removeTemplateFiles = (envPath: string): void => {
308321
fs.unlinkSync(path.join(envPath, f));
309322
});
310323
} catch (e) {
311-
logger.error(`cannot read ${envPath}`);
324+
logger.warn(`cannot read ${envPath}`);
312325
// TOFIX: I guess we are ok with files not removed.
313326
}
314327
};
@@ -342,8 +355,9 @@ export const execute = async (
342355
removeTemplateFiles(opts.name);
343356
await exitFn(0);
344357
} catch (err) {
345-
logger.error("Error occurred while generating scaffold");
346-
logger.error(err);
358+
logError(
359+
buildError(errorStatusCode.CMD_EXE_ERR, "infra-scaffold-cmd-failed", err)
360+
);
347361
await exitFn(1);
348362
}
349363
};

0 commit comments

Comments
 (0)