From e3ab0560f0d9bfe87be1094338c541c246736fdb Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 12 Jan 2023 20:26:52 +0100 Subject: [PATCH 1/6] tests: DynamoDBProvider e2e tests --- ...ures.dynamoDBProvider.test.functionCode.ts | 199 ++++++++++ .../basicFeatures.dynamoDBProvider.test.ts | 341 ++++++++++++++++++ packages/parameters/tests/e2e/constants.ts | 5 + .../tests/helpers/cdkAspectGrantAccess.ts | 44 +++ .../tests/helpers/parametersUtils.ts | 22 ++ .../helpers/sdkMiddlewareRequestCounter.ts | 30 ++ .../parameters/tests/helpers/tinyLogger.ts | 21 ++ 7 files changed, 662 insertions(+) create mode 100644 packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.functionCode.ts create mode 100644 packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts create mode 100644 packages/parameters/tests/e2e/constants.ts create mode 100644 packages/parameters/tests/helpers/cdkAspectGrantAccess.ts create mode 100644 packages/parameters/tests/helpers/parametersUtils.ts create mode 100644 packages/parameters/tests/helpers/sdkMiddlewareRequestCounter.ts create mode 100644 packages/parameters/tests/helpers/tinyLogger.ts diff --git a/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.functionCode.ts b/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.functionCode.ts new file mode 100644 index 0000000000..3455621c98 --- /dev/null +++ b/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.functionCode.ts @@ -0,0 +1,199 @@ +import { Context } from 'aws-lambda'; +import { DynamoDBProvider } from '../../src/dynamodb'; +import { TinyLogger } from '../helpers/tinyLogger'; +/* +import { middleware } from '../helpers/sdkMiddlewareRequestCounter'; +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +*/ + +const tableGet = process.env.TABLE_GET ?? 'my-table'; +const tableGetMultiple = process.env.TABLE_GET_MULTIPLE ?? 'my-table'; +const tableGetCustomkeys = process.env.TABLE_GET_CUSTOM_KEYS ?? 'my-table'; +const tableGetMultipleCustomkeys = process.env.TABLE_GET_MULTIPLE_CUSTOM_KEYS ?? 'my-table'; +const keyAttr = process.env.KEY_ATTR ?? 'id'; +const sortAttr = process.env.SORT_ATTR ?? 'sk'; +const valueAttr = process.env.VALUE_ATTR ?? 'value'; + +// We use a custom logger to log pure JSON objects to stdout +const logger = new TinyLogger(); + +// Provider test 1, 5, 6 +const providerGet = new DynamoDBProvider({ + tableName: tableGet, +}); +// Provider test 2, 7 +const providerGetMultiple = new DynamoDBProvider({ + tableName: tableGetMultiple, +}); +// Provider test 3 +const providerGetCustomKeys = new DynamoDBProvider({ + tableName: tableGetCustomkeys, + keyAttr, + valueAttr, +}); +// Provider 4 +const providerGetMultipleCustomKeys = new DynamoDBProvider({ + tableName: tableGetMultipleCustomkeys, + keyAttr, + sortAttr, + valueAttr, +}); +/* +# TODO: Uncomment this code once the following issue is fixed: #1222 +// Provider test 8, 9 +const customClient = new DynamoDBClient({}); +providerWithMiddleware.middlewareStack.use(middleware); +const providerWithMiddleware = new DynamoDBProvider({ + awsSdkV3Client: customClient +}); +*/ + +export const handler = async (_event: unknown, _context: Context): Promise => { + // Test 1 - get a single parameter with default options (keyAttr: 'id', valueAttr: 'value') + try { + const parameterValue = await providerGet.get('my-param'); + logger.log({ + test: 'get', + value: parameterValue + }); + } catch (err) { + logger.log({ + test: 'get', + error: err.message + }); + } + + // Test 2 - get multiple parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') + try { + const parametersValues = await providerGetMultiple.getMultiple('my-params'); + logger.log({ + test: 'get-multiple', + value: parametersValues + }); + } catch (err) { + logger.log({ + test: 'get-multiple', + error: err.message + }); + } + + // Test 3 - get a single parameter with custom options (keyAttr: 'key', valueAttr: 'val') + try { + const parameterValueCustom = await providerGetCustomKeys.get('my-param'); + logger.log({ + test: 'get-custom', + value: parameterValueCustom + }); + } catch (err) { + logger.log({ + test: 'get-custom', + error: err.message + }); + } + + // Test 4 - get multiple parameters with custom options (keyAttr: 'key', sortAttr: 'sort', valueAttr: 'val') + try { + const parametersValuesCustom = await providerGetMultipleCustomKeys.getMultiple('my-params'); + logger.log({ + test: 'get-multiple-custom', + value: parametersValuesCustom + }); + } catch (err) { + logger.log({ + test: 'get-multiple-custom', + error: err.message + }); + } + + // Test 5 - get a single parameter with json transform + try { + const parameterValueJson = await providerGet.get('my-param-json', { + transform: 'json' + }); + logger.log({ + test: 'get-json-transform', + value: typeof parameterValueJson // should be object + }); + } catch (err) { + logger.log({ + test: 'get-json-transform', + error: err.message + }); + } + + // Test 6 - get a single parameter with binary transform + try { + const parameterValueBinary = await providerGet.get('my-param-binary', { + transform: 'binary' + }); + logger.log({ + test: 'get-binary-transform', + value: typeof parameterValueBinary // should be string + }); + } catch (err) { + logger.log({ + test: 'get-binary-transform', + error: err.message + }); + } + + // Test 7 - get multiple parameters with auto transform + try { + const parametersValuesAuto = await providerGetMultiple.getMultiple('my-encoded-params', { + transform: 'auto' + }); + if (!parametersValuesAuto) throw new Error('parametersValuesAuto is undefined'); + + logger.log({ + test: 'get-multiple-auto-transform', + value: + `${typeof parametersValuesAuto.config},${parametersValuesAuto.key}` // should be object,string + }); + } catch (err) { + logger.log({ + test: 'get-multiple-auto-transform', + error: err.message + }); + } + + /** + * Test 8 - get a parameter twice, second time should be cached + * + * Should only make 1 request, we use middleware to count requests + */ + /* + try { + await providerWithMiddleware.get('my-param'); + await providerWithMiddleware.get('my-param'); + logger.log({ + test: 'get-cache-request-count', + value: middleware.requestCount + }); + } catch (err) { + logger.log({ + test: 'get-cache-request-count', + error: err.message + }); + } + */ + + /** + * Test 9 - get a parameter once more but with forceFetch = true + * + * Request count should increase to 2, we use middleware to count requests + */ + /* + try { + await providerWithMiddleware.get('my-param', { forceFetch: true }); + logger.log({ + test: 'get-force-fetch-request-count', + value: middleware.requestCount + }); + } catch (err) { + logger.log({ + test: 'get-force-fetch-request-count', + error: err.message + }); + } + */ +}; \ No newline at end of file diff --git a/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts b/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts new file mode 100644 index 0000000000..5c9b0ee45c --- /dev/null +++ b/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts @@ -0,0 +1,341 @@ +/** + * Test DynamoDBProvider basic features + * + * @group e2e/parameters/dynamodb/basicFeatures + */ +import path from 'path'; +import { Tracing } from 'aws-cdk-lib/aws-lambda'; +import { AttributeType } from 'aws-cdk-lib/aws-dynamodb'; +import { App, Stack, Aspects } from 'aws-cdk-lib'; +import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb'; +import { marshall } from '@aws-sdk/util-dynamodb'; +import { v4 } from 'uuid'; +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, +} from '../../../commons/tests/utils/e2eUtils'; +import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; +import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { ResourceAccessGranter } from '../helpers/cdkAspectGrantAccess'; +import { + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT +} from './constants'; +import { createDynamoDBTable } from '../helpers/parametersUtils'; + +const runtime: string = process.env.RUNTIME || 'nodejs18x'; + +if (!isValidRuntimeKey(runtime)) { + throw new Error(`Invalid runtime key value: ${runtime}`); +} + +const uuid = v4(); +const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider'); +const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider'); +const lambdaFunctionCodeFile = 'basicFeatures.dynamoDBProvider.test.functionCode.ts'; + +const dynamoDBClient = new DynamoDBClient({}); + +const invocationCount = 1; +// const startTime = new Date(); + +// Parameters to be used by Parameters in the Lambda function +const tableGet = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-Get'); +const tableGetMultiple = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-GetMultiple'); +const tableGetCustomkeys = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-GetCustomKeys'); +const tableGetMultipleCustomkeys = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-GetMultipleCustomKeys'); +const keyAttr = 'key'; +const sortAttr = 'sort'; +const valueAttr = 'val'; + +const integTestApp = new App(); +let stack: Stack; + +describe(`parameters E2E tests (dynamoDBProvider) for runtime: nodejs18x`, () => { + + let invocationLogs: InvocationLogs[]; + + beforeAll(async () => { + // GIVEN a stack + stack = createStackWithLambdaFunction({ + app: integTestApp, + stackName: stackName, + functionName: functionName, + functionEntry: path.join(__dirname, lambdaFunctionCodeFile), + tracing: Tracing.ACTIVE, + environment: { + UUID: uuid, + + // Values(s) to be used by Parameters in the Lambda function + TABLE_GET: tableGet, + TABLE_GET_MULTIPLE: tableGetMultiple, + TABLE_GET_CUSTOM_KEYS: tableGetCustomkeys, + TABLE_GET_MULTIPLE_CUSTOM_KEYS: tableGetMultipleCustomkeys, + KEY_ATTR: keyAttr, + SORT_ATTR: sortAttr, + VALUE_ATTR: valueAttr, + }, + runtime: runtime, + }); + + // Create the DynamoDB tables + const ddbTableGet = createDynamoDBTable({ + stack, + id: 'Table-get', + tableName: tableGet, + partitionKey: { + name: 'id', + type: AttributeType.STRING + }, + }); + const ddbTableGetMultiple = createDynamoDBTable({ + stack, + id: 'Table-getMultiple', + tableName: tableGetMultiple, + partitionKey: { + name: 'id', + type: AttributeType.STRING + }, + sortKey: { + name: 'sk', + type: AttributeType.STRING + } + }); + const ddbTableGetCustomKeys = createDynamoDBTable({ + stack, + id: 'Table-getCustomKeys', + tableName: tableGetCustomkeys, + partitionKey: { + name: keyAttr, + type: AttributeType.STRING + }, + }); + const ddbTabelGetMultipleCustomKeys = createDynamoDBTable({ + stack, + id: 'Table-getMultipleCustomKeys', + partitionKey: { + name: keyAttr, + type: AttributeType.STRING + }, + sortKey: { + name: sortAttr, + type: AttributeType.STRING + }, + }); + + // Give the Lambda access to the DynamoDB tables + Aspects.of(stack).add(new ResourceAccessGranter([ + ddbTableGet, + ddbTableGetMultiple, + ddbTableGetCustomKeys, + ddbTabelGetMultipleCustomKeys, + ])); + + await deployStack(integTestApp, stack); + + // Seed tables with test data + // Test 1 + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGet, + Item: marshall({ + id: 'my-param', + value: 'foo', + }), + })); + + // Test 2 + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGetMultiple, + Item: marshall({ + id: 'my-params', + sk: 'config', + value: 'bar', + }), + })); + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGetMultiple, + Item: marshall({ + id: 'my-params', + sk: 'key', + value: 'baz', + }), + })); + + // Test 3 + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGetCustomkeys, + Item: marshall({ + [keyAttr]: 'my-param', + [valueAttr]: 'foo', + }), + })); + + // Test 4 + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGetMultipleCustomkeys, + Item: marshall({ + [keyAttr]: 'my-params', + [sortAttr]: 'config', + [valueAttr]: 'bar', + }), + })); + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGetMultipleCustomkeys, + Item: marshall({ + [keyAttr]: 'my-params', + [sortAttr]: 'key', + [valueAttr]: 'baz', + }), + })); + + // Test 5 + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGet, + Item: marshall({ + id: 'my-param-json', + value: JSON.stringify({ foo: 'bar' }), + }), + })); + + // Test 6 + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGet, + Item: marshall({ + id: 'my-param-binary', + value: 'YmF6', // base64 encoded 'baz' + }), + })); + + // Test 7 + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGetMultiple, + Item: marshall({ + id: 'my-encoded-params', + sk: 'config.json', + value: JSON.stringify({ foo: 'bar' }), + }), + })); + await dynamoDBClient.send(new PutItemCommand({ + TableName: tableGetMultiple, + Item: marshall({ + id: 'my-encoded-params', + sk: 'key.binary', + value: 'YmF6', // base64 encoded 'baz' + }), + })); + + // Test 8 & 9 use the same items as Test 1 + + // and invoke the Lambda function + invocationLogs = await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); + + }, SETUP_TIMEOUT); + + describe('DynamoDBProvider usage', () => { + + // Test 1 - get a single parameter with default options (keyAttr: 'id', valueAttr: 'value') + it('should retrieve a single parameter', async () => { + + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[0]); + + expect(testLog).toStrictEqual({ + test: 'get', + value: 'foo', + }); + + }, TEST_CASE_TIMEOUT); + + // Test 2 - get multiple parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') + it('should retrieve multiple parameters', async () => { + + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[1]); + + expect(testLog).toStrictEqual({ + test: 'get-multiple', + value: { config: 'bar', key: 'baz' }, + }); + + }, TEST_CASE_TIMEOUT); + + // Test 3 - get a single parameter with custom options (keyAttr: 'key', valueAttr: 'val') + it('should retrieve a single parameter', async () => { + + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[2]); + + expect(testLog).toStrictEqual({ + test: 'get-custom', + value: 'foo', + }); + + }, TEST_CASE_TIMEOUT); + + // Test 4 - get multiple parameters with custom options (keyAttr: 'key', sortAttr: 'sort', valueAttr: 'val') + it('should retrieve multiple parameters', async () => { + + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[3]); + + expect(testLog).toStrictEqual({ + test: 'get-multiple-custom', + value: { config: 'bar', key: 'baz' }, + }); + + }, TEST_CASE_TIMEOUT); + + // Test 5 - get a single parameter with json transform + it('should retrieve a single parameter with json transform', async () => { + + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[4]); + + expect(testLog).toStrictEqual({ + test: 'get-json-transform', + value: 'object', + }); + + }); + + // Test 6 - get a single parameter with binary transform + it('should retrieve a single parameter with binary transform', async () => { + + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[4]); + + expect(testLog).toStrictEqual({ + test: 'get-binary-transform', + value: 'string', // as opposed to Uint8Array + }); + + }); + + // Test 7 - get multiple parameters with auto transforms (json and binary) + it('should retrieve multiple parameters with auto transforms', async () => { + + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[5]); + + expect(testLog).toStrictEqual({ + test: 'get-multiple-auto-transform', + value: 'object,string', + }); + + }); + + // Test 8 - get a parameter twice, second time should be cached + // Test 9 - get a parameter once more but with forceFetch = true + + }); + + afterAll(async () => { + if (!process.env.DISABLE_TEARDOWN) { + await destroyStack(integTestApp, stack); + } + }, TEARDOWN_TIMEOUT); +}); diff --git a/packages/parameters/tests/e2e/constants.ts b/packages/parameters/tests/e2e/constants.ts new file mode 100644 index 0000000000..03fb4eed52 --- /dev/null +++ b/packages/parameters/tests/e2e/constants.ts @@ -0,0 +1,5 @@ +export const RESOURCE_NAME_PREFIX = 'Parameters-E2E'; +export const ONE_MINUTE = 60 * 1000; +export const TEST_CASE_TIMEOUT = 3 * ONE_MINUTE; +export const SETUP_TIMEOUT = 5 * ONE_MINUTE; +export const TEARDOWN_TIMEOUT = 5 * ONE_MINUTE; \ No newline at end of file diff --git a/packages/parameters/tests/helpers/cdkAspectGrantAccess.ts b/packages/parameters/tests/helpers/cdkAspectGrantAccess.ts new file mode 100644 index 0000000000..22f8c09e6f --- /dev/null +++ b/packages/parameters/tests/helpers/cdkAspectGrantAccess.ts @@ -0,0 +1,44 @@ +import { IAspect } from 'aws-cdk-lib'; +import { IConstruct } from 'constructs'; +import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; +import { Table } from 'aws-cdk-lib/aws-dynamodb'; +import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; + +/** + * An aspect that grants access to resources to a Lambda function. + * + * In our integration tests, we dynamically generate AWS CDK stacks that contain a Lambda function. + * We want to grant access to resources to the Lambda function, but we don't know the name of the + * Lambda function at the time we create the resources. Additionally, we want to keep the code + * that creates the stacks and functions as generic as possible. + * + * This aspect allows us to grant access to specific resources to all Lambda functions in a stack + * after the stack tree has been generated and before the stack is deployed. This aspect is + * used to grant access to different resource types (DynamoDB tables, SSM parameters, etc.). + * + * @see {@link https://docs.aws.amazon.com/cdk/v2/guide/aspects.html|CDK Docs - Aspects} + */ +export class ResourceAccessGranter implements IAspect { + private readonly resources: Table[] | Secret[]; + + public constructor(tables: Table[] | Secret[]) { + this.resources = tables; + } + + public visit(node: IConstruct): void { + // See that we're dealing with a Function + if (node instanceof NodejsFunction) { + + // Grant access to the resources + this.resources.forEach((resource: Table | Secret) => { + + if (resource instanceof Table) { + resource.grantReadData(node); + } else if (resource instanceof Secret) { + resource.grantRead(node); + } + + }); + } + } +} \ No newline at end of file diff --git a/packages/parameters/tests/helpers/parametersUtils.ts b/packages/parameters/tests/helpers/parametersUtils.ts new file mode 100644 index 0000000000..3768a1fe53 --- /dev/null +++ b/packages/parameters/tests/helpers/parametersUtils.ts @@ -0,0 +1,22 @@ +import { Stack, RemovalPolicy } from 'aws-cdk-lib'; +import { Table, TableProps, BillingMode } from 'aws-cdk-lib/aws-dynamodb'; + +export type CreateDynamoDBTableOptions = { + stack: Stack + id: string +} & TableProps; + +const createDynamoDBTable = (options: CreateDynamoDBTableOptions): Table => { + const { stack, id, ...tableProps } = options; + const props = { + billingMode: BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, + ...tableProps, + }; + + return new Table(stack, id, props); +}; + +export { + createDynamoDBTable +}; \ No newline at end of file diff --git a/packages/parameters/tests/helpers/sdkMiddlewareRequestCounter.ts b/packages/parameters/tests/helpers/sdkMiddlewareRequestCounter.ts new file mode 100644 index 0000000000..f71a6173eb --- /dev/null +++ b/packages/parameters/tests/helpers/sdkMiddlewareRequestCounter.ts @@ -0,0 +1,30 @@ +/** + * Middleware to count the number of API calls made by the SDK. + * + * The AWS SDK for JavaScript v3 uses a middleware stack to manage the execution of + * operations. Middleware can be added to the stack to perform custom tasks before + * or after an operation is executed. + * + * This middleware is added to the stack to count the number of API calls (`ROUND_TRIP`) made by the SDK. + * This allows us to verify that the SDK is making the expected number of API calls and thus test that + * caching or forcing a retrieval are working as expected. + * + * @see {@link https://aws.amazon.com/blogs/developer/middleware-stack-modular-aws-sdk-js/|AWS Blog - Middleware Stack} + */ +export const middleware = { + // + counter : 0, + applyToStack: (stack) => { + // Middleware added to mark start and end of an complete API call. + stack.add( + (next, _context) => async (args) => { + // Increment counter + middleware.counter++; + + // Call next middleware + return await next(args); + }, + { tags: ['ROUND_TRIP'] } + ); + }, +}; \ No newline at end of file diff --git a/packages/parameters/tests/helpers/tinyLogger.ts b/packages/parameters/tests/helpers/tinyLogger.ts new file mode 100644 index 0000000000..0effa723ae --- /dev/null +++ b/packages/parameters/tests/helpers/tinyLogger.ts @@ -0,0 +1,21 @@ +import { Console } from 'console'; + +/** + * A tiny logger that logs to stdout and stderr. + * + * This is used to log the results of the function code during the integration tests. + * We use this instead of the global console object because we want to log pure JSON objects. + * In Node.js runtimes, AWS Lambda usually patches the global console object to inject some + * metadata like the request ID. This is not desirable in our case because we want to log pure + * JSON objects to stdout and stderr. + * + * This allows us to get the logs when invoking the function and parse them to verify that + * the function code is working as expected. + */ +export class TinyLogger { + private console = new Console({ stdout: process.stdout, stderr: process.stderr }); + + public log(message: unknown): void { + this.console.log(JSON.stringify(message)); + } +} \ No newline at end of file From c4899ba4990e64daebd1512e99ff02b988a9d124 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 12 Jan 2023 20:27:37 +0100 Subject: [PATCH 2/6] tests: DynamoDBProvider e2e tests --- .../tests/e2e/basicFeatures.dynamoDBProvider.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts b/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts index 5c9b0ee45c..d55df80321 100644 --- a/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts +++ b/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts @@ -41,7 +41,6 @@ const lambdaFunctionCodeFile = 'basicFeatures.dynamoDBProvider.test.functionCode const dynamoDBClient = new DynamoDBClient({}); const invocationCount = 1; -// const startTime = new Date(); // Parameters to be used by Parameters in the Lambda function const tableGet = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-Get'); @@ -60,7 +59,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: nodejs18x`, () => let invocationLogs: InvocationLogs[]; beforeAll(async () => { - // GIVEN a stack + // Create a stack with a Lambda function stack = createStackWithLambdaFunction({ app: integTestApp, stackName: stackName, @@ -135,6 +134,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: nodejs18x`, () => ddbTabelGetMultipleCustomKeys, ])); + // Deploy the stack await deployStack(integTestApp, stack); // Seed tables with test data From b393928e85709544e30059d17530132f985a0233 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 13 Jan 2023 17:33:18 +0100 Subject: [PATCH 3/6] tests: DynamoDBProvider e2e tests --- packages/parameters/package.json | 8 ++++---- ...dynamoDBProvider.class.test.functionCode.ts} | 2 +- ...r.test.ts => dynamoDBProvider.class.test.ts} | 17 +++++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) rename packages/parameters/tests/e2e/{basicFeatures.dynamoDBProvider.test.functionCode.ts => dynamoDBProvider.class.test.functionCode.ts} (97%) rename packages/parameters/tests/e2e/{basicFeatures.dynamoDBProvider.test.ts => dynamoDBProvider.class.test.ts} (95%) diff --git a/packages/parameters/package.json b/packages/parameters/package.json index bc1bf31e9b..32c9d56d69 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -13,9 +13,9 @@ "commit": "commit", "test": "npm run test:unit", "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "test:e2e:nodejs14x": "echo \"Not implemented\"", - "test:e2e:nodejs16x": "echo \"Not implemented\"", - "test:e2e:nodejs18x": "echo \"Not implemented\"", + "test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e", + "test:e2e:nodejs16x": "RUNTIME=nodejs16x jest --group=e2e", + "test:e2e:nodejs18x": "RUNTIME=nodejs18x jest --group=e2e", "test:e2e": "echo \"Not implemented\"", "watch": "jest --watch", "build": "tsc", @@ -62,4 +62,4 @@ "dependencies": { "@aws-sdk/util-base64-node": "^3.209.0" } -} +} \ No newline at end of file diff --git a/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.functionCode.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts similarity index 97% rename from packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.functionCode.ts rename to packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts index 3455621c98..b6049956f2 100644 --- a/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.functionCode.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts @@ -147,7 +147,7 @@ export const handler = async (_event: unknown, _context: Context): Promise logger.log({ test: 'get-multiple-auto-transform', value: - `${typeof parametersValuesAuto.config},${parametersValuesAuto.key}` // should be object,string + `${typeof parametersValuesAuto['config.json']},${typeof parametersValuesAuto['key.binary']}` // should be object,string }); } catch (err) { logger.log({ diff --git a/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts similarity index 95% rename from packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts rename to packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts index d55df80321..f8c1278938 100644 --- a/packages/parameters/tests/e2e/basicFeatures.dynamoDBProvider.test.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts @@ -36,17 +36,17 @@ if (!isValidRuntimeKey(runtime)) { const uuid = v4(); const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider'); const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider'); -const lambdaFunctionCodeFile = 'basicFeatures.dynamoDBProvider.test.functionCode.ts'; +const lambdaFunctionCodeFile = 'dynamoDBProvider.class.test.functionCode.ts'; const dynamoDBClient = new DynamoDBClient({}); const invocationCount = 1; // Parameters to be used by Parameters in the Lambda function -const tableGet = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-Get'); -const tableGetMultiple = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-GetMultiple'); -const tableGetCustomkeys = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-GetCustomKeys'); -const tableGetMultipleCustomkeys = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider-Table-GetMultipleCustomKeys'); +const tableGet = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Table-Get'); +const tableGetMultiple = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Table-GetMultiple'); +const tableGetCustomkeys = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Table-GetCustomKeys'); +const tableGetMultipleCustomkeys = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Table-GetMultipleCustomKeys'); const keyAttr = 'key'; const sortAttr = 'sort'; const valueAttr = 'val'; @@ -54,7 +54,7 @@ const valueAttr = 'val'; const integTestApp = new App(); let stack: Stack; -describe(`parameters E2E tests (dynamoDBProvider) for runtime: nodejs18x`, () => { +describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () => { let invocationLogs: InvocationLogs[]; @@ -116,6 +116,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: nodejs18x`, () => const ddbTabelGetMultipleCustomKeys = createDynamoDBTable({ stack, id: 'Table-getMultipleCustomKeys', + tableName: tableGetMultipleCustomkeys, partitionKey: { name: keyAttr, type: AttributeType.STRING @@ -306,7 +307,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: nodejs18x`, () => it('should retrieve a single parameter with binary transform', async () => { const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[4]); + const testLog = InvocationLogs.parseFunctionLog(logs[5]); expect(testLog).toStrictEqual({ test: 'get-binary-transform', @@ -319,7 +320,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: nodejs18x`, () => it('should retrieve multiple parameters with auto transforms', async () => { const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[5]); + const testLog = InvocationLogs.parseFunctionLog(logs[6]); expect(testLog).toStrictEqual({ test: 'get-multiple-auto-transform', From 4abf5fa3838118962847adddc88184e75611831e Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 13 Jan 2023 17:51:41 +0100 Subject: [PATCH 4/6] chore: added todo comments --- .../tests/e2e/dynamoDBProvider.class.test.functionCode.ts | 4 +++- packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts index b6049956f2..f8d80314ae 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts @@ -1,6 +1,7 @@ import { Context } from 'aws-lambda'; import { DynamoDBProvider } from '../../src/dynamodb'; import { TinyLogger } from '../helpers/tinyLogger'; +// # TODO: Uncomment code below once #1222 is fixed /* import { middleware } from '../helpers/sdkMiddlewareRequestCounter'; import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; @@ -38,8 +39,8 @@ const providerGetMultipleCustomKeys = new DynamoDBProvider({ sortAttr, valueAttr, }); +// # TODO: Uncomment code below once #1222 is fixed /* -# TODO: Uncomment this code once the following issue is fixed: #1222 // Provider test 8, 9 const customClient = new DynamoDBClient({}); providerWithMiddleware.middlewareStack.use(middleware); @@ -156,6 +157,7 @@ export const handler = async (_event: unknown, _context: Context): Promise }); } + // # TODO: Uncomment code below once #1222 is fixed /** * Test 8 - get a parameter twice, second time should be cached * diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts index f8c1278938..4d6d88b118 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts @@ -329,6 +329,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = }); + // TODO: implement tests for the following cases once #1222 is merged: // Test 8 - get a parameter twice, second time should be cached // Test 9 - get a parameter once more but with forceFetch = true From 0fdd5003ab9ab432a4a7b731594d12e75e4d1de6 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 13 Jan 2023 17:53:46 +0100 Subject: [PATCH 5/6] chore: updated group comment --- packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts index 4d6d88b118..3409886ed5 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts @@ -1,7 +1,7 @@ /** - * Test DynamoDBProvider basic features + * Test DynamoDBProvider class * - * @group e2e/parameters/dynamodb/basicFeatures + * @group e2e/parameters/dynamodb/class */ import path from 'path'; import { Tracing } from 'aws-cdk-lib/aws-lambda'; From 3527b3f56c161296171bcaca3b577c0c438cd9c7 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 13 Jan 2023 18:26:41 +0100 Subject: [PATCH 6/6] chore: added comments explaining tests --- .../tests/e2e/dynamoDBProvider.class.test.ts | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts index 3409886ed5..ce8f7f50d4 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts @@ -54,6 +54,82 @@ const valueAttr = 'val'; const integTestApp = new App(); let stack: Stack; +/** + * This test suite deploys a CDK stack with a Lambda function and a number of DynamoDB tables. + * The function code uses the Parameters utility to retrieve values from the DynamoDB tables. + * It then logs the values to CloudWatch Logs as JSON. + * + * Once the stack is deployed, the Lambda function is invoked and the CloudWatch Logs are retrieved. + * The logs are then parsed and the values are compared to the expected values in each test case. + * + * The tables are populated with data before the Lambda function is invoked. These tables and values + * allow to test the different use cases of the DynamoDBProvider class. + * + * The tables are: + * + * - Table-Get: a table with a single partition key (id) and attribute (value) + * +-----------------+----------------------+ + * | id | value | + * +-----------------+----------------------+ + * | my-param | foo | + * | my-param-json | "{\"foo\": \"bar\"}" | + * | my-param-binary | "YmF6" | + * +-----------------+----------------------+ + * + * - Table-GetMultiple: a table with a partition key (id) and a sort key (sk) and attribute (value) + * +-------------------+---------------+----------------------+ + * | id | sk | value | + * +-------------------+---------------+----------------------+ + * | my-params | config | bar | + * | my-params | key | baz | + * | my-encoded-params | config.json | "{\"foo\": \"bar\"}" | + * | my-encoded-params | config.binary | "YmF6" | + * +-------------------+---------------+----------------------+ + * + * - Table-GetCustomKeys: a table with a single partition key (key) and attribute (val) + * +-----------------+----------------------+ + * | key | val | + * +-----------------+----------------------+ + * | my-param | foo | + * +-----------------+----------------------+ + * + * - Table-GetMultipleCustomKeys: a table with a partition key (key) and a sort key (sort) and attribute (val) + * +-------------------+---------------+----------------------+ + * | key | sort | val | + * +-------------------+---------------+----------------------+ + * | my-params | config | bar | + * | my-params | key | baz | + * +-------------------+---------------+----------------------+ + * + * The tests are: + * + * Test 1 + * Get a single parameter with default options (keyAttr: 'id', valueAttr: 'value') from table Table-Get + * + * Test 2 + * Get multiple parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') from table Table-GetMultiple + * + * Test 3 + * Get a single parameter with custom options (keyAttr: 'key', valueAttr: 'val') from table Table-GetCustomKeys + * + * Test 4 + * Get multiple parameters with custom options (keyAttr: 'key', sortAttr: 'sort', valueAttr: 'val') from table Table-GetMultipleCustomKeys + * + * Test 5 + * Get a single JSON parameter with default options (keyAttr: 'id', valueAttr: 'value') and transform from table Table-Get + * + * Test 6 + * Get a single binrary parameter with default options (keyAttr: 'id', valueAttr: 'value') and transform it from table Table-Get + * + * Test 7 + * Get multiple JSON and binary parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') and transform them automatically from table Table-GetMultiple + * + * Test 8 + * Get a parameter twice and check that the value is cached. This uses a custom SDK client that counts the number of calls to DynamoDB. + * + * Test 9 + * Get a cached parameter and force retrieval. This also uses the same custom SDK client that counts the number of calls to DynamoDB. + */ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () => { let invocationLogs: InvocationLogs[];