Skip to content

Commit e8301b7

Browse files
authored
read config from file/env (#55)
* read config from file/env
1 parent 8498b9b commit e8301b7

10 files changed

+341
-15
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ coverage
1818
node_modules/
1919

2020
dist
21+
22+
# config
23+
vrt.json

README.md

+31-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import {
2222
} from "@visual-regression-tracker/sdk-js";
2323
```
2424

25-
### Configure connection
25+
### Configure
26+
27+
#### Explicit config from code
2628

2729
```js
2830
const config: Config = {
@@ -50,8 +52,35 @@ const config: Config = {
5052
// Optional - default null
5153
ciBuildId: "SOME_UNIQUE_ID",
5254
};
55+
```
56+
57+
#### Or, as JSON config file `vrt.json`
5358

54-
const vrt = new VisualRegressionTracker(config);
59+
_Used only if not explicit config provided_
60+
_Is overriden if ENV variables are present_
61+
62+
```json
63+
{
64+
"apiUrl": "http://localhost:4200",
65+
"project": "Default project",
66+
"apiKey": "tXZVHX0EA4YQM1MGDD",
67+
"ciBuildId": "commit_sha",
68+
"branchName": "develop",
69+
"enableSoftAssert": false
70+
}
71+
```
72+
73+
#### Or, as environment variables
74+
75+
_Used only if not explicit config provided_
76+
77+
```
78+
VRT_APIURL="http://localhost:4200"
79+
VRT_PROJECT="Default project"
80+
VRT_APIKEY="tXZVHX0EA4YQM1MGDD"
81+
VRT_CIBUILDID="commit_sha"
82+
VRT_BRANCHNAME="develop"
83+
VRT_ENABLESOFTASSERT=true
5584
```
5685

5786
### Setup

jest.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module.exports = {
22
preset: "ts-jest",
33
testEnvironment: "node",
4-
modulePathIgnorePatterns: ["<rootDir>/dist/"]
4+
modulePathIgnorePatterns: ["<rootDir>/dist/"],
55
};

lib/helpers/config.helper.spec.ts

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import {
2+
readConfigFromEnv,
3+
readConfigFromFile,
4+
validateConfig,
5+
} from "./config.helper";
6+
import { mocked } from "ts-jest/utils";
7+
import { Config } from "types";
8+
import * as fs from "fs";
9+
10+
jest.mock("fs");
11+
const mockedFs = mocked(fs);
12+
13+
const initialConfig: Config = {
14+
apiUrl: "http://localhost:4200",
15+
branchName: "develop",
16+
project: "Default project",
17+
apiKey: "CPKVK4JNK24NVNPNGVFQ853HXXEG",
18+
enableSoftAssert: true,
19+
ciBuildId: "someCIBuildId",
20+
};
21+
22+
describe("config.helper", () => {
23+
describe("readConfigFromFile", () => {
24+
beforeEach(() => {
25+
mockedFs.readFileSync.mockClear();
26+
});
27+
28+
const updatedConfig: Config = {
29+
apiUrl: "apiUrlUpdated",
30+
branchName: "branchNameUpdated",
31+
project: "projectUpdated",
32+
apiKey: "apiKeyUpdated",
33+
enableSoftAssert: false,
34+
ciBuildId: "ciBuildIdUpdated",
35+
};
36+
37+
it("should read from file", () => {
38+
mockedFs.existsSync.mockReturnValueOnce(true);
39+
mockedFs.readFileSync.mockReturnValueOnce(
40+
Buffer.from(JSON.stringify(updatedConfig), "utf8")
41+
);
42+
43+
const result = readConfigFromFile(initialConfig);
44+
45+
expect(result).toEqual(updatedConfig);
46+
});
47+
48+
it("should be skipped if no value", () => {
49+
mockedFs.existsSync.mockReturnValueOnce(true);
50+
mockedFs.readFileSync.mockReturnValueOnce(
51+
Buffer.from(JSON.stringify(""), "utf8")
52+
);
53+
54+
const result = readConfigFromFile(initialConfig);
55+
56+
expect(result).toEqual(initialConfig);
57+
});
58+
59+
it("should be skipped if no file", () => {
60+
mockedFs.existsSync.mockReturnValueOnce(false);
61+
62+
const result = readConfigFromFile(initialConfig);
63+
64+
expect(mockedFs.readFileSync).not.toHaveBeenCalled();
65+
expect(result).toEqual(initialConfig);
66+
});
67+
});
68+
describe("readConfigFromEnv", () => {
69+
let INIT_ENV: any;
70+
beforeEach(() => {
71+
INIT_ENV = process.env;
72+
});
73+
afterEach(() => {
74+
process.env = INIT_ENV;
75+
});
76+
it("should read config from env variables", () => {
77+
process.env = {
78+
...process.env,
79+
VRT_APIURL: "apiUrlTest",
80+
VRT_CIBUILDID: "ciBuildIdTest",
81+
VRT_BRANCHNAME: "branchNameTest",
82+
VRT_PROJECT: "projectTest",
83+
VRT_APIKEY: "apiKeyTest",
84+
VRT_ENABLESOFTASSERT: "false",
85+
};
86+
87+
const result = readConfigFromEnv(initialConfig);
88+
89+
expect(result).toEqual({
90+
apiUrl: "apiUrlTest",
91+
ciBuildId: "ciBuildIdTest",
92+
branchName: "branchNameTest",
93+
project: "projectTest",
94+
apiKey: "apiKeyTest",
95+
enableSoftAssert: false,
96+
});
97+
});
98+
it("should be skipped if no value", () => {
99+
process.env = {
100+
...process.env,
101+
VRT_APIURL: undefined,
102+
VRT_CIBUILDID: undefined,
103+
VRT_BRANCHNAME: undefined,
104+
VRT_PROJECT: undefined,
105+
VRT_APIKEY: undefined,
106+
VRT_ENABLESOFTASSERT: undefined,
107+
};
108+
109+
const result = readConfigFromEnv(initialConfig);
110+
111+
expect(result).toEqual(initialConfig);
112+
});
113+
});
114+
describe("validateConfig", () => {
115+
it.each([
116+
[
117+
{
118+
apiUrl: "",
119+
branchName: "develop",
120+
project: "Default project",
121+
apiKey: "CPKVK4JNK24NVNPNGVFQ853HXXEG",
122+
enableSoftAssert: false,
123+
ciBuildId: "someCIBuildId",
124+
},
125+
"apiUrl is not specified",
126+
],
127+
[
128+
{
129+
apiUrl: "http://localhost:4200",
130+
branchName: "",
131+
project: "Default project",
132+
apiKey: "CPKVK4JNK24NVNPNGVFQ853HXXEG",
133+
enableSoftAssert: false,
134+
ciBuildId: "someCIBuildId",
135+
},
136+
"branchName is not specified",
137+
],
138+
[
139+
{
140+
apiUrl: "http://localhost:4200",
141+
branchName: "master",
142+
project: "",
143+
apiKey: "CPKVK4JNK24NVNPNGVFQ853HXXEG",
144+
enableSoftAssert: false,
145+
ciBuildId: "someCIBuildId",
146+
},
147+
"project is not specified",
148+
],
149+
[
150+
{
151+
apiUrl: "http://localhost:4200",
152+
branchName: "master",
153+
project: "project",
154+
apiKey: "",
155+
enableSoftAssert: false,
156+
ciBuildId: "someCIBuildId",
157+
},
158+
"apiKey is not specified",
159+
],
160+
])(`should throw if not valid config`, (notValidConfig, errorMessage) => {
161+
expect(() => validateConfig(notValidConfig)).toThrowError(
162+
new Error(errorMessage)
163+
);
164+
});
165+
});
166+
});

lib/helpers/config.helper.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { existsSync, readFileSync } from "fs";
2+
import { Config } from "types";
3+
4+
const CONFIG_FILE_PATH = "./vrt.json";
5+
6+
const readConfigFromFile = (config: Config): Config => {
7+
if (existsSync(CONFIG_FILE_PATH)) {
8+
const fileConfig = JSON.parse(readFileSync(CONFIG_FILE_PATH).toString());
9+
if (fileConfig.apiUrl) {
10+
config.apiUrl = fileConfig.apiUrl;
11+
}
12+
if (fileConfig.ciBuildId) {
13+
config.ciBuildId = fileConfig.ciBuildId;
14+
}
15+
if (fileConfig.branchName) {
16+
config.branchName = fileConfig.branchName;
17+
}
18+
if (fileConfig.project) {
19+
config.project = fileConfig.project;
20+
}
21+
if (fileConfig.apiKey) {
22+
config.apiKey = fileConfig.apiKey;
23+
}
24+
if (fileConfig.enableSoftAssert !== undefined) {
25+
config.enableSoftAssert = fileConfig.enableSoftAssert;
26+
}
27+
}
28+
return config;
29+
};
30+
31+
const readConfigFromEnv = (config: Config): Config => {
32+
if (process.env["VRT_APIURL"]) {
33+
config.apiUrl = process.env["VRT_APIURL"];
34+
}
35+
if (process.env["VRT_CIBUILDID"]) {
36+
config.ciBuildId = process.env["VRT_CIBUILDID"];
37+
}
38+
if (process.env["VRT_BRANCHNAME"]) {
39+
config.branchName = process.env["VRT_BRANCHNAME"];
40+
}
41+
if (process.env["VRT_PROJECT"]) {
42+
config.project = process.env["VRT_PROJECT"];
43+
}
44+
if (process.env["VRT_APIKEY"]) {
45+
config.apiKey = process.env["VRT_APIKEY"];
46+
}
47+
if (process.env["VRT_ENABLESOFTASSERT"] !== undefined) {
48+
config.enableSoftAssert = process.env["VRT_ENABLESOFTASSERT"] === "true";
49+
}
50+
return config;
51+
};
52+
53+
const validateConfig = (config: Config): void => {
54+
if (!config.apiKey) {
55+
throw new Error("apiKey is not specified");
56+
}
57+
if (!config.branchName) {
58+
throw new Error("branchName is not specified");
59+
}
60+
if (!config.apiUrl) {
61+
throw new Error("apiUrl is not specified");
62+
}
63+
if (!config.project) {
64+
throw new Error("project is not specified");
65+
}
66+
};
67+
68+
export { readConfigFromFile, readConfigFromEnv, validateConfig };

lib/helpers/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./config.helper";

lib/types/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export interface Config {
1+
export interface Config extends Record<string, any> {
22
apiUrl: string;
33
branchName: string;
44
project: string;

lib/visualRegressionTracker.spec.ts

+48-7
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import {
99
import { mocked } from "ts-jest/utils";
1010
import TestRunResult from "./testRunResult";
1111
import axios, { AxiosError, AxiosResponse } from "axios";
12+
import * as configHelper from "./helpers/config.helper";
1213

1314
jest.mock("axios");
1415
const mockedAxios = mocked(axios, true);
1516

1617
jest.mock("./testRunResult");
1718
const mockedTestRunResult = mocked(TestRunResult, true);
1819

20+
jest.mock("./helpers/config.helper");
21+
const mockedConfigHelper = mocked(configHelper);
22+
1923
const axiosError404: AxiosError = {
2024
isAxiosError: true,
2125
config: {},
@@ -87,16 +91,53 @@ const axiosErrorEmptyResponse: AxiosError = {
8791
response: undefined,
8892
};
8993

94+
const config: Config = {
95+
apiUrl: "http://localhost:4200",
96+
branchName: "develop",
97+
project: "Default project",
98+
apiKey: "CPKVK4JNK24NVNPNGVFQ853HXXEG",
99+
enableSoftAssert: false,
100+
ciBuildId: "someCIBuildId",
101+
};
102+
90103
describe("VisualRegressionTracker", () => {
91-
let vrt: VisualRegressionTracker;
92-
const config: Config = {
93-
apiUrl: "http://localhost:4200",
94-
branchName: "develop",
95-
project: "Default project",
96-
apiKey: "CPKVK4JNK24NVNPNGVFQ853HXXEG",
104+
const fileConfig: Config = {
105+
apiUrl: "apiUrlFile",
106+
branchName: "branchNameFile",
107+
project: "projectFile",
108+
apiKey: "apiKeyFile",
97109
enableSoftAssert: false,
98-
ciBuildId: "someCIBuildId",
110+
ciBuildId: "ciBuildIdFile",
99111
};
112+
const envConfig: Config = {
113+
apiUrl: "apiUrlEnv",
114+
branchName: "branchNameEnv",
115+
project: "projectEnv",
116+
apiKey: "apiKeyEnv",
117+
enableSoftAssert: false,
118+
ciBuildId: "ciBuildIdEnv",
119+
};
120+
121+
beforeEach(() => {
122+
mockedConfigHelper.readConfigFromFile.mockReturnValueOnce(fileConfig);
123+
mockedConfigHelper.readConfigFromEnv.mockReturnValueOnce(envConfig);
124+
});
125+
126+
it("should use explicit config", () => {
127+
new VisualRegressionTracker(config);
128+
129+
expect(mockedConfigHelper.validateConfig).toHaveBeenCalledWith(config);
130+
});
131+
132+
it("should use env over file config", () => {
133+
new VisualRegressionTracker();
134+
135+
expect(mockedConfigHelper.validateConfig).toHaveBeenCalledWith(envConfig);
136+
});
137+
});
138+
139+
describe("VisualRegressionTracker", () => {
140+
let vrt: VisualRegressionTracker;
100141

101142
beforeEach(async () => {
102143
vrt = new VisualRegressionTracker(config);

0 commit comments

Comments
 (0)