Skip to content

Commit 34ef180

Browse files
dreamorosiijemmy
andauthoredNov 24, 2022
tests(idempotency): add utility to workspace unit tests and CI (#1160)
* chore: fix package.lock & package * ci: add idempotency to ci checks on PRs * refactor: minor styling changes to folder structure, exports, types * test: unit tests style + added missing * chore: temporarily excluded makeFunctionIdempotent from coverage report * refactor: rename npx DynamoDbPersistenceLayer to DynamoDBPersistenceLayer * refactor: rename npx DynamoDbPersistenceLayer to DynamoDBPersistenceLayer * refactor: rename npx DynamoDbPersistenceLayer to DynamoDBPersistenceLayer * chore: revert changes to PersistenceLayerInterface * Trigger Build * Update packages/idempotency/src/persistence/PersistenceLayer.ts Co-authored-by: ijemmy <[email protected]> Co-authored-by: ijemmy <[email protected]>
·
v2.26.1v1.5.0
1 parent cb91374 commit 34ef180

25 files changed

+1290
-444
lines changed
 

‎.github/workflows/reusable-run-linting-check-and-unit-tests.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ jobs:
4242
if: steps.cache-node-modules.outputs.cache-hit == 'true'
4343
run: |
4444
npm run build -w packages/commons
45-
npm run build -w packages/logger & npm run build -w packages/tracer & npm run build -w packages/metrics -w packages/parameters
45+
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
4646
- name: Run linting
47-
run: npm run lint -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters
47+
run: npm run lint -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters -w packages/idempotency
4848
- name: Run unit tests
49-
run: npm t -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters
49+
run: npm t -w packages/commons -w packages/logger -w packages/tracer -w packages/metrics -w packages/parameters -w packages/idempotency
5050
check-examples:
5151
runs-on: ubuntu-latest
5252
env:

‎package-lock.json‎

Lines changed: 857 additions & 164 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"packages/logger",
1010
"packages/metrics",
1111
"packages/tracer",
12-
"packages/parameters"
12+
"packages/parameters",
13+
"packages/idempotency"
1314
],
1415
"scripts": {
1516
"init-environment": "husky install",
@@ -84,4 +85,4 @@
8485
"dependencies": {
8586
"hosted-git-info": "^5.0.0"
8687
}
87-
}
88+
}

‎packages/idempotency/jest.config.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module.exports = {
22
displayName: {
33
name: 'AWS Lambda Powertools utility: IDEMPOTENCY',
4-
color: 'cyan',
4+
color: 'blue',
55
},
66
'runner': 'groups',
77
'preset': 'ts-jest',
@@ -25,6 +25,7 @@ module.exports = {
2525
'coveragePathIgnorePatterns': [
2626
'/node_modules/',
2727
'/types/',
28+
'src/makeFunctionIdempotent.ts', // TODO: remove this once makeFunctionIdempotent is implemented
2829
],
2930
'coverageThreshold': {
3031
'global': {
@@ -38,5 +39,8 @@ module.exports = {
3839
'json-summary',
3940
'text',
4041
'lcov'
42+
],
43+
'setupFiles': [
44+
'<rootDir>/tests/helpers/populateEnvironmentVariables.ts'
4145
]
4246
};

‎packages/idempotency/package.json‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@
1313
"commit": "commit",
1414
"test": "npm run test:unit",
1515
"test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose",
16-
"test:e2e:nodejs12x": "RUNTIME=nodejs12x jest --group=e2e",
17-
"test:e2e:nodejs14x": "RUNTIME=nodejs14x jest --group=e2e",
18-
"test:e2e:nodejs16x": "RUNTIME=nodejs16x jest --group=e2e",
19-
"test:e2e": "jest --group=e2e",
16+
"test:e2e:nodejs12x": "echo \"Not implemented\"",
17+
"test:e2e:nodejs14x": "echo \"Not implemented\"",
18+
"test:e2e:nodejs16x": "echo \"Not implemented\"",
19+
"test:e2e": "echo \"Not implemented\"",
2020
"watch": "jest --watch --group=unit",
2121
"build": "tsc",
2222
"lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests",
2323
"lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests",
2424
"package": "mkdir -p dist/ && npm pack && mv *.tgz dist/",
25-
"package-bundle": "../../package-bundler.sh logger-bundle ./dist",
25+
"package-bundle": "../../package-bundler.sh idempotency-bundle ./dist",
2626
"prepare": "npm run build",
27-
"postversion": "git push --tags"
27+
"postversion": "echo \"Not implemented\""
2828
},
2929
"homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/master/packages/idempotency#readme",
3030
"license": "MIT",
@@ -42,7 +42,7 @@
4242
"url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues"
4343
},
4444
"dependencies": {
45-
"@aws-lambda-powertools/commons": "^1.2.1",
45+
"@aws-lambda-powertools/commons": "^1.4.1",
4646
"@aws-sdk/lib-dynamodb": "^3.170.0"
4747
},
4848
"keywords": [
@@ -56,4 +56,4 @@
5656
"aws-sdk-client-mock": "^2.0.0",
5757
"aws-sdk-client-mock-jest": "^2.0.0"
5858
}
59-
}
59+
}

‎packages/idempotency/src/EnvironmentVariablesService.ts‎

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
interface ConfigServiceInterface {
2+
3+
get(name: string): string
4+
5+
getServiceName(): string
6+
7+
getFunctionName(): string
8+
9+
}
10+
11+
export {
12+
ConfigServiceInterface
13+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { ConfigServiceInterface } from './ConfigServiceInterface';
2+
import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons';
3+
4+
/**
5+
* Class EnvironmentVariablesService
6+
*
7+
* This class is used to return environment variables that are available in the runtime of
8+
* the current Lambda invocation.
9+
* These variables can be a mix of runtime environment variables set by AWS and
10+
* variables that can be set by the developer additionally.
11+
*
12+
* @class
13+
* @extends {CommonEnvironmentVariablesService}
14+
* @implements {ConfigServiceInterface}
15+
* @see https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
16+
* @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/#environment-variables
17+
*/
18+
class EnvironmentVariablesService extends CommonEnvironmentVariablesService implements ConfigServiceInterface {
19+
20+
// Reserved environment variables
21+
private functionNameVariable = 'AWS_LAMBDA_FUNCTION_NAME';
22+
23+
/**
24+
* It returns the value of the AWS_LAMBDA_FUNCTION_NAME environment variable.
25+
*
26+
* @returns {string}
27+
*/
28+
public getFunctionName(): string {
29+
return this.get(this.functionNameVariable);
30+
}
31+
32+
}
33+
34+
export {
35+
EnvironmentVariablesService
36+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './EnvironmentVariablesService';
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { AnyFunction } from './types/AnyFunction';
3-
import { IdempotencyOptions } from './IdempotencyOptions';
1+
import type { AnyFunction } from './types/AnyFunction';
2+
import type { IdempotencyOptions } from './types/IdempotencyOptions';
43

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

1011
export { makeFunctionIdempotent };

‎packages/idempotency/src/persistence/DynamoDbPersistenceLayer.ts‎ renamed to ‎packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { DynamoDB, DynamoDBServiceException } from '@aws-sdk/client-dynamodb';
2-
import { DynamoDBDocument, GetCommandOutput } from '@aws-sdk/lib-dynamodb';
2+
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
3+
import type { GetCommandOutput } from '@aws-sdk/lib-dynamodb';
34
import { DynamoPersistenceConstructorOptions } from '../types/DynamoPersistenceConstructorOptions';
45
import { IdempotencyItemAlreadyExistsError, IdempotencyItemNotFoundError } from '../Exceptions';
56
import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus';
@@ -23,7 +24,7 @@ class DynamoDBPersistenceLayer extends PersistenceLayer {
2324
this.statusAttr = constructorOptions.statusAttr ?? 'status';
2425
this.expiryAttr = constructorOptions.expiryAttr ?? 'expiration';
2526
this.inProgressExpiryAttr = constructorOptions.inProgressExpiryAttr ?? 'in_progress_expiry_attr';
26-
this.dataAttr = constructorOptions.data_attr ?? 'data';
27+
this.dataAttr = constructorOptions.dataAttr ?? 'data';
2728
}
2829

2930
protected async _deleteRecord(record: IdempotencyRecord): Promise<void> {

‎packages/idempotency/src/persistence/PersistenceLayer.ts‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BinaryToTextEncoding, createHash, Hash } from 'crypto';
22
import { IdempotencyRecordStatus } from '../types/IdempotencyRecordStatus';
3-
import { EnvironmentVariablesService } from '../EnvironmentVariablesService';
3+
import type { PersistenceLayerConfigureOptions } from '../types/PersistenceLayer';
4+
import { EnvironmentVariablesService } from '../config';
45
import { IdempotencyRecord } from './IdempotencyRecord';
56
import { PersistenceLayerInterface } from './PersistenceLayerInterface';
67

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

1213
private expiresAfterSeconds: number;
13-
14-
private functionName: string = '';
15-
14+
1615
private hashDigest: BinaryToTextEncoding;
17-
16+
1817
private hashFunction: string;
18+
19+
private idempotencyKeyPrefix: string;
1920

2021
public constructor() {
2122
this.setEnvVarsService();
2223
this.expiresAfterSeconds = 60 * 60; //one hour is the default expiration
2324
this.hashFunction = 'md5';
2425
this.hashDigest = 'base64';
25-
26+
this.idempotencyKeyPrefix = this.getEnvVarsService().getFunctionName();
27+
2628
}
27-
public configure(functionName: string = ''): void {
28-
this.functionName = this.getEnvVarsService().getLambdaFunctionName() + '.' + functionName;
29+
30+
/**
31+
* Configures the persistence layer by passing the name of the idempotent function. This will be used
32+
* in the prefix of the idempotency key
33+
*
34+
* @param {PersistenceLayerConfigureOptions} options - configuration object for the persistence layer
35+
*/
36+
public configure(options?: PersistenceLayerConfigureOptions): void {
37+
if (options?.functionName && options.functionName.trim() !== '') {
38+
this.idempotencyKeyPrefix = `${this.idempotencyKeyPrefix}.${options.functionName}`;
39+
}
2940
}
3041

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

139-
return this.functionName + '#' + this.generateHash(JSON.stringify(data));
150+
return `${this.idempotencyKeyPrefix}#${this.generateHash(JSON.stringify(data))}`;
140151
}
141152

142153
/**

‎packages/idempotency/src/persistence/PersistenceLayerInterface.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { IdempotencyRecord } from './IdempotencyRecord';
2+
import type { PersistenceLayerConfigureOptions } from '../types/PersistenceLayer';
23

34
interface PersistenceLayerInterface {
4-
configure(functionName: string): void
5+
configure(options?: PersistenceLayerConfigureOptions): void
56
saveInProgress(data: unknown): Promise<void>
67
saveSuccess(data: unknown, result: unknown): Promise<void>
78
deleteRecord(data: unknown): Promise<void>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * from './DynamoDbPersistenceLayer';
1+
export * from './DynamoDBPersistenceLayer';
22
export * from './PersistenceLayer';
33
export * from './PersistenceLayerInterface';
44
export * from './IdempotencyRecord';

‎packages/idempotency/src/types/DynamoPersistenceConstructorOptions.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type DynamoPersistenceConstructorOptions = {
44
statusAttr?: string
55
expiryAttr?: string
66
inProgressExpiryAttr?: string
7-
data_attr?: string
7+
dataAttr?: string
88
};
99

1010
export {

‎packages/idempotency/src/IdempotencyOptions.ts‎ renamed to ‎packages/idempotency/src/types/IdempotencyOptions.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PersistenceLayer } from './persistence/PersistenceLayer';
1+
import { PersistenceLayer } from '../persistence/PersistenceLayer';
22

33
type IdempotencyOptions = {
44
dataKeywordArgument: string
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type PersistenceLayerConfigureOptions = {
2+
functionName?: string
3+
};
4+
5+
export {
6+
PersistenceLayerConfigureOptions
7+
};
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './AnyFunction';
2-
export * from './IdempotencyRecordStatus';
2+
export * from './IdempotencyRecordStatus';
3+
export * from './PersistenceLayer';
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
// Reserved variables
2-
process.env.AWS_REGION = 'us-east-1';
2+
process.env._X_AMZN_TRACE_ID = '1-abcdef12-3456abcdef123456abcdef12';
3+
process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function';
4+
process.env.AWS_EXECUTION_ENV = 'nodejs16.x';
5+
process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128';
6+
process.env.AWS_REGION = 'eu-west-1';
7+
process.env._HANDLER = 'index.handler';

‎packages/idempotency/tests/unit/EnvironmentVariableService.test.ts‎

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Test EnvironmentVariableService class
3+
*
4+
* @group unit/idempotency/all
5+
*/
6+
import { EnvironmentVariablesService } from '../../../src/config';
7+
8+
describe('Class: EnvironmentVariableService', () => {
9+
10+
const ENVIRONMENT_VARIABLES = process.env;
11+
12+
beforeEach(() => {
13+
jest.resetModules();
14+
process.env = { ...ENVIRONMENT_VARIABLES };
15+
});
16+
17+
afterEach(() => {
18+
process.env = ENVIRONMENT_VARIABLES;
19+
});
20+
21+
describe('Method: getFunctionName', () => {
22+
23+
test('When called, it gets the Lambda function name from the environment variable', () => {
24+
25+
// Prepare
26+
const expectedName = process.env.AWS_LAMBDA_FUNCTION_NAME;
27+
28+
// Act
29+
const lambdaName = new EnvironmentVariablesService().getFunctionName();
30+
31+
// Assess
32+
expect(lambdaName).toEqual(expectedName);
33+
34+
});
35+
36+
test('When called without the environment variable set, it returns an empty string', () => {
37+
38+
// Prepare
39+
delete process.env.AWS_LAMBDA_FUNCTION_NAME;
40+
41+
// Act
42+
const lambdaName = new EnvironmentVariablesService().getFunctionName();
43+
44+
// Assess
45+
expect(lambdaName).toEqual('');
46+
47+
});
48+
49+
});
50+
});
Lines changed: 118 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1+
/**
2+
* Test DynamoDBPersistenceLayer class
3+
*
4+
* @group unit/idempotency/all
5+
*/
16
import { DeleteCommand, DynamoDBDocument, GetCommand, PutCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb';
27
import { mockClient } from 'aws-sdk-client-mock';
38
import 'aws-sdk-client-mock-jest';
49
import { IdempotencyItemAlreadyExistsError, IdempotencyItemNotFoundError } from '../../../src/Exceptions';
5-
import { DynamoDBPersistenceLayer } from '../../../src/persistence/DynamoDbPersistenceLayer';
10+
import { DynamoDBPersistenceLayer } from '../../../src/persistence/DynamoDBPersistenceLayer';
611
import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord';
12+
import { DynamoPersistenceConstructorOptions } from '../../../src/types/DynamoPersistenceConstructorOptions';
713
import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus';
814

9-
/**
10-
* Test DynamoDBPersistenceLayer class
11-
*
12-
* @group unit/idempotency/all
13-
*/
15+
describe('Class: DynamoDBPersistenceLayer', () => {
16+
17+
const dummyTableName = 'someTable';
18+
const dummyKey = 'someKey';
19+
20+
class TestDynamoDBPersistenceLayer extends DynamoDBPersistenceLayer {
1421

15-
describe('Class: DynamoDbPersistenceLayer', () => {
16-
class TestDynamoPersistenceLayer extends DynamoDBPersistenceLayer {
1722
public _deleteRecord(record: IdempotencyRecord): Promise<void> {
1823
return super._deleteRecord(record);
1924
}
@@ -29,31 +34,77 @@ describe('Class: DynamoDbPersistenceLayer', () => {
2934
public _updateRecord(record: IdempotencyRecord): Promise<void> {
3035
return super._updateRecord(record);
3136
}
37+
3238
}
3339

3440
beforeEach(() => {
3541
jest.clearAllMocks();
3642
});
3743

44+
describe('Method: constructor', () => {
45+
46+
test('when instantiated with minimum options it creates an instance with default values', () => {
47+
48+
// Prepare
49+
const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName });
50+
51+
// Act / Assess
52+
expect(persistenceLayer).toEqual(expect.objectContaining({
53+
tableName: dummyTableName,
54+
keyAttr: 'id',
55+
statusAttr: 'status',
56+
expiryAttr: 'expiration',
57+
inProgressExpiryAttr: 'in_progress_expiry_attr',
58+
dataAttr: 'data'
59+
}));
60+
61+
});
62+
63+
test('when instantiated with specific options it creates an instance with correct values', () => {
64+
65+
// Prepare
66+
const testDynamoDBPersistenceLayerOptions: DynamoPersistenceConstructorOptions = {
67+
tableName: dummyTableName,
68+
keyAttr: dummyKey,
69+
statusAttr: 'someStatusAttr',
70+
expiryAttr: 'someExpiryAttr',
71+
inProgressExpiryAttr: 'someInProgressExpiryAttr',
72+
dataAttr: 'someDataAttr'
73+
};
74+
const persistenceLayer = new TestDynamoDBPersistenceLayer(testDynamoDBPersistenceLayerOptions);
75+
76+
// Act / Assess
77+
expect(persistenceLayer).toEqual(expect.objectContaining({
78+
tableName: dummyTableName,
79+
keyAttr: dummyKey,
80+
statusAttr: testDynamoDBPersistenceLayerOptions.statusAttr,
81+
expiryAttr: testDynamoDBPersistenceLayerOptions.expiryAttr,
82+
inProgressExpiryAttr: testDynamoDBPersistenceLayerOptions.inProgressExpiryAttr,
83+
dataAttr: testDynamoDBPersistenceLayerOptions.dataAttr
84+
}));
85+
86+
});
87+
88+
});
89+
3890
describe('Method: _putRecord', () => {
91+
3992
const currentDateInMilliseconds = 1000;
4093
const currentDateInSeconds = 1;
4194

4295
beforeEach(() => {
4396
jest.spyOn(Date, 'now').mockReturnValue(currentDateInMilliseconds);
4497
});
4598

46-
test('when called with a record that meets conditions, it puts record in dynamo table', async () => {
47-
// Prepare
48-
const tableName = 'tableName';
49-
const persistenceLayer = new TestDynamoPersistenceLayer({ tableName });
99+
test('when called with a record that meets conditions, it puts record in DynamoDB table', async () => {
50100

51-
const key = 'key';
101+
// Prepare
102+
const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName });
52103
const status = IdempotencyRecordStatus.EXPIRED;
53104
const expiryTimestamp = 0;
54105
const inProgressExpiryTimestamp = 0;
55106
const record = new IdempotencyRecord({
56-
idempotencyKey: key,
107+
idempotencyKey: dummyKey,
57108
status,
58109
expiryTimestamp,
59110
inProgressExpiryTimestamp
@@ -65,104 +116,84 @@ describe('Class: DynamoDbPersistenceLayer', () => {
65116

66117
// Assess
67118
expect(dynamoClient).toReceiveCommandWith(PutCommand, {
68-
TableName: tableName,
69-
Item: { 'id': key, 'expiration': expiryTimestamp, status: status },
119+
TableName: dummyTableName,
120+
Item: { 'id': dummyKey, 'expiration': expiryTimestamp, status: status },
70121
ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' },
71122
ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS },
72123
ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress'
73124
});
125+
74126
});
75127

76128
test('when called with a record that fails any condition, it throws IdempotencyItemAlreadyExistsError', async () => {
129+
77130
// Prepare
78-
const tableName = 'tableName';
79-
const persistenceLayer = new TestDynamoPersistenceLayer({ tableName });
80-
81-
const key = 'key';
131+
const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName });
82132
const status = IdempotencyRecordStatus.EXPIRED;
83133
const expiryTimestamp = 0;
84134
const inProgressExpiryTimestamp = 0;
85135
const record = new IdempotencyRecord({
86-
idempotencyKey: key,
136+
idempotencyKey: dummyKey,
87137
status,
88138
expiryTimestamp,
89139
inProgressExpiryTimestamp
90140
});
91-
92141
const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).rejects({ name: 'ConditionalCheckFailedException' });
93142

94-
// Act
95-
let error: unknown;
96-
try {
97-
await persistenceLayer._putRecord(record);
98-
} catch (e){
99-
error = e;
100-
}
101-
102-
// Assess
143+
// Act / Assess
144+
await expect(persistenceLayer._putRecord(record)).rejects.toThrowError(IdempotencyItemAlreadyExistsError);
103145
expect(dynamoClient).toReceiveCommandWith(PutCommand, {
104-
TableName: tableName,
105-
Item: { 'id': key, 'expiration': expiryTimestamp, status: status },
146+
TableName: dummyTableName,
147+
Item: { 'id': dummyKey, 'expiration': expiryTimestamp, status: status },
106148
ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' },
107149
ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS },
108150
ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress'
109151
});
110-
expect(error).toBeInstanceOf(IdempotencyItemAlreadyExistsError);
152+
111153
});
112154

113155
test('when encountering an unknown error, it throws the causing error', async () => {
114-
// Prepare
115-
const tableName = 'tableName';
116-
const persistenceLayer = new TestDynamoPersistenceLayer({ tableName });
117156

118-
const key = 'key';
157+
// Prepare
158+
const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName });
119159
const status = IdempotencyRecordStatus.EXPIRED;
120160
const expiryTimestamp = 0;
121161
const inProgressExpiryTimestamp = 0;
122162
const record = new IdempotencyRecord({
123-
idempotencyKey: key,
163+
idempotencyKey: dummyKey,
124164
status,
125165
expiryTimestamp,
126166
inProgressExpiryTimestamp
127167
});
128-
129168
const dynamoClient = mockClient(DynamoDBDocument).on(PutCommand).rejects(new Error());
130169

131-
// Act
132-
let error: unknown;
133-
try {
134-
await persistenceLayer._putRecord(record);
135-
} catch (e){
136-
error = e;
137-
}
138-
139-
// Assess
170+
// Act / Assess
171+
await expect(persistenceLayer._putRecord(record)).rejects.toThrow();
140172
expect(dynamoClient).toReceiveCommandWith(PutCommand, {
141-
TableName: tableName,
142-
Item: { 'id': key, 'expiration': expiryTimestamp, status: status },
173+
TableName: dummyTableName,
174+
Item: { 'id': dummyKey, 'expiration': expiryTimestamp, status: status },
143175
ExpressionAttributeNames: { '#id': 'id', '#expiry': 'expiration', '#status': 'status' },
144176
ExpressionAttributeValues: { ':now': currentDateInSeconds, ':inprogress': IdempotencyRecordStatus.INPROGRESS },
145177
ConditionExpression: 'attribute_not_exists(#id) OR #expiry < :now OR NOT #status = :inprogress'
146178
});
147-
expect(error).toBe(error);
179+
148180
});
181+
149182
});
150183

151184
describe('Method: _getRecord', () => {
152-
test('when called with a record whose key exists, it gets the correct record', async () => {
153-
// Prepare
154-
const tableName = 'tableName';
155-
const persistenceLayer = new TestDynamoPersistenceLayer({ tableName });
156185

157-
const key = 'key';
186+
test('when called with a record whose key exists, it gets the correct record', async () => {
158187

188+
// Prepare
189+
const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName });
159190
const status = IdempotencyRecordStatus.INPROGRESS;
160191
const expiryTimestamp = 10;
161192
const inProgressExpiryTimestamp = 10;
162193
const responseData = {};
163194
const dynamoClient = mockClient(DynamoDBDocument).on(GetCommand).resolves({
164195
Item: {
165-
id: key,
196+
id: dummyKey,
166197
status,
167198
'expiration': expiryTimestamp,
168199
'in_progress_expiry_attr': inProgressExpiryTimestamp,
@@ -172,65 +203,55 @@ describe('Class: DynamoDbPersistenceLayer', () => {
172203
jest.spyOn(Date, 'now').mockReturnValue(0);
173204

174205
// Act
175-
const record: IdempotencyRecord = await persistenceLayer._getRecord(key);
206+
const record: IdempotencyRecord = await persistenceLayer._getRecord(dummyKey);
176207

177208
// Assess
178209
expect(dynamoClient).toReceiveCommandWith(GetCommand, {
179-
TableName: tableName,
210+
TableName: dummyTableName,
180211
Key: {
181-
id: key
212+
id: dummyKey
182213
},
183214
ConsistentRead: true
184215
});
185216
expect(record.getStatus()).toEqual(IdempotencyRecordStatus.INPROGRESS);
186-
expect(record.idempotencyKey).toEqual(key);
217+
expect(record.idempotencyKey).toEqual(dummyKey);
187218
expect(record.inProgressExpiryTimestamp).toEqual(inProgressExpiryTimestamp);
188219
expect(record.responseData).toEqual(responseData);
189220
expect(record.expiryTimestamp).toEqual(expiryTimestamp);
190221
});
191222

192223
test('when called with a record whose key does not exist, it throws IdempotencyItemNotFoundError', async () => {
193-
// Prepare
194-
const tableName = 'tableName';
195-
const persistenceLayer = new TestDynamoPersistenceLayer({ tableName });
196-
197-
const key = 'key';
198224

225+
// Prepare
226+
const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName });
199227
const dynamoClient = mockClient(DynamoDBDocument).on(GetCommand).resolves({ Item: undefined });
200228
jest.spyOn(Date, 'now').mockReturnValue(0);
201229

202-
// Act
203-
let error: unknown;
204-
try {
205-
await persistenceLayer._getRecord(key);
206-
} catch (e){
207-
error = e;
208-
}
209-
210-
// Assess
230+
// Act / Assess
231+
await expect(persistenceLayer._getRecord(dummyKey)).rejects.toThrowError(IdempotencyItemNotFoundError);
211232
expect(dynamoClient).toReceiveCommandWith(GetCommand, {
212-
TableName: tableName,
233+
TableName: dummyTableName,
213234
Key: {
214-
id: key
235+
id: dummyKey
215236
},
216237
ConsistentRead: true
217238
});
218-
expect(error).toBeInstanceOf(IdempotencyItemNotFoundError);
239+
219240
});
241+
220242
});
221243

222244
describe('Method: _updateRecord', () => {
245+
223246
test('when called to update a record, it resolves successfully', async () => {
224-
// Prepare
225-
const tableName = 'tableName';
226-
const persistenceLayer = new TestDynamoPersistenceLayer({ tableName });
227247

228-
const key = 'key';
248+
// Prepare
249+
const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName });
229250
const status = IdempotencyRecordStatus.EXPIRED;
230251
const expiryTimestamp = 0;
231252
const inProgressExpiryTimestamp = 0;
232253
const record = new IdempotencyRecord({
233-
idempotencyKey: key,
254+
idempotencyKey: dummyKey,
234255
status,
235256
expiryTimestamp,
236257
inProgressExpiryTimestamp
@@ -242,27 +263,28 @@ describe('Class: DynamoDbPersistenceLayer', () => {
242263

243264
// Assess
244265
expect(dynamoClient).toReceiveCommandWith(UpdateCommand, {
245-
TableName: tableName,
246-
Key: { id: key },
266+
TableName: dummyTableName,
267+
Key: { id: dummyKey },
247268
UpdateExpression: 'SET #status = :status, #expiry = :expiry',
248269
ExpressionAttributeNames: { '#status': 'status', '#expiry': 'expiration' },
249270
ExpressionAttributeValues: { ':status': IdempotencyRecordStatus.EXPIRED,':expiry': expiryTimestamp },
250271
});
272+
251273
});
274+
252275
});
253276

254277
describe('Method: _deleteRecord', () => {
278+
255279
test('when called with a valid record, record is deleted', async () => {
256-
// Prepare
257-
const tableName = 'tableName';
258-
const persistenceLayer = new TestDynamoPersistenceLayer({ tableName });
259280

260-
const key = 'key';
281+
// Prepare
282+
const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName });
261283
const status = IdempotencyRecordStatus.EXPIRED;
262284
const expiryTimestamp = 0;
263285
const inProgressExpiryTimestamp = 0;
264286
const record = new IdempotencyRecord({
265-
idempotencyKey: key,
287+
idempotencyKey: dummyKey,
266288
status,
267289
expiryTimestamp,
268290
inProgressExpiryTimestamp
@@ -274,9 +296,12 @@ describe('Class: DynamoDbPersistenceLayer', () => {
274296

275297
// Assess
276298
expect(dynamoClient).toReceiveCommandWith(DeleteCommand, {
277-
TableName: tableName,
278-
Key: { id: key }
299+
TableName: dummyTableName,
300+
Key: { id: dummyKey }
279301
});
302+
280303
});
304+
281305
});
306+
282307
});

‎packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { IdempotencyInvalidStatusError } from '../../../src/Exceptions';
2-
import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord';
3-
import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus';
41
/**
52
* Test IdempotencyRecord class
63
*
74
* @group unit/idempotency/all
85
*/
6+
import { IdempotencyInvalidStatusError } from '../../../src/Exceptions';
7+
import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord';
8+
import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus';
9+
910
const mockIdempotencyKey = '123';
1011
const mockData = undefined;
1112
const mockInProgressExpiry = 123;

‎packages/idempotency/tests/unit/persistence/PersistenceLayer.test.ts‎

Lines changed: 141 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
* @group unit/idempotency/all
55
*/
66
import { createHash, Hash } from 'crypto';
7-
import { EnvironmentVariablesService } from '../../../src/EnvironmentVariablesService';
87
import { IdempotencyRecord, PersistenceLayer } from '../../../src/persistence';
98
import { IdempotencyRecordStatus } from '../../../src/types/IdempotencyRecordStatus';
109

@@ -16,7 +15,10 @@ const cryptoUpdateMock = jest.fn();
1615
const cryptoDigestMock = jest.fn();
1716
const mockDigest = 'hashDigest';
1817

19-
describe('Class: Persistence Layer', ()=> {
18+
describe('Class: PersistenceLayer', () => {
19+
20+
const dummyData = 'someData';
21+
const idempotentFunctionName = 'foo';
2022

2123
const deleteRecord = jest.fn();
2224
const getRecord = jest.fn();
@@ -34,102 +36,146 @@ describe('Class: Persistence Layer', ()=> {
3436
protected _updateRecord = updateRecord;
3537
}
3638

37-
describe('Method: saveInProgress', ()=> {
38-
beforeEach(()=> {
39-
putRecord.mockClear();
40-
(createHash as jest.MockedFunction<typeof createHash>).mockReturnValue(
41-
{
42-
update: cryptoUpdateMock,
43-
digest: cryptoDigestMock.mockReturnValue(mockDigest)
44-
} as unknown as Hash
45-
);
39+
describe('Method: configure', () => {
40+
41+
test('when called without options it maintains the default value for the key prefix', () => {
42+
43+
// Prepare
44+
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
45+
persistenceLayer.configure();
46+
47+
expect(persistenceLayer).toEqual(expect.objectContaining({
48+
idempotencyKeyPrefix: process.env.AWS_LAMBDA_FUNCTION_NAME,
49+
}));
50+
4651
});
4752

48-
test('When called, it saves an IN_PROGRESS idempotency record via _putRecord()', async ()=> {
49-
const data = 'someData';
53+
test('when called with an empty option object it maintains the default value for the key prefix', () => {
54+
55+
// Prepare
5056
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
57+
persistenceLayer.configure({});
5158

52-
await persistenceLayer.saveInProgress(data);
59+
expect(persistenceLayer).toEqual(expect.objectContaining({
60+
idempotencyKeyPrefix: process.env.AWS_LAMBDA_FUNCTION_NAME,
61+
}));
5362

54-
const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0];
55-
expect(savedIdempotencyRecord.getStatus()).toBe(IdempotencyRecordStatus.INPROGRESS);
5663
});
5764

58-
test('When called, it creates an idempotency key from the function name and a digest of the md5 hash of the data', async ()=> {
59-
const data = 'someData';
60-
const lambdaFunctionName = 'LambdaName';
61-
jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName);
65+
test('when called with an empty string as functionName it maintains the default value for the key prefix', () => {
6266

63-
const functionName = 'functionName';
67+
// Prepare
68+
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
69+
persistenceLayer.configure({ functionName: '' });
6470

65-
const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest;
71+
expect(persistenceLayer).toEqual(expect.objectContaining({
72+
idempotencyKeyPrefix: process.env.AWS_LAMBDA_FUNCTION_NAME,
73+
}));
74+
75+
});
76+
77+
test('when called with a valid functionName it concatenates the key prefix correctly', () => {
78+
79+
// Prepare
80+
const expectedIdempotencyKeyPrefix = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}`;
6681
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
67-
persistenceLayer.configure(functionName);
82+
persistenceLayer.configure({ functionName: idempotentFunctionName });
6883

69-
await persistenceLayer.saveInProgress(data);
84+
expect(persistenceLayer).toEqual(expect.objectContaining({
85+
idempotencyKeyPrefix: expectedIdempotencyKeyPrefix
86+
}));
7087

71-
const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0];
88+
});
7289

73-
expect(createHash).toHaveBeenCalledWith(
74-
expect.stringMatching('md5'),
75-
);
76-
expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data));
77-
expect(cryptoDigestMock).toHaveBeenCalledWith(
78-
expect.stringMatching('base64')
90+
});
91+
92+
describe('Method: saveInProgress', () => {
93+
94+
beforeEach(() => {
95+
putRecord.mockClear();
96+
(createHash as jest.MockedFunction<typeof createHash>).mockReturnValue(
97+
{
98+
update: cryptoUpdateMock,
99+
digest: cryptoDigestMock.mockReturnValue(mockDigest)
100+
} as unknown as Hash
79101
);
80-
expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey);
102+
81103
});
82104

83-
test('When called without a function name, it creates an idempotency key from the Lambda name only and a digest of the md5 hash of the data', async ()=> {
84-
const data = 'someData';
85-
const lambdaFunctionName = 'LambdaName';
86-
jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName);
105+
test('when called, it saves an IN_PROGRESS idempotency record via _putRecord()', async () => {
87106

88-
const expectedIdempotencyKey = lambdaFunctionName + '.' + '#' + mockDigest;
107+
// Prepare
89108
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
90-
persistenceLayer.configure();
91109

92-
await persistenceLayer.saveInProgress(data);
110+
// Act
111+
await persistenceLayer.saveInProgress(dummyData);
93112

113+
// Assess
94114
const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0];
115+
expect(savedIdempotencyRecord.getStatus()).toBe(IdempotencyRecordStatus.INPROGRESS);
116+
117+
});
95118

119+
test('when called, it creates an idempotency key from the function name and a digest of the md5 hash of the data', async () => {
120+
121+
// Prepare
122+
const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`;
123+
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
124+
persistenceLayer.configure({ functionName: idempotentFunctionName });
125+
126+
// Act
127+
await persistenceLayer.saveInProgress(dummyData);
128+
129+
// Assess
130+
const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0];
96131
expect(createHash).toHaveBeenCalledWith(
97132
expect.stringMatching('md5'),
98133
);
99-
expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data));
134+
expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(dummyData));
100135
expect(cryptoDigestMock).toHaveBeenCalledWith(
101136
expect.stringMatching('base64')
102137
);
103138
expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey);
139+
104140
});
105141

106-
test('When called, it sets the expiry timestamp to one hour in the future', async ()=> {
142+
test('when called, it sets the expiry timestamp to one hour in the future', async () => {
143+
144+
// Prepare
107145
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
108-
const data = 'someData';
109146
const currentMillisTime = 3000;
110147
const currentSeconds = currentMillisTime / 1000;
111148
const oneHourSeconds = 60 * 60;
112149
jest.spyOn(Date, 'now').mockReturnValue(currentMillisTime);
113150

114-
await persistenceLayer.saveInProgress(data);
151+
// Act
152+
await persistenceLayer.saveInProgress(dummyData);
115153

154+
// Assess
116155
const savedIdempotencyRecord: IdempotencyRecord = putRecord.mock.calls[0][0];
117156
expect(savedIdempotencyRecord.expiryTimestamp).toEqual(currentSeconds + oneHourSeconds);
118157

119158
});
120159

121-
test('When called without data, it logs a warning', async ()=> {
122-
const consoleSpy = jest.spyOn(console, 'warn');
160+
test('when called without data, it logs a warning', async () => {
161+
162+
// Prepare
163+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => ({}));
123164
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
124165

166+
// Act
125167
await persistenceLayer.saveInProgress('');
168+
169+
// Assess
126170
expect(consoleSpy).toHaveBeenCalled();
171+
127172
});
128173

129174
});
130175

131-
describe('Method: saveSuccess', ()=> {
132-
beforeEach(()=> {
176+
describe('Method: saveSuccess', () => {
177+
178+
beforeEach(() => {
133179
updateRecord.mockClear();
134180
(createHash as jest.MockedFunction<typeof createHash>).mockReturnValue(
135181
{
@@ -139,64 +185,69 @@ describe('Class: Persistence Layer', ()=> {
139185
);
140186
});
141187

142-
test('When called, it updates the idempotency record status to COMPLETED', async () => {
143-
const data = 'someData';
188+
test('when called, it updates the idempotency record status to COMPLETED', async () => {
189+
190+
// Prepare
144191
const result = {};
145192
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
146193

147-
await persistenceLayer.saveSuccess(data, result);
194+
// Act
195+
await persistenceLayer.saveSuccess(dummyData, result);
148196

197+
// Assess
149198
const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0];
150199
expect(savedIdempotencyRecord.getStatus()).toBe(IdempotencyRecordStatus.COMPLETED);
151200

152201
});
153202

154-
test('When called, it generates the idempotency key from the function name and a digest of the md5 hash of the data', async ()=> {
155-
const data = 'someData';
156-
const result = {};
157-
const lambdaFunctionName = 'LambdaName';
158-
jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName);
159-
160-
const functionName = 'functionName';
203+
test('when called, it generates the idempotency key from the function name and a digest of the md5 hash of the data', async () => {
161204

162-
const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest;
205+
// Prepare
206+
const result = {};
207+
const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`;
163208
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
164-
persistenceLayer.configure(functionName);
209+
persistenceLayer.configure({ functionName: idempotentFunctionName });
165210

166-
await persistenceLayer.saveSuccess(data, result);
211+
// Act
212+
await persistenceLayer.saveSuccess(dummyData, result);
167213

214+
// Assess
168215
const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0];
169-
170216
expect(createHash).toHaveBeenCalledWith(
171217
expect.stringMatching('md5'),
172218
);
173-
expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(data));
219+
expect(cryptoUpdateMock).toHaveBeenCalledWith(expect.stringMatching(dummyData));
174220
expect(cryptoDigestMock).toHaveBeenCalledWith(
175221
expect.stringMatching('base64')
176222
);
177223
expect(savedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey);
224+
178225
});
179226

180-
test('When called, it sets the expiry timestamp to one hour in the future', async ()=> {
227+
test('when called, it sets the expiry timestamp to one hour in the future', async () => {
228+
229+
// Prepare
181230
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
182-
const data = 'someData';
183231
const result = {};
184232
const currentMillisTime = 3000;
185233
const currentSeconds = currentMillisTime / 1000;
186234
const oneHourSeconds = 60 * 60;
187235
jest.spyOn(Date, 'now').mockReturnValue(currentMillisTime);
188236

189-
await persistenceLayer.saveSuccess(data, result);
237+
// Act
238+
await persistenceLayer.saveSuccess(dummyData, result);
190239

240+
// Assess
191241
const savedIdempotencyRecord: IdempotencyRecord = updateRecord.mock.calls[0][0];
192242
expect(savedIdempotencyRecord.expiryTimestamp).toEqual(currentSeconds + oneHourSeconds);
193243

194244
});
195245

196246
});
197247

198-
describe('Method: getRecord', ()=> {
199-
beforeEach(()=> {
248+
describe('Method: getRecord', () => {
249+
250+
beforeEach(() => {
200251
putRecord.mockClear();
201252
(createHash as jest.MockedFunction<typeof createHash>).mockReturnValue(
202253
{
@@ -205,24 +256,26 @@ describe('Class: Persistence Layer', ()=> {
205256
} as unknown as Hash
206257
);
207258
});
208-
test('When called, it gets the record for the idempotency key for the data passed in', ()=> {
209-
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
210-
const data = 'someData';
211-
const lambdaFunctionName = 'LambdaName';
212-
jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName);
213259

214-
const functionName = 'functionName';
215-
const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest;
216-
persistenceLayer.configure(functionName);
260+
test('when called, it gets the record for the idempotency key for the data passed in', () => {
217261

218-
persistenceLayer.getRecord(data);
262+
// Prepare
263+
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
264+
const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`;
265+
persistenceLayer.configure({ functionName: idempotentFunctionName });
266+
267+
// Act
268+
persistenceLayer.getRecord(dummyData);
219269

270+
// Assess
220271
expect(getRecord).toHaveBeenCalledWith(expectedIdempotencyKey);
272+
221273
});
222274
});
223275

224-
describe('Method: deleteRecord', ()=> {
225-
beforeEach(()=> {
276+
describe('Method: deleteRecord', () => {
277+
278+
beforeEach(() => {
226279
putRecord.mockClear();
227280
(createHash as jest.MockedFunction<typeof createHash>).mockReturnValue(
228281
{
@@ -232,20 +285,20 @@ describe('Class: Persistence Layer', ()=> {
232285
);
233286
});
234287

235-
test('When called, it deletes the record with the idempotency key for the data passed in', ()=> {
236-
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
237-
const data = 'someData';
238-
const lambdaFunctionName = 'LambdaName';
239-
jest.spyOn(EnvironmentVariablesService.prototype, 'getLambdaFunctionName').mockReturnValue(lambdaFunctionName);
288+
test('when called, it deletes the record with the idempotency key for the data passed in', () => {
240289

241-
const functionName = 'functionName';
242-
const expectedIdempotencyKey = lambdaFunctionName + '.' + functionName + '#' + mockDigest;
243-
persistenceLayer.configure(functionName);
290+
// Prepare
291+
const persistenceLayer: PersistenceLayer = new PersistenceLayerTestClass();
292+
const expectedIdempotencyKey = `${process.env.AWS_LAMBDA_FUNCTION_NAME}.${idempotentFunctionName}#${mockDigest}`;
293+
persistenceLayer.configure({ functionName: idempotentFunctionName });
244294

245-
persistenceLayer.deleteRecord(data);
295+
// Act
296+
persistenceLayer.deleteRecord(dummyData);
297+
298+
// Assess
246299
const deletedIdempotencyRecord: IdempotencyRecord = deleteRecord.mock.calls[0][0];
247-
248300
expect(deletedIdempotencyRecord.idempotencyKey).toEqual(expectedIdempotencyKey);
301+
249302
});
250303
});
251304
});

‎packages/parameters/package.json‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
"license": "MIT-0",
3131
"main": "./lib/index.js",
3232
"types": "./lib/index.d.ts",
33-
"devDependencies": {},
3433
"files": [
3534
"lib"
3635
],
@@ -41,7 +40,9 @@
4140
"bugs": {
4241
"url": "https://github.com/awslabs/aws-lambda-powertools-typescript/issues"
4342
},
44-
"dependencies": {},
43+
"dependencies": {
44+
"@aws-sdk/util-base64-node": "^3.209.0"
45+
},
4546
"keywords": [
4647
"aws",
4748
"lambda",
@@ -51,4 +52,4 @@
5152
"serverless",
5253
"nodejs"
5354
]
54-
}
55+
}

0 commit comments

Comments
 (0)
Please sign in to comment.