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

Commit 0e0ba83

Browse files
authored
[BUG-FIX] validate project and org names (#442)
* [BUG-FIX] validate project and org names * add validation for project create pipeline command
1 parent 511399c commit 0e0ba83

12 files changed

+377
-112
lines changed

src/commands/hld/pipeline.test.ts

+57-16
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,54 @@ describe("test emptyStringIfUndefined function", () => {
7373
});
7474
});
7575

76+
const orgNameTest = (hasVal: boolean): void => {
77+
const data = {
78+
buildScriptUrl: "",
79+
devopsProject: "project",
80+
hldName: "",
81+
hldUrl: "https://dev.azure.com/mocked/fabrikam/_git/hld",
82+
manifestUrl: "https://dev.azure.com/mocked/fabrikam/_git/materialized",
83+
orgName: hasVal ? "org Name" : "",
84+
personalAccessToken: "",
85+
pipelineName: "",
86+
yamlFileBranch: "",
87+
};
88+
89+
if (hasVal) {
90+
expect(() => populateValues(data)).toThrow(
91+
"Organization names must start with a letter or number, followed by letters, numbers or hyphens, and must end with a letter or number."
92+
);
93+
} else {
94+
expect(() => populateValues(data)).toThrow(
95+
"value for -o, --org-name <organization-name> is missing"
96+
);
97+
}
98+
};
99+
100+
const projectNameTest = (hasVal: boolean): void => {
101+
const data = {
102+
buildScriptUrl: "",
103+
devopsProject: hasVal ? "project\\abc" : "",
104+
hldName: "",
105+
hldUrl: "https://dev.azure.com/mocked/fabrikam/_git/hld",
106+
manifestUrl: "https://dev.azure.com/mocked/fabrikam/_git/materialized",
107+
orgName: "orgName",
108+
personalAccessToken: "",
109+
pipelineName: "",
110+
yamlFileBranch: "",
111+
};
112+
113+
if (hasVal) {
114+
expect(() => populateValues(data)).toThrow(
115+
"Project name can't contain special characters, such as / : \\ ~ & % ; @ ' \" ? < > | # $ * } { , + = [ ]"
116+
);
117+
} else {
118+
expect(() => populateValues(data)).toThrow(
119+
"value for -d, --devops-project <devops-project> is missing"
120+
);
121+
}
122+
};
123+
76124
describe("test populateValues function", () => {
77125
it("with all values in command opts", () => {
78126
jest.spyOn(config, "Config").mockImplementationOnce(
@@ -125,31 +173,24 @@ describe("test populateValues function", () => {
125173
expect(() =>
126174
populateValues({
127175
buildScriptUrl: "",
128-
devopsProject: "",
176+
devopsProject: "project",
129177
hldName: "",
130178
hldUrl: "https://github.com/fabrikam/hld",
131179
manifestUrl: "https://github.com/fabrikam/materialized",
132-
orgName: "",
180+
orgName: "orgName",
133181
personalAccessToken: "",
134182
pipelineName: "",
135183
yamlFileBranch: "",
136184
})
137185
).toThrow(`GitHub repos are not supported`);
138186
});
139-
it("negative tests: github repos not supported", () => {
140-
expect(() =>
141-
populateValues({
142-
buildScriptUrl: "",
143-
devopsProject: "",
144-
hldName: "",
145-
hldUrl: "https://github.com/fabrikam/hld",
146-
manifestUrl: "https://github.com/fabrikam/materialized",
147-
orgName: "",
148-
personalAccessToken: "",
149-
pipelineName: "",
150-
yamlFileBranch: "",
151-
})
152-
).toThrow(`GitHub repos are not supported`);
187+
it("negative tests: missing and invalid org name", () => {
188+
orgNameTest(false);
189+
orgNameTest(true);
190+
});
191+
it("negative tests: missing and invalid project name", () => {
192+
projectNameTest(false);
193+
projectNameTest(true);
153194
});
154195
});
155196

src/commands/hld/pipeline.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import {
77
import commander from "commander";
88
import { Config } from "../../config";
99
import { validateRepository } from "../../lib/azdoClient";
10-
import { build as buildCmd, exit as exitCmd } from "../../lib/commandBuilder";
10+
import {
11+
build as buildCmd,
12+
exit as exitCmd,
13+
getOption as getCmdOption,
14+
} from "../../lib/commandBuilder";
1115
import {
1216
BUILD_SCRIPT_URL,
1317
RENDER_HLD_PIPELINE_FILENAME,
@@ -23,6 +27,11 @@ import {
2327
} from "../../lib/pipelines/pipelines";
2428
import { logger } from "../../logger";
2529
import decorator from "./pipeline.decorator.json";
30+
import {
31+
hasValue,
32+
validateOrgNameThrowable,
33+
validateProjectNameThrowable,
34+
} from "../../lib/validator";
2635

2736
export interface CommandOptions {
2837
pipelineName: string;
@@ -65,13 +74,31 @@ export const populateValues = (opts: CommandOptions): CommandOptions => {
6574

6675
opts.orgName = opts.orgName || emptyStringIfUndefined(azure_devops?.org);
6776

77+
if (hasValue(opts.orgName)) {
78+
validateOrgNameThrowable(opts.orgName);
79+
} else {
80+
throw Error(
81+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
82+
`value for ${getCmdOption(decorator, "org-name")!.arg} is missing`
83+
);
84+
}
85+
6886
opts.personalAccessToken =
6987
opts.personalAccessToken ||
7088
emptyStringIfUndefined(azure_devops?.access_token);
7189

7290
opts.devopsProject =
7391
opts.devopsProject || emptyStringIfUndefined(azure_devops?.project);
7492

93+
if (hasValue(opts.devopsProject)) {
94+
validateProjectNameThrowable(opts.devopsProject);
95+
} else {
96+
throw Error(
97+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
98+
`value for ${getCmdOption(decorator, "devops-project")!.arg} is missing`
99+
);
100+
}
101+
75102
opts.pipelineName =
76103
opts.hldName + "-to-" + getRepositoryName(opts.manifestUrl);
77104

src/commands/project/create-variable-group.test.ts

+61-13
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import mockFs from "mock-fs";
55
import path from "path";
66
import uuid from "uuid/v4";
77
import { readYaml, write } from "../../config";
8+
import * as config from "../../config";
89
import {
910
create as createBedrockYaml,
1011
isExists as isBedrockFileExists,
1112
read as readBedrockFile,
1213
} from "../../lib/bedrockYaml";
14+
import * as commandBuilder from "../../lib/commandBuilder";
1315
import {
1416
PROJECT_PIPELINE_FILENAME,
1517
VERSION_MESSAGE,
@@ -32,7 +34,9 @@ import {
3234
execute,
3335
setVariableGroupInBedrockFile,
3436
updateLifeCyclePipeline,
37+
validateValues,
3538
} from "./create-variable-group";
39+
import * as createVariableGrp from "./create-variable-group";
3640
import * as fileutils from "../../lib/fileutils";
3741

3842
beforeAll(() => {
@@ -57,11 +61,45 @@ const hldRepoUrl = uuid();
5761
const servicePrincipalId = uuid();
5862
const servicePrincipalPassword: string = uuid();
5963
const tenant = uuid();
60-
const orgName = uuid();
61-
const devopsProject = uuid();
64+
const orgName = "orgName";
65+
const devopsProject = "projectName";
6266
const personalAccessToken = uuid();
6367

6468
describe("test execute function", () => {
69+
it("positive test", async () => {
70+
const exitFn = jest.fn();
71+
jest.spyOn(createVariableGrp, "checkDependencies").mockReturnValueOnce();
72+
jest.spyOn(config, "Config").mockReturnValueOnce({});
73+
jest
74+
.spyOn(commandBuilder, "validateForRequiredValues")
75+
.mockReturnValueOnce([]);
76+
jest.spyOn(createVariableGrp, "create").mockResolvedValueOnce({
77+
name: "groupName",
78+
});
79+
jest
80+
.spyOn(createVariableGrp, "setVariableGroupInBedrockFile")
81+
.mockReturnValueOnce();
82+
jest
83+
.spyOn(createVariableGrp, "updateLifeCyclePipeline")
84+
.mockReturnValueOnce();
85+
86+
await execute(
87+
"groupName",
88+
{
89+
devopsProject,
90+
hldRepoUrl,
91+
orgName,
92+
personalAccessToken,
93+
registryName,
94+
servicePrincipalId,
95+
servicePrincipalPassword,
96+
tenant,
97+
},
98+
exitFn
99+
);
100+
expect(exitFn).toBeCalledTimes(1);
101+
expect(exitFn.mock.calls).toEqual([[0]]);
102+
});
65103
it("missing variable name", async () => {
66104
const exitFn = jest.fn();
67105
await execute(
@@ -79,6 +117,7 @@ describe("test execute function", () => {
79117
exitFn
80118
);
81119
expect(exitFn).toBeCalledTimes(1);
120+
expect(exitFn.mock.calls).toEqual([[1]]);
82121
});
83122
it("missing registry name", async () => {
84123
const exitFn = jest.fn();
@@ -97,25 +136,18 @@ describe("test execute function", () => {
97136
exitFn
98137
);
99138
expect(exitFn).toBeCalledTimes(1);
139+
expect(exitFn.mock.calls).toEqual([[1]]);
100140
});
101141
});
102142

103143
describe("create", () => {
104-
test("Should fail with empty variable group arguments", async () => {
144+
it("Should fail with empty variable group arguments", async () => {
105145
const accessOpts: AzureDevOpsOpts = {
106146
orgName,
107147
personalAccessToken,
108148
project: devopsProject,
109149
};
110-
111-
let invalidDataError: Error | undefined;
112-
try {
113-
logger.info("calling create");
114-
await create("", "", "", "", "", "", accessOpts);
115-
} catch (err) {
116-
invalidDataError = err;
117-
}
118-
expect(invalidDataError).toBeDefined();
150+
await expect(create("", "", "", "", "", "", accessOpts)).rejects.toThrow();
119151
});
120152

121153
test("Should pass with variable group arguments", async () => {
@@ -336,7 +368,7 @@ describe("updateLifeCyclePipeline", () => {
336368

337369
fs.writeFileSync(hldFilePath, asYaml);
338370

339-
await updateLifeCyclePipeline(randomTmpDir);
371+
updateLifeCyclePipeline(randomTmpDir);
340372

341373
const hldLifeCycleYaml = readYaml<AzurePipelinesYaml>(hldFilePath);
342374
logger.info(`filejson: ${JSON.stringify(hldLifeCycleYaml)}`);
@@ -345,3 +377,19 @@ describe("updateLifeCyclePipeline", () => {
345377
});
346378
});
347379
});
380+
381+
describe("test validateValues function", () => {
382+
it("valid org and project name", () => {
383+
validateValues(devopsProject, orgName);
384+
});
385+
it("invalid project name", () => {
386+
expect(() => {
387+
validateValues("project\\abc", orgName);
388+
}).toThrow();
389+
});
390+
it("invalid org name", () => {
391+
expect(() => {
392+
validateValues(devopsProject, "org name");
393+
}).toThrow();
394+
});
395+
});

src/commands/project/create-variable-group.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import {
1717
} from "../../lib/constants";
1818
import { AzureDevOpsOpts } from "../../lib/git";
1919
import { addVariableGroup } from "../../lib/pipelines/variableGroup";
20-
import { hasValue } from "../../lib/validator";
20+
import {
21+
hasValue,
22+
validateProjectNameThrowable,
23+
validateOrgNameThrowable,
24+
} from "../../lib/validator";
2125
import { logger } from "../../logger";
2226
import {
2327
AzurePipelinesYaml,
@@ -46,6 +50,11 @@ export const checkDependencies = (projectPath: string): void => {
4650
}
4751
};
4852

53+
export const validateValues = (projectName: string, orgName: string): void => {
54+
validateProjectNameThrowable(projectName);
55+
validateOrgNameThrowable(orgName);
56+
};
57+
4958
/**
5059
* Executes the command.
5160
*
@@ -105,6 +114,11 @@ export const execute = async (
105114
return;
106115
}
107116

117+
// validateForRequiredValues assure that devopsProject
118+
// and orgName are not empty string
119+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
120+
validateValues(devopsProject!, orgName!);
121+
108122
const variableGroup = await create(
109123
variableGroupName,
110124
registryName,
@@ -176,6 +190,7 @@ export const create = (
176190
logger.info(
177191
`Creating Variable Group from group definition '${variableGroupName}'`
178192
);
193+
179194
const vars: VariableGroupDataVariable = {
180195
ACR_NAME: {
181196
value: registryName,

0 commit comments

Comments
 (0)