Skip to content

Commit e2311ce

Browse files
committed
feat: add test file
1 parent 4541bfb commit e2311ce

10 files changed

+260
-29
lines changed

.npmignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ serverless.*
1212
*.gz
1313
*bak.js
1414
*BAK.js
15-
test
15+
test
16+
__test__

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
MIT License
1+
# MIT License
22

33
Copyright (c) 2023 Alex Zeng
44

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ yarn add cloud-configuration
1717
## Basic Usage
1818

1919
```typescript
20-
import cloudConfig from "cloud-configuration";
20+
import cloudConfig from 'cloud-configuration';
2121

2222
const configs = await cloudConfig.fetchAll();
2323

24-
const auFlagUrl = cloudConfig.get({ configs, featureKey: "au_flag_url" }); // return value or null
24+
const auFlagUrl = cloudConfig.get({ configs, featureKey: 'au_flag_url' }); // return value or null
2525

2626
const usFlagUrl = cloudConfig.getWithDefault({
2727
configs,
28-
featureKey: "us_flag_url",
29-
defaultValue: "https://example.com/us.png",
28+
featureKey: 'us_flag_url',
29+
defaultValue: 'https://example.com/us.png',
3030
}); // return typed value or default value, good for typescript
3131
```
3232

__test__/cloud-config.test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { mockApiData } from './mock-api-data';
2+
import cloudConfig, { CloudConfigData } from '../index';
3+
4+
let cloudConfigData: CloudConfigData[] = [];
5+
6+
const decryptSecret = 'CLIENT_/lMAHJcive';
7+
8+
describe('fetchAllConfigs', () => {
9+
it('should fetch configs successfully', async () => {
10+
const mockResponse = {
11+
ok: true,
12+
json: jest.fn().mockResolvedValue(mockApiData),
13+
};
14+
const mockFetch = jest.fn().mockResolvedValue(mockResponse);
15+
global.fetch = mockFetch;
16+
17+
cloudConfigData = await cloudConfig.fetchAll({
18+
decryptSecret,
19+
});
20+
21+
expect(mockFetch).toHaveBeenCalledTimes(1);
22+
expect(mockFetch).toHaveBeenCalledWith(
23+
expect.any(String),
24+
expect.any(Object)
25+
);
26+
expect(mockResponse.json).toHaveBeenCalledTimes(1);
27+
expect(cloudConfigData[2].featureKey).toEqual(mockApiData[2].featureKey);
28+
});
29+
30+
it('should throw an error when response is not ok', async () => {
31+
const mockResponse = { ok: false, status: 404, statusText: 'Not Found' };
32+
const mockFetch = jest.fn().mockResolvedValue(mockResponse);
33+
global.fetch = mockFetch;
34+
35+
const result = await cloudConfig.fetchAll();
36+
expect(result.length).toEqual(0);
37+
});
38+
39+
it('should catch and log errors', async () => {
40+
const mockError = new Error('Test error');
41+
const mockFetch = jest.fn().mockRejectedValue(mockError);
42+
global.fetch = mockFetch;
43+
44+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
45+
46+
const result = await cloudConfig.fetchAll();
47+
48+
expect(consoleSpy).toHaveBeenCalledTimes(1);
49+
expect(consoleSpy).toHaveBeenCalledWith(
50+
'💔💔💔 fetchAllConfigs error:',
51+
mockError
52+
);
53+
expect(result).toEqual([]);
54+
});
55+
});
56+
57+
describe('getConfigWithDefaultValue', () => {
58+
it('should return the correct cloud config value', () => {
59+
const boolean_2 = cloudConfig.getWithDefault({
60+
configs: cloudConfigData,
61+
featureKey: 'boolean_2',
62+
defaultValue: true,
63+
});
64+
expect(boolean_2).toEqual(false);
65+
66+
const key_not_exists = cloudConfig.getWithDefault({
67+
configs: cloudConfigData,
68+
featureKey: 'key_not_exists',
69+
defaultValue: 'default_value_if_key_not_exists',
70+
});
71+
expect(key_not_exists).toEqual('default_value_if_key_not_exists');
72+
73+
const encrypt_array_001 = cloudConfig.getWithDefault({
74+
configs: cloudConfigData,
75+
projectName: 'project001',
76+
groupName: 'group001',
77+
featureKey: 'encrypt_key_001',
78+
defaultValue: ['encrypt_key_001_value', 'value2'],
79+
});
80+
expect(encrypt_array_001).toEqual([
81+
'a1',
82+
'a2',
83+
's3',
84+
's a7',
85+
'hamilton east',
86+
]);
87+
88+
const encrypt_json_001 = cloudConfig.getWithDefault({
89+
configs: cloudConfigData,
90+
featureKey: 'json_key_1',
91+
defaultValue: { message: 'json message' },
92+
});
93+
expect(encrypt_json_001.message).toEqual('I am JSON');
94+
95+
const not_enabled_key = cloudConfig.get({
96+
configs: cloudConfigData,
97+
featureKey: 'float_key_1',
98+
});
99+
expect(not_enabled_key).toEqual(null);
100+
});
101+
});

__test__/mock-api-data.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
export const mockApiData = [
2+
{
3+
projectName: 'defaultProject',
4+
groupName: 'defaultGroup',
5+
featureKey: 'json_key_1',
6+
value:
7+
'U2FsdGVkX1+mogKJ4wd9gZ2YJ2e7s+mJXtADCnww2B1VfsSBnpyI+Y+Bwz7kpayLjkvSWEMk4iRNo38BtNKFBUrkj5lm7K0my/7xHY1KqvHA5z1msr4yAIsrJETCIl3X',
8+
valueType: 'json',
9+
valueEncrypted: true,
10+
prodEnabled: true,
11+
devEnabled: true,
12+
},
13+
{
14+
projectName: 'defaultProject',
15+
groupName: 'defaultGroup',
16+
featureKey: 'population_auk',
17+
value: 'U2FsdGVkX1+r7+y9XWTBXxtfCQLUiryxKVyT3C298A0=',
18+
valueType: 'number',
19+
valueEncrypted: true,
20+
prodEnabled: false,
21+
devEnabled: false,
22+
},
23+
{
24+
projectName: 'defaultProject',
25+
groupName: 'defaultGroup',
26+
featureKey: 'key1',
27+
value: 'value1',
28+
valueType: 'string',
29+
prodEnabled: true,
30+
devEnabled: true,
31+
},
32+
{
33+
projectName: 'defaultProject',
34+
groupName: 'defaultGroup',
35+
featureKey: 'key1a',
36+
value: 'Default Value1',
37+
valueType: 'string',
38+
prodEnabled: true,
39+
devEnabled: false,
40+
},
41+
{
42+
projectName: 'project001',
43+
groupName: 'group001',
44+
featureKey: 'encrypt_key_001',
45+
value:
46+
'U2FsdGVkX1/r7C9jX5YV0e772hXz0dkdcz3WF+UfLooTybLFCjzVVUGVHxOPhNU0A8nf7lT45sJKD5cVIAHOyA==',
47+
valueType: 'array',
48+
valueEncrypted: true,
49+
prodEnabled: true,
50+
devEnabled: true,
51+
},
52+
{
53+
projectName: 'project002',
54+
groupName: 'group002',
55+
featureKey: 'encrypt_key_002',
56+
value: 'U2FsdGVkX1+jmy577IqUlWs+ydML3Rs8S6LnAo1wWKG9Gk9Vvjh35rZETsW5D09f',
57+
valueType: 'array',
58+
valueEncrypted: true,
59+
prodEnabled: false,
60+
devEnabled: true,
61+
},
62+
{
63+
projectName: 'defaultProject',
64+
groupName: 'defaultGroup',
65+
featureKey: 'boolean_1',
66+
value: true,
67+
valueType: 'boolean',
68+
prodEnabled: true,
69+
devEnabled: false,
70+
},
71+
{
72+
projectName: 'defaultProject',
73+
groupName: 'defaultGroup',
74+
featureKey: 'boolean_2',
75+
value: false,
76+
valueType: 'boolean',
77+
prodEnabled: true,
78+
devEnabled: true,
79+
},
80+
{
81+
projectName: 'defaultProject',
82+
groupName: 'defaultGroup',
83+
featureKey: 'boolean_003',
84+
value: true,
85+
valueType: 'boolean',
86+
prodEnabled: true,
87+
devEnabled: true,
88+
},
89+
{
90+
projectName: 'defaultProject',
91+
groupName: 'defaultGroup',
92+
featureKey: 'float_key_1',
93+
value: 23.32,
94+
valueType: 'number',
95+
prodEnabled: false,
96+
devEnabled: false,
97+
},
98+
{
99+
projectName: 'defaultProject',
100+
groupName: 'defaultGroup',
101+
featureKey: 'cloudConfigExtraData',
102+
value: {
103+
dataRetrievedAt: '2023-10-13T02:18:22.237Z',
104+
configCount: 10,
105+
},
106+
valueType: 'json',
107+
prodEnabled: true,
108+
devEnabled: true,
109+
},
110+
];

cloud-config.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ export const decryptConfig = (
2020
const decryptedData = AES.decrypt(data, cryptSecret);
2121
const decryptedText = decryptedData.toString(encUtf8);
2222
if (!decryptedText || decryptedText === data) {
23-
return 'Decrypt value failed! Make sure the encrypt secret is correct in env';
23+
return (
24+
'Decrypt value failed! Make sure the encrypt secret is correct in env' +
25+
cryptSecret
26+
);
2427
}
2528
return decryptedText;
2629
} catch (error) {
@@ -31,15 +34,17 @@ export const decryptConfig = (
3134

3235
export const parseSingleConfig = (
3336
config: CloudConfigData,
34-
serverSideOnly = false
37+
serverSideOnly = false,
38+
decryptSecret?: string
3539
): CloudConfigData => {
3640
if (!config.valueEncrypted) {
3741
return config;
3842
}
3943
const cryptSecret = serverSideOnly
4044
? CLOUD_CONFIG_SERVER_ENCRYPT_SECRET
4145
: CLOUD_CONFIG_CLIENT_ENCRYPT_SECRET;
42-
if (!cryptSecret) {
46+
const newDecryptSecret = decryptSecret || cryptSecret;
47+
if (!newDecryptSecret) {
4348
// eslint-disable-next-line no-console
4449
console.log(
4550
`😅😅😅 Can't decrypt featureKey ${config.featureKey}, Please set ${
@@ -50,7 +55,10 @@ export const parseSingleConfig = (
5055
);
5156
return config;
5257
}
53-
const decryptedValue = decryptConfig(config.value as string, cryptSecret);
58+
const decryptedValue = decryptConfig(
59+
config.value as string,
60+
newDecryptSecret
61+
);
5462
if (!decryptedValue) {
5563
return config;
5664
}
@@ -87,9 +95,12 @@ export const parseSingleConfig = (
8795

8896
export const parseAllConfigs = (
8997
configs: CloudConfigData[],
90-
serverSideOnly = false
98+
serverSideOnly = false,
99+
decryptSecret?: string
91100
): CloudConfigData[] => {
92-
return configs.map((config) => parseSingleConfig(config, serverSideOnly));
101+
return configs.map((config) =>
102+
parseSingleConfig(config, serverSideOnly, decryptSecret)
103+
);
93104
};
94105

95106
interface GetCloudConfigParams<T> {
@@ -145,7 +156,15 @@ export const getConfigWithDefaultValue = <T>(
145156

146157
export const fetchAllConfigs = async (params?: FetchAllConfigsParams) => {
147158
try {
148-
const { orgId, serverSide, accessToken, cache, apiPrefix, cacheSeconds } = {
159+
const {
160+
orgId,
161+
serverSide,
162+
accessToken,
163+
cache,
164+
apiPrefix,
165+
cacheSeconds,
166+
decryptSecret,
167+
} = {
149168
...CLOUD_CONFIG_FETCH_ALL_DEFAULT_VALUE,
150169
...params,
151170
};
@@ -188,7 +207,7 @@ export const fetchAllConfigs = async (params?: FetchAllConfigsParams) => {
188207

189208
const configs = ((await response.json()) || []) as CloudConfigData[];
190209

191-
return parseAllConfigs(configs, serverSide);
210+
return parseAllConfigs(configs, serverSide, decryptSecret);
192211
} catch (error) {
193212
console.log('💔💔💔 fetchAllConfigs error:', error);
194213
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
22
"name": "cloud-configuration",
3-
"version": "0.1.8",
3+
"version": "0.1.9",
44
"description": "This package allows you to use CloudConfig easily.",
55
"author": "Alex Zeng",
66
"license": "MIT",
77
"main": "index.js",
88
"type": "module",
99
"scripts": {
10-
"test": "echo \"Error: no test specified\" && exit 1"
10+
"test": "echo \"Error: no test specified\" && exit 1",
11+
"publish-package": "npm publish --access public"
1112
},
1213
"repository": {
1314
"type": "git",

test/test-config.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

tsconfig.json

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@
2525
}
2626
]
2727
},
28-
"ts-node": {
29-
// Tell ts-node CLI to install the --loader automatically
30-
"esm": true
31-
},
32-
"include": ["next-env.d.ts", "**/*.ts", "*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28+
"ts-node": {
29+
// Tell ts-node CLI to install the --loader automatically
30+
"esm": true
31+
},
32+
"include": [
33+
"next-env.d.ts",
34+
"**/*.ts",
35+
"*.ts",
36+
"**/*.tsx",
37+
".next/types/**/*.ts"
38+
],
3339
"exclude": ["node_modules"],
3440
"moduleResolution": ["node_modules", ".next", "node"]
3541
}

types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ export interface FetchAllConfigsParams {
1616
cache?: RequestCache;
1717
apiPrefix?: string;
1818
cacheSeconds?: number;
19+
decryptSecret?: string;
1920
}

0 commit comments

Comments
 (0)