Skip to content

Commit b511267

Browse files
author
Ben Gowers
committed
added tests and refactored to handle multiple option variables within a stack name (from the serverless config file)
1 parent dd7b006 commit b511267

File tree

10 files changed

+3270
-1198
lines changed

10 files changed

+3270
-1198
lines changed

.eslintrc.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module.exports = {
22
env: {
33
node: true,
44
es6: true,
5+
"jest/globals": true,
56
},
67
extends: ["airbnb-base", "prettier"],
78
globals: {
@@ -12,7 +13,7 @@ module.exports = {
1213
ecmaVersion: 2018,
1314
sourceType: "module",
1415
},
15-
plugins: ["prettier"],
16+
plugins: ["prettier", "jest"],
1617
rules: {
1718
"prettier/prettier": "error",
1819
quotes: ["error", "double"],
@@ -22,5 +23,11 @@ module.exports = {
2223
"no-new": "off",
2324
"import/named": "off",
2425
"import/no-cycle": "off",
26+
// jest
27+
"jest/no-disabled-tests": "warn",
28+
"jest/no-focused-tests": "error",
29+
"jest/no-identical-title": "error",
30+
"jest/prefer-to-have-length": "warn",
31+
"jest/valid-expect": "error",
2532
},
2633
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import fs from "fs";
2+
3+
import {
4+
transformArgsToDict,
5+
replaceStacknameOpt,
6+
CYAN_STRING_FORMAT,
7+
} from "../../src/services/severlessConfigParser/helpers";
8+
import ServerlessConfigParser from "../../src/services/severlessConfigParser/serverlessConfigParser";
9+
10+
const TEST_YAML_FILE = `
11+
service:
12+
name: test-\${opt:testArg1}
13+
14+
package:
15+
exclude:
16+
- .git/**
17+
- .gitignore
18+
19+
plugins:
20+
- serverless-webpack
21+
22+
provider:
23+
name: aws
24+
runtime: nodejs10.x
25+
region: eu-west-1
26+
stage: \${opt:stage, 'dev'}
27+
usagePlan:
28+
quota:
29+
limit: 5000
30+
offset: 2
31+
period: MONTH
32+
throttle:
33+
burstLimit: 200
34+
rateLimit: 100
35+
iamRoleStatements:
36+
- Effect: Allow
37+
Action:
38+
- dynamodb:DescribeTable
39+
- dynamodb:Query
40+
- dynamodb:Scan
41+
- dynamodb:GetItem
42+
- dynamodb:PutItem
43+
- dynamodb:UpdateItem
44+
- dynamodb:DeleteItem
45+
Resource:
46+
- 'Fn::GetAtt': [Table, Arn]
47+
environment:
48+
tableName: \${self:custom.dynamodbTableName}
49+
50+
functions:
51+
hello:
52+
handler: hello.main
53+
memorySize: 1024
54+
timeout: 6
55+
events:
56+
- http:
57+
method: get
58+
path: hello
59+
authorizer:
60+
type: COGNITO_USER_POOLS
61+
authorizerId:
62+
Ref: ApiGatewayAuthorizer
63+
create:
64+
handler: create.main
65+
events:
66+
- http:
67+
method: post
68+
path: items
69+
cors: true
70+
authorizer:
71+
type: COGNITO_USER_POOLS
72+
authorizerId:
73+
Ref: ApiGatewayAuthorizer
74+
list:
75+
handler: list.main
76+
events:
77+
- http:
78+
method: get
79+
path: items
80+
cors: true
81+
authorizer:
82+
type: COGNITO_USER_POOLS
83+
authorizerId:
84+
Ref: ApiGatewayAuthorizer
85+
get:
86+
handler: get.main
87+
events:
88+
- http:
89+
method: get
90+
path: items/{id}
91+
cors: true
92+
authorizer:
93+
type: COGNITO_USER_POOLS
94+
authorizerId:
95+
Ref: ApiGatewayAuthorizer
96+
update:
97+
handler: update.main
98+
events:
99+
- http:
100+
method: put
101+
path: items/{id}
102+
cors: true
103+
authorizer:
104+
type: COGNITO_USER_POOLS
105+
authorizerId:
106+
Ref: ApiGatewayAuthorizer
107+
delete:
108+
handler: delete.main
109+
events:
110+
- http:
111+
method: delete
112+
path: items/{id}
113+
cors: true
114+
authorizer:
115+
type: COGNITO_USER_POOLS
116+
authorizerId:
117+
Ref: ApiGatewayAuthorizer
118+
`;
119+
120+
// eslint-disable-next-line no-template-curly-in-string
121+
const STACK_NAME_WITH_OPT = "test-${opt:testArg1}";
122+
const STACK_NAME_WITHOUT_OPT = "test-backend";
123+
const CMD_VAR_1 = "--testArg1";
124+
const CMD_VAL_1 = "backend";
125+
const CMD_VAR_2 = "-testArg2";
126+
const CMD_VAL_2 = "arg2";
127+
const INVALID_ARGUMENT_EXIT_CODE = 9;
128+
129+
jest.mock("fs");
130+
131+
describe("Serverless Config Options", () => {
132+
/*
133+
* Transform Args
134+
*/
135+
it("should transform 1 arg (double dash) to dict", () => {
136+
const args = [CMD_VAR_1, CMD_VAL_1];
137+
const result = { testArg1: CMD_VAL_1 };
138+
expect(transformArgsToDict(args)).toStrictEqual(result);
139+
});
140+
141+
it("should transform 1 arg (single dash) to dict", () => {
142+
const args = [CMD_VAR_2, CMD_VAL_2];
143+
const result = { testArg2: CMD_VAL_2 };
144+
expect(transformArgsToDict(args)).toStrictEqual(result);
145+
});
146+
147+
it("should not remove dashes from middle of option in service name", () => {
148+
const args = ["--env-name", CMD_VAL_1];
149+
const result = { "env-name": CMD_VAL_1 };
150+
expect(transformArgsToDict(args)).toStrictEqual(result);
151+
});
152+
153+
it("should transform multiple args (mixed dash) to dict", () => {
154+
const args = [CMD_VAR_1, CMD_VAL_1, CMD_VAR_2, CMD_VAL_2];
155+
const result = {
156+
testArg1: CMD_VAL_1,
157+
testArg2: CMD_VAL_2,
158+
};
159+
expect(transformArgsToDict(args)).toStrictEqual(result);
160+
});
161+
162+
/*
163+
* Replace service.name Opt
164+
*/
165+
it("should replace the service name option with stored option", () => {
166+
const testOptions = { testArg1: CMD_VAL_1 };
167+
expect(replaceStacknameOpt(STACK_NAME_WITH_OPT, testOptions)).toBe(
168+
STACK_NAME_WITHOUT_OPT
169+
);
170+
});
171+
172+
it("should not replace the stack name where there is not opt variable in the YAML configuration file", () => {
173+
const testOptions = { testArg1: CMD_VAL_1 };
174+
expect(replaceStacknameOpt(STACK_NAME_WITHOUT_OPT, testOptions)).toBe(
175+
STACK_NAME_WITHOUT_OPT
176+
);
177+
});
178+
179+
it("should output an error message when an opt varibale exists in serverless configuration but no option is passed", () => {
180+
console.error = jest.fn();
181+
process.exit = jest.fn();
182+
replaceStacknameOpt(STACK_NAME_WITH_OPT, []);
183+
184+
expect(console.error).toHaveBeenCalledWith(
185+
CYAN_STRING_FORMAT,
186+
`Your project requires stack name option ${CMD_VAR_1} to be passed when starting sls-dev-tools`
187+
);
188+
expect(process.exit).toHaveBeenCalledWith(INVALID_ARGUMENT_EXIT_CODE);
189+
});
190+
});
191+
192+
describe("Serverless Config Parsing", () => {
193+
let SLS;
194+
195+
beforeAll(() => {
196+
fs.readFileSync.mockReturnValue(TEST_YAML_FILE);
197+
fs.existsSync.mockReturnValue(true);
198+
199+
const program = {
200+
args: [CMD_VAR_1, CMD_VAL_1],
201+
location: "~/Dev/testProject/backend",
202+
};
203+
204+
SLS = new ServerlessConfigParser(program);
205+
});
206+
207+
it("should read YAML file and replace opt in service.name with stored arg", () => {
208+
expect(SLS.getStackName("dev")).toBe("test-backend-dev");
209+
});
210+
211+
it("should get a stackname given a stage", () => {
212+
const stage = "test";
213+
expect(SLS.getStackName(stage)).toBe("test-backend-test");
214+
});
215+
});

package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@
2020
"@babel/cli": "^7.5.5",
2121
"@babel/core": "^7.5.5",
2222
"@babel/preset-env": "^7.5.5",
23-
"eslint": "^7.0.0",
23+
"@types/jest": "^26.0.20",
24+
"babel-jest": "^26.6.3",
25+
"eslint": "^7.21.0",
2426
"eslint-config-airbnb-base": "^14.0.0",
2527
"eslint-config-prettier": "^8.1.0",
2628
"eslint-plugin-import": "^2.18.2",
29+
"eslint-plugin-jest": "^24.1.5",
2730
"eslint-plugin-prettier": "^3.1.3",
31+
"jest": "^26.6.3",
2832
"prettier": "^2.0.4",
2933
"release-it": "^14.0.3"
3034
},
@@ -36,7 +40,7 @@
3640
"debug": "node --nolazy --inspect-brk=9229 ./lib/index.js",
3741
"lint": "eslint ./src",
3842
"fix": "yarn lint --fix",
39-
"test": "yarn lint",
43+
"test": "yarn lint && jest",
4044
"release": "release-it"
4145
},
4246
"babel": {

src/guardian/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
getStackResources,
1313
getLambdaFunctions,
1414
} from "../services";
15-
import ServerlessConfigParser from "../services/serverlessConfigParser";
15+
import ServerlessConfigParser from "../services/severlessConfigParser/serverlessConfigParser";
1616

1717
const infoLog = chalk.greenBright;
1818
const titleLog = chalk.greenBright.underline.bold;

src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env node
2-
import ServerlessConfigParser from "./services/serverlessConfigParser";
2+
import ServerlessConfigParser from "./services/severlessConfigParser/serverlessConfigParser";
33
import GuardianCI from "./guardian/index";
44
import Main from "./CLIMain";
55

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export const CYAN_STRING_FORMAT = "\x1b[36m%s\x1b[0m";
2+
3+
export const transformArgsToDict = (args) => {
4+
const options = {};
5+
6+
for (let i = 0; i < args.length; i += 2) {
7+
const key = args[i].replace(/^-+/, "");
8+
options[key] = args[i + 1];
9+
}
10+
11+
return options;
12+
};
13+
14+
export const replaceStacknameOpt = (stackname, cmdOptions) => {
15+
const optRegex = /\$\{opt:.*\}/;
16+
17+
if (!stackname.match(optRegex)) return stackname;
18+
19+
let variable = stackname.match(optRegex)[0];
20+
21+
variable = variable.replace(/[${}]/g, "");
22+
const varName = variable.split(":")[1];
23+
24+
if (cmdOptions[varName] === undefined) {
25+
console.error(
26+
CYAN_STRING_FORMAT,
27+
`Your project requires stack name option --${varName} to be passed when starting sls-dev-tools`
28+
);
29+
process.exit(9);
30+
}
31+
32+
return stackname.replace(/\$\{opt:.*\}/, cmdOptions[varName]);
33+
};

src/services/serverlessConfigParser.js src/services/severlessConfigParser/serverlessConfigParser.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import transformArgsToDict from "../utils/transformArgsToDict";
2-
import replaceVariablesInYml from "../utils/replaceVariablesInYml";
1+
import { transformArgsToDict, replaceStacknameOpt } from "./helpers";
32

43
const fs = require("fs");
54
const path = require("path");
@@ -15,15 +14,18 @@ class ServerlessConfigParser {
1514

1615
if (fs.existsSync(ymlPath)) {
1716
this.config = YAML.load(fs.readFileSync(ymlPath).toString("utf8"));
18-
}
19-
if (fs.existsSync(yamlPath)) {
17+
} else if (fs.existsSync(yamlPath)) {
2018
this.config = YAML.load(fs.readFileSync(yamlPath).toString("utf8"));
21-
}
22-
if (fs.existsSync(jsonPath)) {
19+
} else if (fs.existsSync(jsonPath)) {
2320
this.config = JSON.parse(fs.readFileSync(jsonPath).toString("utf8"));
2421
}
2522

26-
replaceVariablesInYml(this.config, options);
23+
if (!this.config) return;
24+
25+
this.config.service.name = replaceStacknameOpt(
26+
this.config.service.name,
27+
options
28+
);
2729
}
2830

2931
getFunctionConfig(functionName) {

src/utils/replaceVariablesInYml.js

-17
This file was deleted.

src/utils/transformArgsToDict.js

-12
This file was deleted.

0 commit comments

Comments
 (0)