Skip to content

Commit fa236b9

Browse files
authored
tests(parameters): integration tests for SSMProvider (aws-powertools#1257)
* chore: update cdkAspect to include SSM * tests: SSMProvider class usage * chore: removed unused import * tests: completed tests * tests: fixed typos * chore: added missing effect in cdkAspect * chore: reduced scope of ssmSecureString custom resource * refactor: options setting logic
1 parent a9658c9 commit fa236b9

8 files changed

+696
-38
lines changed

packages/parameters/src/ssm/SSMProvider.ts

+15-23
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,11 @@ class SSMProvider extends BaseProvider {
144144
options?: SSMGetOptionsInterface
145145
): Promise<string | undefined> {
146146
const sdkOptions: GetParameterCommandInput = {
147+
...(options?.sdkOptions || {}),
147148
Name: name,
148149
};
149-
if (options) {
150-
if (options.hasOwnProperty('decrypt')) sdkOptions.WithDecryption = options.decrypt;
151-
if (options.hasOwnProperty('sdkOptions')) {
152-
Object.assign(sdkOptions, options.sdkOptions);
153-
}
154-
}
150+
sdkOptions.WithDecryption = options?.decrypt !== undefined ?
151+
options.decrypt : sdkOptions.WithDecryption;
155152
const result = await this.client.send(new GetParameterCommand(sdkOptions));
156153

157154
return result.Parameter?.Value;
@@ -162,21 +159,18 @@ class SSMProvider extends BaseProvider {
162159
options?: SSMGetMultipleOptionsInterface
163160
): Promise<Record<string, string | undefined>> {
164161
const sdkOptions: GetParametersByPathCommandInput = {
162+
...(options?.sdkOptions || {}),
165163
Path: path,
166164
};
167165
const paginationOptions: PaginationConfiguration = {
168166
client: this.client
169167
};
170-
if (options) {
171-
if (options.hasOwnProperty('decrypt')) sdkOptions.WithDecryption = options.decrypt;
172-
if (options.hasOwnProperty('recursive')) sdkOptions.Recursive = options.recursive;
173-
if (options.hasOwnProperty('sdkOptions')) {
174-
Object.assign(sdkOptions, options.sdkOptions);
175-
if (sdkOptions.MaxResults) {
176-
paginationOptions.pageSize = sdkOptions.MaxResults;
177-
}
178-
}
179-
}
168+
sdkOptions.WithDecryption = options?.decrypt !== undefined ?
169+
options.decrypt : sdkOptions.WithDecryption;
170+
sdkOptions.Recursive = options?.recursive !== undefined ?
171+
options.recursive : sdkOptions.Recursive;
172+
paginationOptions.pageSize = sdkOptions.MaxResults !== undefined ?
173+
sdkOptions.MaxResults : undefined;
180174

181175
const parameters: Record<string, string | undefined> = {};
182176
for await (const page of paginateGetParametersByPath(paginationOptions, sdkOptions)) {
@@ -389,13 +383,11 @@ class SSMProvider extends BaseProvider {
389383
const overrides = parameterOptions;
390384
overrides.transform = overrides.transform || configs.transform;
391385

392-
if (!overrides.hasOwnProperty('decrypt')) {
393-
overrides.decrypt = configs.decrypt;
394-
}
395-
if (!overrides.hasOwnProperty('maxAge')) {
396-
overrides.maxAge = configs.maxAge;
397-
}
398-
386+
overrides.decrypt = overrides.decrypt !== undefined ?
387+
overrides.decrypt : configs.decrypt;
388+
overrides.maxAge = overrides.maxAge !== undefined ?
389+
overrides.maxAge : configs.maxAge;
390+
399391
if (overrides.decrypt) {
400392
parametersToDecrypt[parameterName] = overrides;
401393
} else {

packages/parameters/src/ssm/index.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export * from './SSMProvider';
22
export * from './getParameter';
33
export * from './getParameters';
4-
export * from './getParametersByName';
5-
export * from '../types/SSMProvider';
4+
export * from './getParametersByName';

packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
* @group e2e/parameters/dynamodb/class
55
*/
66
import path from 'path';
7-
import { Tracing } from 'aws-cdk-lib/aws-lambda';
87
import { AttributeType } from 'aws-cdk-lib/aws-dynamodb';
98
import { App, Stack, Aspects } from 'aws-cdk-lib';
109
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
@@ -138,10 +137,9 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () =
138137
// Create a stack with a Lambda function
139138
stack = createStackWithLambdaFunction({
140139
app: integTestApp,
141-
stackName: stackName,
142-
functionName: functionName,
140+
stackName,
141+
functionName,
143142
functionEntry: path.join(__dirname, lambdaFunctionCodeFile),
144-
tracing: Tracing.ACTIVE,
145143
environment: {
146144
UUID: uuid,
147145

@@ -154,7 +152,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () =
154152
SORT_ATTR: sortAttr,
155153
VALUE_ATTR: valueAttr,
156154
},
157-
runtime: runtime,
155+
runtime,
158156
});
159157

160158
// Create the DynamoDB tables
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { Context } from 'aws-lambda';
2+
import {
3+
SSMProvider,
4+
} from '../../src/ssm';
5+
import {
6+
SSMGetOptionsInterface,
7+
SSMGetMultipleOptionsInterface,
8+
SSMGetParametersByNameOptionsInterface
9+
} from '../../src/types';
10+
import { TinyLogger } from '../helpers/tinyLogger';
11+
import { middleware } from '../helpers/sdkMiddlewareRequestCounter';
12+
import { SSMClient } from '@aws-sdk/client-ssm';
13+
14+
// We use a custom logger to log pure JSON objects to stdout
15+
const logger = new TinyLogger();
16+
17+
const defaultProvider = new SSMProvider();
18+
// Provider test 8, 9
19+
const customClient = new SSMClient({});
20+
customClient.middlewareStack.use(middleware);
21+
const providerWithMiddleware = new SSMProvider({
22+
awsSdkV3Client: customClient
23+
});
24+
25+
const paramA = process.env.PARAM_A ?? 'my-param';
26+
const paramB = process.env.PARAM_B ?? 'my-param';
27+
const paramEncryptedA = process.env.PARAM_ENCRYPTED_A ?? 'my-encrypted-param';
28+
const paramEncryptedB = process.env.PARAM_ENCRYPTED_B ?? 'my-encrypted-param';
29+
30+
// Use provider specified, or default to main one & return it with cache cleared
31+
const resolveProvider = (provider?: SSMProvider): SSMProvider => {
32+
const resolvedProvider = provider ? provider : defaultProvider;
33+
resolvedProvider.clearCache();
34+
35+
return resolvedProvider;
36+
};
37+
38+
// Helper function to call get() and log the result
39+
const _call_get = async (
40+
paramName: string,
41+
testName: string,
42+
options?: SSMGetOptionsInterface,
43+
provider?: SSMProvider
44+
): Promise<void> => {
45+
try {
46+
const currentProvider = resolveProvider(provider);
47+
48+
const parameterValue = await currentProvider.get(paramName, options);
49+
logger.log({
50+
test: testName,
51+
value: parameterValue
52+
});
53+
} catch (err) {
54+
logger.log({
55+
test: testName,
56+
error: err.message
57+
});
58+
}
59+
};
60+
61+
// Helper function to call getMultiple() and log the result
62+
const _call_get_multiple = async (
63+
paramPath: string,
64+
testName: string,
65+
options?: SSMGetMultipleOptionsInterface,
66+
provider?: SSMProvider
67+
): Promise<void> => {
68+
try {
69+
const currentProvider = resolveProvider(provider);
70+
71+
const parameterValues = await currentProvider.getMultiple(
72+
paramPath,
73+
options
74+
);
75+
logger.log({
76+
test: testName,
77+
value: parameterValues
78+
});
79+
} catch (err) {
80+
logger.log({
81+
test: testName,
82+
error: err.message
83+
});
84+
}
85+
};
86+
87+
// Helper function to call getParametersByName() and log the result
88+
const _call_get_parameters_by_name = async (
89+
params: Record<string, SSMGetParametersByNameOptionsInterface>,
90+
testName: string,
91+
options?: SSMGetParametersByNameOptionsInterface,
92+
provider?: SSMProvider
93+
): Promise<void> => {
94+
try {
95+
const currentProvider = resolveProvider(provider);
96+
97+
const parameterValues = await currentProvider.getParametersByName(params, options);
98+
logger.log({
99+
test: testName,
100+
value: parameterValues
101+
});
102+
} catch (err) {
103+
logger.log({
104+
test: testName,
105+
error: err.message
106+
});
107+
}
108+
};
109+
110+
export const handler = async (_event: unknown, _context: Context): Promise<void> => {
111+
// Test 1 - get a single parameter by name with default options
112+
await _call_get(paramA, 'get');
113+
114+
// Test 2 - get a single parameter by name with decrypt
115+
await _call_get(paramEncryptedA, 'get-decrypt', { decrypt: true });
116+
117+
// Test 3 - get multiple parameters by path with default options
118+
// Get path (/param/get)
119+
const parameterPath = paramA.substring(0, paramA.lastIndexOf('/'));
120+
await _call_get_multiple(parameterPath, 'get-multiple');
121+
122+
// Test 4 - get multiple parameters by path recursively (aka. get all parameters under a path recursively)
123+
// Get parameters root (i.e. from /param/get/a & /param/get/b to /param)
124+
const parameterRoot = paramA.substring(
125+
0,
126+
paramA.substring(1, paramA.length).indexOf('/') + 1
127+
);
128+
await _call_get_multiple(parameterRoot, 'get-multiple-recursive', { recursive: true });
129+
130+
// Test 5 - get multiple parameters by path with decrypt
131+
// Get parameters path (i.e. from /param/get/a & /param/get/b to /param/get)
132+
const parameterPathDecrypt = paramEncryptedA.substring(0, paramEncryptedA.lastIndexOf('/'));
133+
await _call_get_multiple(parameterPathDecrypt, 'get-multiple-decrypt', { decrypt: true });
134+
135+
// Test 6 - get multiple parameters by name with default options
136+
await _call_get_parameters_by_name({
137+
[paramA]: {},
138+
[paramB]: {},
139+
}, 'get-multiple-by-name');
140+
141+
// Test 7 - get multiple parameters by name, some of them encrypted and some not
142+
await _call_get_parameters_by_name({
143+
[paramA]: {},
144+
[paramEncryptedA]: { decrypt: true },
145+
[paramEncryptedB]: { decrypt: true },
146+
}, 'get-multiple-by-name-mixed-decrypt');
147+
148+
// Test 8
149+
// get parameter twice with middleware, which counts the number of requests, we check later if we only called SSM API once
150+
try {
151+
providerWithMiddleware.clearCache();
152+
middleware.counter = 0;
153+
await providerWithMiddleware.get(paramA);
154+
await providerWithMiddleware.get(paramA);
155+
logger.log({
156+
test: 'get-cached',
157+
value: middleware.counter // should be 1
158+
});
159+
} catch (err) {
160+
logger.log({
161+
test: 'get-cached',
162+
error: err.message
163+
});
164+
}
165+
166+
// Test 9
167+
// get parameter twice, but force fetch 2nd time, we count number of SDK requests and check that we made two API calls
168+
try {
169+
providerWithMiddleware.clearCache();
170+
middleware.counter = 0;
171+
await providerWithMiddleware.get(paramA);
172+
await providerWithMiddleware.get(paramA, { forceFetch: true });
173+
logger.log({
174+
test: 'get-forced',
175+
value: middleware.counter // should be 2
176+
});
177+
} catch (err) {
178+
logger.log({
179+
test: 'get-forced',
180+
error: err.message
181+
});
182+
}
183+
};

0 commit comments

Comments
 (0)