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

Commit a4807a7

Browse files
authored
Fix issue 991: Add more tests to azure/keyvault.ts (#300)
* Fix issue 991: Add more tests to azure/keyvault.ts * fix lint issue
1 parent 4c9066d commit a4807a7

File tree

3 files changed

+143
-77
lines changed

3 files changed

+143
-77
lines changed

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

+5-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import fs from "fs";
22
import yaml from "js-yaml";
33
import mockFs from "mock-fs";
4-
import os from "os";
54
import path from "path";
65
import uuid from "uuid/v4";
76
import { readYaml, write } from "../../config";
@@ -12,6 +11,7 @@ import {
1211
} from "../../lib/bedrockYaml";
1312
import { PROJECT_PIPELINE_FILENAME } from "../../lib/constants";
1413
import { IAzureDevOpsOpts } from "../../lib/git";
14+
import { createTempDir } from "../../lib/ioUtil";
1515
import * as pipelineVariableGroup from "../../lib/pipelines/variableGroup";
1616
import {
1717
disableVerboseLogging,
@@ -189,9 +189,7 @@ describe("setVariableGroupInBedrockFile", () => {
189189

190190
test("Should fail adding a variable group name when no bedrock file exists", async () => {
191191
// Create random directory to initialize
192-
const randomTmpDir = path.join(os.tmpdir(), uuid());
193-
fs.mkdirSync(randomTmpDir);
194-
192+
const randomTmpDir = createTempDir();
195193
let noFileError: Error | undefined;
196194

197195
try {
@@ -265,9 +263,7 @@ describe("updateLifeCyclePipeline", () => {
265263

266264
test("Should fail adding a variable group name when no pipeline yaml file exists", async () => {
267265
// Create random directory to initialize
268-
const randomTmpDir = path.join(os.tmpdir(), uuid());
269-
fs.mkdirSync(randomTmpDir);
270-
266+
const randomTmpDir = createTempDir();
271267
let noFileError: Error | undefined;
272268

273269
try {
@@ -280,8 +276,7 @@ describe("updateLifeCyclePipeline", () => {
280276

281277
test("Should pass adding variable groups when bedrock file exists with empty variableGroups", async () => {
282278
// Create random directory to initialize
283-
const randomTmpDir = path.join(os.tmpdir(), uuid());
284-
fs.mkdirSync(randomTmpDir);
279+
const randomTmpDir = createTempDir();
285280
const writeSpy = jest.spyOn(fs, "writeFileSync");
286281

287282
const defaultBedrockFileObject = createTestBedrockYaml(
@@ -311,8 +306,7 @@ describe("updateLifeCyclePipeline", () => {
311306

312307
test("Should pass adding variable groups when bedrock file exists with one variableGroup", async () => {
313308
// Create random directory to initialize
314-
const randomTmpDir = path.join(os.tmpdir(), uuid());
315-
fs.mkdirSync(randomTmpDir);
309+
const randomTmpDir = createTempDir();
316310
logger.info(`random dir: ${randomTmpDir})`);
317311

318312
const defaultBedrockFileObject = createTestBedrockYaml(

src/lib/azure/keyvault.test.ts

+105-20
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,53 @@
1+
import {
2+
GetSecretOptions,
3+
KeyVaultSecret,
4+
SecretClient,
5+
SetSecretOptions
6+
} from "@azure/keyvault-secrets";
17
import uuid from "uuid/v4";
28
import {
39
disableVerboseLogging,
410
enableVerboseLogging,
511
logger
612
} from "../../logger";
713
import { getSecret, setSecret } from "./keyvault";
14+
import * as keyvault from "./keyvault";
815

916
const keyVaultName = uuid();
10-
const secretName = uuid();
17+
const mockedName = uuid();
1118
const secretValue = uuid();
1219

20+
jest.spyOn(keyvault, "getClient").mockReturnValue(
21+
Promise.resolve({
22+
getSecret: async (
23+
secretName: string,
24+
options?: GetSecretOptions
25+
): Promise<KeyVaultSecret> => {
26+
return {
27+
name: "test",
28+
properties: {
29+
name: "test",
30+
vaultUrl: "http://test.com"
31+
},
32+
value: "secretValue"
33+
};
34+
},
35+
setSecret: async (
36+
secretName: string,
37+
value: string,
38+
options?: SetSecretOptions
39+
): Promise<KeyVaultSecret> => {
40+
return {
41+
name: "test",
42+
properties: {
43+
name: "test",
44+
vaultUrl: "http://test.com"
45+
}
46+
};
47+
}
48+
} as SecretClient)
49+
);
50+
1351
beforeAll(() => {
1452
enableVerboseLogging();
1553
});
@@ -20,41 +58,88 @@ afterAll(() => {
2058

2159
describe("set secret", () => {
2260
test("should fail when all arguments are not specified", async () => {
23-
let error: Error | undefined;
61+
await expect(setSecret("", "", "")).rejects.toThrow();
62+
});
63+
test("should create storage account", async () => {
2464
try {
25-
await setSecret("", "", "");
26-
} catch (err) {
27-
error = err;
65+
await setSecret(keyVaultName, mockedName, secretValue);
66+
} catch (_) {
67+
expect(true).toBe(false);
2868
}
29-
expect(error).toBeDefined();
3069
});
31-
32-
test("should create storage account", async () => {
70+
test("negative test", async () => {
71+
jest.spyOn(keyvault, "getClient").mockReturnValueOnce(
72+
Promise.resolve({
73+
setSecret: async (
74+
secretName: string,
75+
value: string,
76+
options?: SetSecretOptions
77+
): Promise<KeyVaultSecret> => {
78+
throw new Error("fake error");
79+
}
80+
} as SecretClient)
81+
);
3382
try {
34-
await setSecret(keyVaultName, secretName, secretValue);
35-
} catch (err) {
36-
logger.error(err);
83+
await setSecret(keyVaultName, mockedName, secretValue);
84+
expect(true).toBe(false);
85+
} catch (e) {
86+
expect(e).toBeDefined();
3787
}
3888
});
3989
});
4090

4191
describe("get secret", () => {
4292
test("should fail getting storage account key when arguments are not specified", async () => {
43-
let error: Error | undefined;
93+
await expect(getSecret("", "")).rejects.toThrow();
94+
});
95+
it("should get storage account key", async () => {
96+
try {
97+
const val = await getSecret(keyVaultName, mockedName);
98+
expect(val).toBe("secretValue");
99+
} catch (err) {
100+
expect(true).toBe(false);
101+
}
102+
});
103+
it("negative test: secret not found", async () => {
104+
jest.spyOn(keyvault, "getClient").mockReturnValueOnce(
105+
Promise.resolve({
106+
getSecret: async (
107+
secretName: string,
108+
options?: GetSecretOptions
109+
): Promise<KeyVaultSecret> => {
110+
throw {
111+
code: "SecretNotFound",
112+
statusCode: 404
113+
};
114+
}
115+
} as SecretClient)
116+
);
44117
try {
45-
await getSecret("", "");
118+
const val = await getSecret(keyVaultName, mockedName);
119+
expect(val).toBe(undefined);
46120
} catch (err) {
47-
error = err;
121+
expect(true).toBe(false);
48122
}
49-
expect(error).toBeDefined();
50123
});
51-
test("should get storage account key", async () => {
52-
let latestValue: string | undefined;
124+
it("negative test: other errors", async () => {
125+
jest.spyOn(keyvault, "getClient").mockReturnValueOnce(
126+
Promise.resolve({
127+
getSecret: async (
128+
secretName: string,
129+
options?: GetSecretOptions
130+
): Promise<KeyVaultSecret> => {
131+
throw {
132+
code: "something else",
133+
statusCode: 400
134+
};
135+
}
136+
} as SecretClient)
137+
);
53138
try {
54-
latestValue = await getSecret(keyVaultName, secretName);
139+
const val = await getSecret(keyVaultName, mockedName);
140+
expect(true).toBe(false);
55141
} catch (err) {
56-
logger.error(err);
142+
expect(err).toBeDefined();
57143
}
58-
expect(latestValue).toBeUndefined();
59144
});
60145
});

src/lib/azure/keyvault.ts

+33-46
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,58 @@ import { logger } from "../../logger";
33
import { IAzureAccessOpts } from "../../types";
44
import { getCredentials } from "./azurecredentials";
55

6-
/**
7-
* Create or update the secret `secretName` with value `secretValue` in Azure Key Vault `keyVaultName`
8-
*
9-
* @param keyVaultName The Azure key vault name
10-
* @param secretName Name of the secret
11-
* @param secretValue Value of the secret
12-
* @param opts optionally override spk config with Azure subscription access options
13-
*
14-
*/
15-
export const setSecret = async (
6+
export const validateValues = (
167
keyVaultName: string,
178
secretName: string,
18-
secretValue: string,
19-
opts: IAzureAccessOpts = {}
9+
secretValue?: string
2010
) => {
21-
// validate input
2211
const errors: string[] = [];
23-
2412
if (!keyVaultName) {
2513
errors.push(`Invalid keyVaultName`);
2614
}
27-
2815
if (!secretName) {
2916
errors.push(`Invalid secretName`);
3017
}
31-
32-
if (!secretValue) {
18+
if (secretValue !== undefined && !secretValue) {
3319
errors.push(`Invalid secretValue`);
3420
}
35-
3621
if (errors.length !== 0) {
3722
throw new Error(`\n${errors.join("\n")}`);
3823
}
24+
};
3925

26+
export const getClient = async (
27+
keyVaultName: string,
28+
opts: IAzureAccessOpts
29+
) => {
4030
const url = `https://${keyVaultName}.vault.azure.net`;
41-
const message = `secret ${secretName} with a value ${secretValue} in key vault ${keyVaultName}`;
31+
const credentials = await getCredentials(opts);
32+
return new SecretClient(url, credentials!);
33+
};
34+
35+
/**
36+
* Create or update the secret `secretName` with value `secretValue` in Azure Key Vault `keyVaultName`
37+
*
38+
* @param keyVaultName The Azure key vault name
39+
* @param secretName Name of the secret
40+
* @param secretValue Value of the secret
41+
* @param opts optionally override spk config with Azure subscription access options
42+
*
43+
*/
44+
export const setSecret = async (
45+
keyVaultName: string,
46+
secretName: string,
47+
secretValue: string,
48+
opts: IAzureAccessOpts = {}
49+
) => {
50+
validateValues(keyVaultName, secretName, secretValue);
4251
const messageWithNoValue = `secret ${secretName} in key vault ${keyVaultName}`;
43-
try {
44-
const credentials = await getCredentials(opts);
45-
const client = new SecretClient(url, credentials!);
4652

47-
// Create a secret
53+
try {
54+
const client = await getClient(keyVaultName, opts);
4855
logger.debug(`Setting ${messageWithNoValue}`);
49-
logger.verbose(`Setting ${message}`);
50-
const result = await client.setSecret(secretName, secretValue);
56+
await client.setSecret(secretName, secretValue);
5157
logger.debug(`Setting ${messageWithNoValue} is complete`);
52-
logger.verbose(`Set ${message} complete`);
5358
} catch (err) {
5459
logger.error(`Unable to set ${messageWithNoValue}. \n ${err}`);
5560
throw err;
@@ -69,32 +74,14 @@ export const getSecret = async (
6974
secretName: string,
7075
opts: IAzureAccessOpts = {}
7176
): Promise<string | undefined> => {
72-
// validate input
73-
const errors: string[] = [];
74-
75-
if (!keyVaultName) {
76-
errors.push(`Invalid keyVaultName`);
77-
}
78-
79-
if (!secretName) {
80-
errors.push(`Invalid secretName`);
81-
}
77+
validateValues(keyVaultName, secretName);
8278

83-
if (errors.length !== 0) {
84-
throw new Error(`\n${errors.join("\n")}`);
85-
}
86-
87-
const url = `https://${keyVaultName}.vault.azure.net`;
8879
const message = `secret ${secretName} from key vault ${keyVaultName}`;
8980
try {
90-
const credentials = await getCredentials(opts);
91-
const client = new SecretClient(url, credentials!);
92-
93-
// Get the secret
81+
const client = await getClient(keyVaultName, opts);
9482
logger.debug(`Getting ${message}`);
9583
const latestSecret = await client.getSecret(secretName);
9684
logger.debug(`Got ${message}`);
97-
logger.verbose(`Found ${message} and the value is ${latestSecret.value}`);
9885
return latestSecret.value;
9986
} catch (err) {
10087
if (err.code === "SecretNotFound" && err.statusCode === 404) {

0 commit comments

Comments
 (0)