Skip to content

improv(tests): adopt new aws cdk cli library #1624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
410 changes: 409 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"docs/snippets",
"layers",
"examples/cdk",
"examples/sam"
"examples/sam",
"packages/testing"
],
"scripts": {
"init-environment": "husky install",
Expand Down
28 changes: 28 additions & 0 deletions packages/testing/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
displayName: {
name: 'Powertools for AWS Lambda (TypeScript) utility: TESTING',
color: 'blue',
},
runner: 'groups',
preset: 'ts-jest',
transform: {
'^.+\\.ts?$': 'ts-jest',
},
moduleFileExtensions: ['js', 'ts'],
collectCoverageFrom: ['**/src/**/*.ts', '!**/node_modules/**'],
testMatch: ['**/?(*.)+(spec|test).ts'],
roots: ['<rootDir>/src', '<rootDir>/tests'],
testPathIgnorePatterns: ['/node_modules/'],
testEnvironment: 'node',
coveragePathIgnorePatterns: ['/node_modules/', '/types/'],
coverageThreshold: {
global: {
statements: 100,
branches: 100,
functions: 100,
lines: 100,
},
},
coverageReporters: ['json-summary', 'text', 'lcov'],
setupFiles: ['<rootDir>/tests/helpers/populateEnvironmentVariables.ts'],
};
49 changes: 49 additions & 0 deletions packages/testing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@aws-lambda-powertools/testing-utils",
"version": "1.11.1",
"description": "A package containing utilities to test your serverless workloads",
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com"
},
"private": true,
"devDependencies": {
"@aws-cdk/cli-lib-alpha": "^2.87.0-alpha.0",
"aws-cdk-lib": "^2.73.0"
},
"scripts": {
"test": "npm run test:unit",
"test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose",
"test:e2e": "echo 'Not implemented'",
"watch": "jest --watch",
"build": "tsc",
"lint": "eslint --ext .ts,.js --no-error-on-unmatched-pattern .",
"lint-fix": "eslint --fix --ext .ts,.js --no-error-on-unmatched-pattern .",
"prebuild": "rimraf ./lib",
"prepack": "node ../../.github/scripts/release_patch_package_json.js ."
},
"lint-staged": {
"*.{js,ts}": "npm run lint-fix"
},
"repository": {
"type": "git",
"url": "git+https://github.com/aws-powertools/powertools-lambda-typescript.git"
},
"files": [
"lib"
],
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"keywords": [
"aws",
"lambda",
"powertools",
"testing",
"serverless"
],
"license": "MIT-0",
"bugs": {
"url": "https://github.com/aws-powertools/powertools-lambda-typescript/issues"
},
"homepage": "https://github.com/aws-powertools/powertools-lambda-typescript/tree/main/packages/testing#readme"
}
215 changes: 215 additions & 0 deletions packages/testing/src/TestCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import type { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import type { Table } from 'aws-cdk-lib/aws-dynamodb';
import type { IStringParameter, StringParameter } from 'aws-cdk-lib/aws-ssm';
import { PolicyStatement, Effect } from 'aws-cdk-lib/aws-iam';

class NodeJsFunction {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name overload can be confusing, I see this class is internal, so it won't be exposed for maintainers when they write tests. Still, I'd propose to distinguish between our resource wrapper and the actual resource we hold.

public functionName: string;
public ref: NodejsFunction;

public constructor(name: string, ref: NodejsFunction) {
this.functionName = name;
this.ref = ref;
}
}

class DynamoDBTable {
public ref: Table;
public tableName: string;
readonly #envVariableName?: string;

public constructor(name: string, ref: Table, envVariableName?: string) {
this.tableName = name;
this.ref = ref;
this.#envVariableName = envVariableName;
}

public get envVariableName(): string {
return this.#envVariableName ?? 'TABLE_NAME';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this check to the constructor?

}
}

abstract class SSMResource {
public parameterName: string;
public ref: StringParameter | IStringParameter;
protected readonly envVarName?: string;

public constructor(
name: string,
ref: StringParameter | IStringParameter,
envVariableName?: string
) {
this.parameterName = name;
this.ref = ref;
this.envVarName = envVariableName;
}
}

class SsmSecureString extends SSMResource {
public constructor(
name: string,
ref: IStringParameter,
envVariableName?: string
) {
super(name, ref, envVariableName);
}

public get envVariableName(): string {
return this.envVarName ?? 'SECURE_STRING_NAME';
}
}

class SsmString extends SSMResource {
public constructor(
name: string,
ref: StringParameter,
envVariableName?: string
) {
super(name, ref, envVariableName);
}

public get envVariableName(): string {
return this.envVarName ?? 'SSM_STRING_NAME';
}
}

/**
* A test case that can be added to a test stack.
*/
class TestCase {
public testName: string;
#dynamodb?: DynamoDBTable;
#function?: NodeJsFunction;
#ssmSecureString?: SsmSecureString;
#ssmString?: SsmString;

public constructor(testName: string) {
this.testName = testName;
}

/**
* The NodejsFunction that is associated with this test case.
*/
public set function(fn: NodeJsFunction) {
if (this.#dynamodb) {
this.#grantAccessToDynamoDBTableAndSetEnv(fn, this.#dynamodb);
}
if (this.#ssmSecureString) {
this.#grantAccessToSsmeStringAndSetEnv(fn, this.#ssmSecureString);
}
if (this.#ssmString) {
this.#grantAccessToSsmeStringAndSetEnv(fn, this.#ssmString);
}
this.#function = fn;
}

/**
* Get the NodejsFunction that is associated with this test case.
*/
public get function(): NodeJsFunction {
if (!this.#function) throw new Error('This test case has no function.');

return this.#function;
}

/**
* The DynamoDB table that is associated with this test case.
*/
public set dynamodb(table: DynamoDBTable) {
if (this.#function) {
this.#grantAccessToDynamoDBTableAndSetEnv(this.#function, table);
}
this.#dynamodb = table;
}

/**
* Get the DynamoDB table that is associated with this test case.
*/
public get dynamodb(): DynamoDBTable {
if (!this.#dynamodb)
throw new Error('This test case has no DynamoDB table.');

return this.#dynamodb;
}

/**
* Grant access to the DynamoDB table and set the environment variable to
* the table name.
*
* @param fn - The function to grant access to the table and set the environment variable.
* @param table - The table to grant access to and identified by the environment variable.
*/
#grantAccessToDynamoDBTableAndSetEnv = (
fn: NodeJsFunction,
table: DynamoDBTable
): void => {
table.ref.grantReadWriteData(fn.ref);
fn.ref.addEnvironment(table.envVariableName, table.ref.tableName);
};

/**
* The SSM SecureString that is associated with this test case.
*/
public set ssmSecureString(parameter: SsmSecureString) {
if (this.#function) {
this.#grantAccessToSsmeStringAndSetEnv(this.#function, parameter);
}
this.#ssmSecureString = parameter;
}

/**
* Get the SSM SecureString that is associated with this test case.
*/
public get ssmSecureString(): SsmSecureString {
if (!this.#ssmSecureString)
throw new Error('This test case has no SSM SecureString.');

return this.#ssmSecureString;
}

/**
* The SSM String that is associated with this test case.
*/
public set ssmString(parameter: SsmString) {
if (this.#function) {
this.#grantAccessToSsmeStringAndSetEnv(this.#function, parameter);
}
this.#ssmString = parameter;
}

/**
* Get the SSM String that is associated with this test case.
*/
public get ssmString(): SsmString {
if (!this.#ssmString) throw new Error('This test case has no SSM String.');

return this.#ssmString;
}

/**
* Grant access to the SSM String and set the environment variable to
* the parameter name.
*
* @param fn - The function to grant access to the parameter and set the environment variable.
* @param parameter - The parameter to grant access to and identified by the environment variable.
*/
#grantAccessToSsmeStringAndSetEnv = (
fn: NodeJsFunction,
parameter: SsmSecureString | SsmString
): void => {
fn.ref.addEnvironment(parameter.envVariableName, parameter.parameterName);
// Grant access also to the path of the parameter
fn.ref.addToRolePolicy(
new PolicyStatement({
effect: Effect.ALLOW,
actions: ['ssm:GetParametersByPath'],
resources: [
parameter.ref.parameterArn.split(':').slice(0, -1).join(':'),
],
})
);
parameter.ref.grantRead(fn.ref);
};
}

export { TestCase, NodeJsFunction, DynamoDBTable, SsmSecureString, SsmString };
Loading