Skip to content

Commit 94dffdd

Browse files
feat(APIM-468): change how integer config values are parsed
1 parent 2e5a948 commit 94dffdd

File tree

5 files changed

+192
-71
lines changed

5 files changed

+192
-71
lines changed

src/config/app.config.test.ts

Lines changed: 32 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
1+
import { withEnvironmentVariableParsingUnitTests } from '@ukef-test/common-tests/environment-variable-parsing-unit-tests';
22

3-
import appConfig from './app.config';
3+
import appConfig, { AppConfig } from './app.config';
44
import { InvalidConfigException } from './invalid-config.exception';
55

66
describe('appConfig', () => {
7-
const valueGenerator = new RandomValueGenerator();
8-
97
let originalProcessEnv: NodeJS.ProcessEnv;
108

119
beforeEach(() => {
@@ -79,57 +77,37 @@ describe('appConfig', () => {
7977
});
8078
});
8179

82-
describe('parsing REDACT_LOGS', () => {
83-
it('sets redactLogs to true if REDACT_LOGS is true', () => {
84-
replaceEnvironmentVariables({
85-
REDACT_LOGS: 'true',
86-
});
87-
88-
const config = appConfig();
89-
90-
expect(config.redactLogs).toBe(true);
91-
});
92-
93-
it('sets redactLogs to false if REDACT_LOGS is false', () => {
94-
replaceEnvironmentVariables({
95-
REDACT_LOGS: 'false',
96-
});
97-
98-
const config = appConfig();
99-
100-
expect(config.redactLogs).toBe(false);
101-
});
102-
103-
it('sets redactLogs to true if REDACT_LOGS is not specified', () => {
104-
replaceEnvironmentVariables({});
105-
106-
const config = appConfig();
107-
108-
expect(config.redactLogs).toBe(true);
109-
});
110-
111-
it('sets redactLogs to true if REDACT_LOGS is the empty string', () => {
112-
replaceEnvironmentVariables({
113-
REDACT_LOGS: '',
114-
});
115-
116-
const config = appConfig();
117-
118-
expect(config.redactLogs).toBe(true);
119-
});
120-
121-
it('sets redactLogs to true if REDACT_LOGS is any string other than true or false', () => {
122-
replaceEnvironmentVariables({
123-
REDACT_LOGS: valueGenerator.string(),
124-
});
125-
126-
const config = appConfig();
127-
128-
expect(config.redactLogs).toBe(true);
129-
});
130-
});
131-
13280
const replaceEnvironmentVariables = (newEnvVariables: Record<string, string>): void => {
13381
process.env = newEnvVariables;
13482
};
83+
84+
const configParsedBooleanFromEnvironmentVariablesWithDefault: {
85+
configPropertyName: keyof AppConfig;
86+
environmentVariableName: string;
87+
defaultConfigValue: boolean;
88+
}[] = [
89+
{
90+
configPropertyName: 'redactLogs',
91+
environmentVariableName: 'REDACT_LOGS',
92+
defaultConfigValue: true,
93+
},
94+
];
95+
96+
const configParsedAsIntFromEnvironmentVariablesWithDefault: {
97+
configPropertyName: keyof AppConfig;
98+
environmentVariableName: string;
99+
defaultConfigValue: number;
100+
}[] = [
101+
{
102+
configPropertyName: 'port',
103+
environmentVariableName: 'HTTP_PORT',
104+
defaultConfigValue: 3003,
105+
},
106+
];
107+
108+
withEnvironmentVariableParsingUnitTests({
109+
configParsedBooleanFromEnvironmentVariablesWithDefault,
110+
configParsedAsIntFromEnvironmentVariablesWithDefault,
111+
getConfig: () => appConfig(),
112+
});
135113
});

src/config/app.config.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
import { registerAs } from '@nestjs/config';
2+
import { getIntConfig } from '@ukef/helpers/get-int-config';
23

34
import { InvalidConfigException } from './invalid-config.exception';
45

56
const validLogLevels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'];
67

8+
export interface AppConfig {
9+
name: string;
10+
env: string;
11+
versioning: {
12+
enable: boolean;
13+
prefix: string;
14+
version: string;
15+
};
16+
globalPrefix: string;
17+
port: number;
18+
apiKey: string;
19+
logLevel: string;
20+
redactLogs: boolean;
21+
}
22+
723
export default registerAs('app', (): Record<string, any> => {
824
const logLevel = process.env.LOG_LEVEL || 'info';
925
if (!validLogLevels.includes(logLevel)) {
@@ -20,7 +36,7 @@ export default registerAs('app', (): Record<string, any> => {
2036
},
2137

2238
globalPrefix: '/api',
23-
port: process.env.HTTP_PORT ? Number.parseInt(process.env.HTTP_PORT, 10) : 3003,
39+
port: getIntConfig(process.env.HTTP_PORT, 3003),
2440
apiKey: process.env.API_KEY,
2541
logLevel: process.env.LOG_LEVEL || 'info',
2642
redactLogs: process.env.REDACT_LOGS !== 'false',

src/config/informatica.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { registerAs } from '@nestjs/config';
2+
import { getIntConfig } from '@ukef/helpers/get-int-config';
23

34
export const KEY = 'informatica';
45

@@ -16,7 +17,7 @@ export default registerAs(
1617
baseUrl: process.env.APIM_INFORMATICA_URL,
1718
username: process.env.APIM_INFORMATICA_USERNAME,
1819
password: process.env.APIM_INFORMATICA_PASSWORD,
19-
maxRedirects: parseInt(process.env.APIM_INFORMATICA_MAX_REDIRECTS) || 5,
20-
timeout: parseInt(process.env.APIM_INFORMATICA_TIMEOUT) || 30000,
20+
maxRedirects: getIntConfig(process.env.APIM_INFORMATICA_MAX_REDIRECTS, 5),
21+
timeout: getIntConfig(process.env.APIM_INFORMATICA_TIMEOUT, 30000),
2122
}),
2223
);

src/helpers/get-int-config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { InvalidConfigException } from '@ukef/config/invalid-config.exception';
2+
3+
// This helper function is used to get integer from configuration.
4+
export const getIntConfig = (environmentVariable: string, defaultValue?: number): number | undefined => {
5+
if (typeof environmentVariable === 'undefined') {
6+
if (typeof defaultValue === 'undefined') {
7+
throw new InvalidConfigException(`Environment variable is missing and doesn't have default value.`);
8+
}
9+
return defaultValue;
10+
}
11+
if (typeof environmentVariable !== 'string') {
12+
throw new InvalidConfigException(`Input environment variable type for ${environmentVariable} should be string.`);
13+
}
14+
15+
const integer = parseInt(environmentVariable, 10);
16+
// Check if parseInt is number, decimal base integer and input didn't have anything non-numeric.
17+
if (isNaN(integer) || integer.toString() !== environmentVariable) {
18+
throw new InvalidConfigException(`Invalid integer value "${environmentVariable}" for configuration property.`);
19+
}
20+
return integer;
21+
};

test/common-tests/environment-variable-parsing-unit-tests.ts

Lines changed: 119 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
import { InvalidConfigException } from '@ukef/config/invalid-config.exception';
12
import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
23

34
interface Options<ConfigUnderTest> {
4-
configDirectlyFromEnvironmentVariables: {
5+
configDirectlyFromEnvironmentVariables?: {
56
configPropertyName: keyof ConfigUnderTest;
67
environmentVariableName: string;
78
}[];
8-
configParsedAsIntFromEnvironmentVariablesWithDefault: {
9+
configParsedBooleanFromEnvironmentVariablesWithDefault?: {
10+
configPropertyName: keyof ConfigUnderTest;
11+
environmentVariableName: string;
12+
defaultConfigValue: boolean;
13+
}[];
14+
configParsedAsIntFromEnvironmentVariablesWithDefault?: {
915
configPropertyName: keyof ConfigUnderTest;
1016
environmentVariableName: string;
1117
defaultConfigValue: number;
@@ -15,6 +21,7 @@ interface Options<ConfigUnderTest> {
1521

1622
export const withEnvironmentVariableParsingUnitTests = <ConfigUnderTest>({
1723
configDirectlyFromEnvironmentVariables,
24+
configParsedBooleanFromEnvironmentVariablesWithDefault,
1825
configParsedAsIntFromEnvironmentVariablesWithDefault,
1926
getConfig,
2027
}: Options<ConfigUnderTest>): void => {
@@ -30,18 +37,79 @@ export const withEnvironmentVariableParsingUnitTests = <ConfigUnderTest>({
3037
process.env = originalProcessEnv;
3138
});
3239

33-
describe.each(configDirectlyFromEnvironmentVariables)('$configPropertyName', ({ configPropertyName, environmentVariableName }) => {
34-
it(`is the env variable ${environmentVariableName}`, () => {
35-
const environmentVariableValue = valueGenerator.string();
36-
process.env = {
37-
[environmentVariableName]: environmentVariableValue,
38-
};
40+
if (configDirectlyFromEnvironmentVariables) {
41+
describe.each(configDirectlyFromEnvironmentVariables)('$configPropertyName', ({ configPropertyName, environmentVariableName }) => {
42+
it(`is the env variable ${environmentVariableName}`, () => {
43+
const environmentVariableValue = valueGenerator.string();
44+
process.env = {
45+
[environmentVariableName]: environmentVariableValue,
46+
};
3947

40-
const { [configPropertyName]: configPropertyValue } = getConfig();
48+
const { [configPropertyName]: configPropertyValue } = getConfig();
4149

42-
expect(configPropertyValue).toBe(environmentVariableValue);
50+
expect(configPropertyValue).toBe(environmentVariableValue);
51+
});
4352
});
44-
});
53+
}
54+
55+
if (configParsedBooleanFromEnvironmentVariablesWithDefault) {
56+
describe.each(configParsedBooleanFromEnvironmentVariablesWithDefault)(
57+
'$configPropertyName',
58+
({ configPropertyName, environmentVariableName, defaultConfigValue }) => {
59+
it(`is true if environment variable ${environmentVariableName} is true`, () => {
60+
const expectedConfigValue = true;
61+
const environmentVariableValue = expectedConfigValue.toString();
62+
process.env = {
63+
[environmentVariableName]: environmentVariableValue,
64+
};
65+
66+
const { [configPropertyName]: configPropertyValue } = getConfig();
67+
68+
expect(configPropertyValue).toBe(expectedConfigValue);
69+
});
70+
71+
it(`is false if environment variable ${environmentVariableName} is false`, () => {
72+
const expectedConfigValue = false;
73+
const environmentVariableValue = expectedConfigValue.toString();
74+
process.env = {
75+
[environmentVariableName]: environmentVariableValue,
76+
};
77+
78+
const { [configPropertyName]: configPropertyValue } = getConfig();
79+
80+
expect(configPropertyValue).toBe(expectedConfigValue);
81+
});
82+
83+
it(`is the default value if ${environmentVariableName} is any string other than true or false`, () => {
84+
process.env = {
85+
[environmentVariableName]: valueGenerator.string(),
86+
};
87+
88+
const { [configPropertyName]: configPropertyValue } = getConfig();
89+
90+
expect(configPropertyValue).toBe(defaultConfigValue);
91+
});
92+
93+
it(`is the default value if ${environmentVariableName} is not specified`, () => {
94+
process.env = {};
95+
96+
const { [configPropertyName]: configPropertyValue } = getConfig();
97+
98+
expect(configPropertyValue).toBe(defaultConfigValue);
99+
});
100+
101+
it(`is the default value if ${environmentVariableName} is empty string`, () => {
102+
process.env = {
103+
[environmentVariableName]: '',
104+
};
105+
106+
const { [configPropertyName]: configPropertyValue } = getConfig();
107+
108+
expect(configPropertyValue).toBe(defaultConfigValue);
109+
});
110+
},
111+
);
112+
}
45113

46114
describe.each(configParsedAsIntFromEnvironmentVariablesWithDefault)(
47115
'$configPropertyName',
@@ -66,15 +134,52 @@ export const withEnvironmentVariableParsingUnitTests = <ConfigUnderTest>({
66134
expect(configPropertyValue).toBe(defaultConfigValue);
67135
});
68136

69-
it(`is the default value ${defaultConfigValue} if ${environmentVariableName} is not parseable as an integer`, () => {
137+
it(`throws InvalidConfigException if ${environmentVariableName} is not parseable as an integer`, () => {
70138
const environmentVariableValue = 'abc';
71139
process.env = {
72140
[environmentVariableName]: environmentVariableValue,
73141
};
74142

75-
const { [configPropertyName]: configPropertyValue } = getConfig();
143+
const gettingTheConfig = () => getConfig();
76144

77-
expect(configPropertyValue).toBe(defaultConfigValue);
145+
expect(gettingTheConfig).toThrow(InvalidConfigException);
146+
expect(gettingTheConfig).toThrow(`Invalid integer value "${environmentVariableValue}" for configuration property.`);
147+
});
148+
149+
it(`throws InvalidConfigException if ${environmentVariableName} is float number`, () => {
150+
const environmentVariableValue = valueGenerator.nonnegativeFloat().toString();
151+
process.env = {
152+
[environmentVariableName]: environmentVariableValue,
153+
};
154+
155+
const gettingTheConfig = () => getConfig();
156+
157+
expect(gettingTheConfig).toThrow(InvalidConfigException);
158+
expect(gettingTheConfig).toThrow(`Invalid integer value "${environmentVariableValue}" for configuration property.`);
159+
});
160+
161+
it(`throws InvalidConfigException if ${environmentVariableName} is hex number`, () => {
162+
const environmentVariableValue = '0xFF';
163+
process.env = {
164+
[environmentVariableName]: environmentVariableValue,
165+
};
166+
167+
const gettingTheConfig = () => getConfig();
168+
169+
expect(gettingTheConfig).toThrow(InvalidConfigException);
170+
expect(gettingTheConfig).toThrow(`Invalid integer value "${environmentVariableValue}" for configuration property.`);
171+
});
172+
173+
it(`throws InvalidConfigException if ${environmentVariableName} is binary number`, () => {
174+
const environmentVariableValue = '0b101';
175+
process.env = {
176+
[environmentVariableName]: environmentVariableValue,
177+
};
178+
179+
const gettingTheConfig = () => getConfig();
180+
181+
expect(gettingTheConfig).toThrow(InvalidConfigException);
182+
expect(gettingTheConfig).toThrow(`Invalid integer value "${environmentVariableValue}" for configuration property.`);
78183
});
79184
},
80185
);

0 commit comments

Comments
 (0)