Skip to content

tests(idempotency): add utility to workspace unit tests and CI #1160

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

Merged
merged 13 commits into from
Nov 24, 2022
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: |
npm run build -w packages/commons
npm run build -w packages/logger & npm run build -w packages/tracer & npm run build -w packages/metrics -w packages/parameters
npm run build -w packages/logger & npm run build -w packages/tracer & npm run build -w packages/metrics & npm run build -w packages/parameters & npm run build -w packages/idempotency
- name: Run linting
run: npm run lint -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters
run: npm run lint -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters -w packages/idempotency
- name: Run unit tests
run: npm t -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters
run: npm t -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters -w packages/idempotency
check-examples:
runs-on: ubuntu-latest
env:
Expand Down
1,021 changes: 857 additions & 164 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"packages/logger",
"packages/metrics",
"packages/tracer",
"packages/parameters"
"packages/parameters",
"packages/idempotency"
],
"scripts": {
"init-environment": "husky install",
Expand Down Expand Up @@ -84,4 +85,4 @@
"dependencies": {
"hosted-git-info": "^5.0.0"
}
}
}
6 changes: 5 additions & 1 deletion packages/idempotency/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
displayName: {
name: 'AWS Lambda Powertools utility: IDEMPOTENCY',
color: 'cyan',
color: 'blue',
},
'runner': 'groups',
'preset': 'ts-jest',
Expand All @@ -25,6 +25,7 @@ module.exports = {
'coveragePathIgnorePatterns': [
'/node_modules/',
'/types/',
'src/makeFunctionIdempotent.ts', // TODO: remove this once makeFunctionIdempotent is implemented
],
'coverageThreshold': {
'global': {
Expand All @@ -38,5 +39,8 @@ module.exports = {
'json-summary',
'text',
'lcov'
],
'setupFiles': [
'<rootDir>/tests/helpers/populateEnvironmentVariables.ts'
]
};
16 changes: 8 additions & 8 deletions packages/idempotency/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
"commit": "commit",
"test": "npm run test:unit",
"test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose",
"test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e",
"test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e",
"test:e2e:nodejs16x": "RUNTIME=nodejs16x jest --group=e2e",
"test:e2e": "jest --group=e2e",
"test:e2e:nodejs12x": "echo \"Not implemented\"",
"test:e2e:nodejs14x": "echo \"Not implemented\"",
"test:e2e:nodejs16x": "echo \"Not implemented\"",
"test:e2e": "echo \"Not implemented\"",
"watch": "jest --watch --group=unit",
"build": "tsc",
"lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests",
"lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests",
"package": "mkdir -p dist/ && npm pack && mv *.tgz dist/",
"package-bundle": "../../package-bundler.sh logger-bundle ./dist",
"package-bundle": "../../package-bundler.sh idempotency-bundle ./dist",
"prepare": "npm run build",
"postversion": "git push --tags"
"postversion": "echo \"Not implemented\""
},
"homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/idempotency#readme",
"license": "MIT",
Expand All @@ -42,7 +42,7 @@
"url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues"
},
"dependencies": {
"@aws-lambda-powertools/commons": "^1.2.1",
"@aws-lambda-powertools/commons": "^1.4.1",
"@aws-sdk/lib-dynamodb": "^3.170.0"
},
"keywords": [
Expand All @@ -56,4 +56,4 @@
"aws-sdk-client-mock": "^2.0.0",
"aws-sdk-client-mock-jest": "^2.0.0"
}
}
}
25 changes: 0 additions & 25 deletions packages/idempotency/src/EnvironmentVariablesService.ts

This file was deleted.

13 changes: 13 additions & 0 deletions packages/idempotency/src/config/ConfigServiceInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
interface ConfigServiceInterface {

get(name: string): string

getServiceName(): string

getFunctionName(): string

}

export {
ConfigServiceInterface
};
36 changes: 36 additions & 0 deletions packages/idempotency/src/config/EnvironmentVariablesService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ConfigServiceInterface } from './ConfigServiceInterface';
import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons';

/**
* Class EnvironmentVariablesService
*
* This class is used to return environment variables that are available in the runtime of
* the current Lambda invocation.
* These variables can be a mix of runtime environment variables set by AWS and
* variables that can be set by the developer additionally.
*
* @class
* @extends {CommonEnvironmentVariablesService}
* @implements {ConfigServiceInterface}
* @see https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
* @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/#environment-variables
*/
class EnvironmentVariablesService extends CommonEnvironmentVariablesService implements ConfigServiceInterface {

// Reserved environment variables
private functionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME';

/**
* It returns the value of the AWS_LAMBDA_FUNCTION_NAME environment variable.
*
* @returns {string}
*/
public getFunctionName(): string {
return this.get(this.functionNameVariable);
}

}

export {
EnvironmentVariablesService
};
1 change: 1 addition & 0 deletions packages/idempotency/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './EnvironmentVariablesService';
7 changes: 4 additions & 3 deletions packages/idempotency/src/makeFunctionIdempotent.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AnyFunction } from './types/AnyFunction';
import { IdempotencyOptions } from './IdempotencyOptions';
import type { AnyFunction } from './types/AnyFunction';
import type { IdempotencyOptions } from './types/IdempotencyOptions';

const makeFunctionIdempotent = <U>(
fn: AnyFunction<U>,
_options: IdempotencyOptions
// TODO: revisit this with a more specific type if possible
/* eslint-disable @typescript-eslint/no-explicit-any */
): (...args: Array<any>) => Promise<U | void> => (...args) => fn(...args);

export { makeFunctionIdempotent };
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DynamoDB, DynamoDBServiceException } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument, GetCommandOutput } from '@aws-sdk/lib-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import type { GetCommandOutput } from '@aws-sdk/lib-dynamodb';
import { DynamoPersistenceConstructorOptions } from '../types/DynamoPersistenceConstructorOptions';
import { IdempotencyItemAlreadyExistsError, IdempotencyItemNotFoundError } from '../Exceptions';
import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus';
Expand All @@ -23,7 +24,7 @@ class DynamoDBPersistenceLayer extends PersistenceLayer {
this.statusAttr = constructorOptions.statusAttr ?? 'status';
this.expiryAttr = constructorOptions.expiryAttr ?? 'expiration';
this.inProgressExpiryAttr = constructorOptions.inProgressExpiryAttr ?? 'in_progress_expiry_attr';
this.dataAttr = constructorOptions.data_attr ?? 'data';
this.dataAttr = constructorOptions.dataAttr ?? 'data';
}

protected async _deleteRecord(record: IdempotencyRecord): Promise<void> {
Expand Down
29 changes: 20 additions & 9 deletions packages/idempotency/src/persistence/PersistenceLayer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BinaryToTextEncoding, createHash, Hash } from 'crypto';
import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus';
import { EnvironmentVariablesService } from '../EnvironmentVariablesService';
import type { PersistenceLayerConfigureOptions } from '../types/PersistenceLayer';
import { EnvironmentVariablesService } from '../config';
import { IdempotencyRecord } from './IdempotencyRecord';
import { PersistenceLayerInterface } from './PersistenceLayerInterface';

Expand All @@ -10,22 +11,32 @@ abstract class PersistenceLayer implements PersistenceLayerInterface {
private envVarsService!: EnvironmentVariablesService;

private expiresAfterSeconds: number;

private functionName: string = '';


private hashDigest: BinaryToTextEncoding;

private hashFunction: string;

private idempotencyKeyPrefix: string;

public constructor() {
this.setEnvVarsService();
this.expiresAfterSeconds = 60 * 60; //one hour is the default expiration
this.hashFunction = 'md5';
this.hashDigest = 'base64';

this.idempotencyKeyPrefix = this.getEnvVarsService().getFunctionName();

}
public configure(functionName: string = ''): void {
this.functionName = this.getEnvVarsService().getLambdaFunctionName() + '.' + functionName;

/**
* Configures the persistence layer by passing the name of the idempotent function. This will be used
* in the prefix of the idempotency key
*
* @param {PersistenceLayerConfigureOptions} options - configuration object for the persistence layer
*/
public configure(options?: PersistenceLayerConfigureOptions): void {
if (options?.functionName && options.functionName.trim() !== '') {
this.idempotencyKeyPrefix = `${this.idempotencyKeyPrefix}.${options.functionName}`;
}
}

/**
Expand Down Expand Up @@ -136,7 +147,7 @@ abstract class PersistenceLayer implements PersistenceLayerInterface {
console.warn('No data found for idempotency key');
}

return this.functionName + '#' + this.generateHash(JSON.stringify(data));
return `${this.idempotencyKeyPrefix}#${this.generateHash(JSON.stringify(data))}`;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { IdempotencyRecord } from './IdempotencyRecord';
import type { PersistenceLayerConfigureOptions } from '../types/PersistenceLayer';

interface PersistenceLayerInterface {
configure(functionName: string): void
configure(options?: PersistenceLayerConfigureOptions): void
saveInProgress(data: unknown): Promise<void>
saveSuccess(data: unknown, result: unknown): Promise<void>
deleteRecord(data: unknown): Promise<void>
Expand Down
2 changes: 1 addition & 1 deletion packages/idempotency/src/persistence/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './DynamoDbPersistenceLayer';
export * from './DynamoDBPersistenceLayer';
export * from './PersistenceLayer';
export * from './PersistenceLayerInterface';
export * from './IdempotencyRecord';
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type DynamoPersistenceConstructorOptions = {
statusAttr?: string
expiryAttr?: string
inProgressExpiryAttr?: string
data_attr?: string
dataAttr?: string
};

export {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PersistenceLayer } from './persistence/PersistenceLayer';
import { PersistenceLayer } from '../persistence/PersistenceLayer';

type IdempotencyOptions = {
dataKeywordArgument: string
Expand Down
7 changes: 7 additions & 0 deletions packages/idempotency/src/types/PersistenceLayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type PersistenceLayerConfigureOptions = {
functionName?: string
};

export {
PersistenceLayerConfigureOptions
};
3 changes: 2 additions & 1 deletion packages/idempotency/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './AnyFunction';
export * from './IdempotencyRecordStatus';
export * from './IdempotencyRecordStatus';
export * from './PersistenceLayer';
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
// Reserved variables
process.env.AWS_REGION = 'us-east-1';
process.env._X_AMZN_TRACE_ID = '1-abcdef12-3456abcdef123456abcdef12';
process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function';
process.env.AWS_EXECUTION_ENV = 'nodejs16.x';
process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128';
process.env.AWS_REGION = 'eu-west-1';
process.env._HANDLER = 'index.handler';
Comment on lines +2 to +7
Copy link
Contributor

Choose a reason for hiding this comment

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

(Question) Do we really need all of these env vars? Wouldn't only AWS_LAMBDA_FUNCTION_NAME be enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We use only the name at the moment, and we'll be using also the region in the e2e tests.

I have added them mostly for consistency so that all the unit tests in the repo have the same environment. Mostly for cognitive load.

What do you think?

34 changes: 0 additions & 34 deletions packages/idempotency/tests/unit/EnvironmentVariableService.test.ts

This file was deleted.

Loading