From 62a52a3de86efda156e260dc5993c418a6b3fca7 Mon Sep 17 00:00:00 2001 From: Alexander Melnyk Date: Wed, 21 Jun 2023 19:54:33 +0200 Subject: [PATCH 1/5] fix in_progress_expiration timestamp in idempotency handler --- .../idempotency/src/IdempotencyHandler.ts | 3 +- ...akeFunctionIdempotent.test.FunctionCode.ts | 7 ++++ .../tests/e2e/makeFunctionIdempotent.test.ts | 9 +++++ .../tests/unit/IdempotencyHandler.test.ts | 34 +++++++++++++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/idempotency/src/IdempotencyHandler.ts b/packages/idempotency/src/IdempotencyHandler.ts index 7fece6ba25..07b0786732 100644 --- a/packages/idempotency/src/IdempotencyHandler.ts +++ b/packages/idempotency/src/IdempotencyHandler.ts @@ -141,7 +141,8 @@ export class IdempotencyHandler { try { await this.persistenceStore.saveInProgress( - this.functionPayloadToBeHashed + this.functionPayloadToBeHashed, + this.idempotencyConfig.lambdaContext?.getRemainingTimeInMillis() ); } catch (e) { if (e instanceof IdempotencyItemAlreadyExistsError) { diff --git a/packages/idempotency/tests/e2e/makeFunctionIdempotent.test.FunctionCode.ts b/packages/idempotency/tests/e2e/makeFunctionIdempotent.test.FunctionCode.ts index af2d3062c6..8e947f7950 100644 --- a/packages/idempotency/tests/e2e/makeFunctionIdempotent.test.FunctionCode.ts +++ b/packages/idempotency/tests/e2e/makeFunctionIdempotent.test.FunctionCode.ts @@ -2,6 +2,7 @@ import type { Context } from 'aws-lambda'; import { DynamoDBPersistenceLayer } from '../../src/persistence/DynamoDBPersistenceLayer'; import { makeFunctionIdempotent } from '../../src'; import { Logger } from '@aws-lambda-powertools/logger'; +import { IdempotencyConfig } from '../../src'; const IDEMPOTENCY_TABLE_NAME = process.env.IDEMPOTENCY_TABLE_NAME || 'table_name'; @@ -32,15 +33,19 @@ const processRecord = (record: Record): string => { return 'Processing done: ' + record['foo']; }; +const idempotencyConfig = new IdempotencyConfig({}); + const processIdempotently = makeFunctionIdempotent(processRecord, { persistenceStore: dynamoDBPersistenceLayer, dataKeywordArgument: 'foo', + config: idempotencyConfig, }); export const handler = async ( _event: EventRecords, _context: Context ): Promise => { + idempotencyConfig.registerLambdaContext(_context); for (const record of _event.records) { const result = await processIdempotently(record); logger.info(result.toString()); @@ -52,12 +57,14 @@ export const handler = async ( const processIdempotentlyCustomized = makeFunctionIdempotent(processRecord, { persistenceStore: ddbPersistenceLayerCustomized, dataKeywordArgument: 'foo', + config: idempotencyConfig, }); export const handlerCustomized = async ( _event: EventRecords, _context: Context ): Promise => { + idempotencyConfig.registerLambdaContext(_context); for (const record of _event.records) { const result = await processIdempotentlyCustomized(record); logger.info(result.toString()); diff --git a/packages/idempotency/tests/e2e/makeFunctionIdempotent.test.ts b/packages/idempotency/tests/e2e/makeFunctionIdempotent.test.ts index 8ee55021d9..cd4344b024 100644 --- a/packages/idempotency/tests/e2e/makeFunctionIdempotent.test.ts +++ b/packages/idempotency/tests/e2e/makeFunctionIdempotent.test.ts @@ -109,6 +109,7 @@ describe('Idempotency e2e test function wrapper, default settings', () => { { id: 3, foo: 'bar' }, ], }; + const invokeStart = Date.now(); await invokeFunction( functionNameDefault, 2, @@ -136,6 +137,10 @@ describe('Idempotency e2e test function wrapper, default settings', () => { }) ); expect(resultFirst?.Item?.data).toEqual('Processing done: bar'); + expect(resultFirst?.Item?.expiration).toBeGreaterThan(Date.now() / 1000); + expect(resultFirst?.Item?.in_progress_expiration).toBeGreaterThan( + invokeStart + ); expect(resultFirst?.Item?.status).toEqual('COMPLETED'); const resultSecond = await ddb.send( @@ -145,6 +150,10 @@ describe('Idempotency e2e test function wrapper, default settings', () => { }) ); expect(resultSecond?.Item?.data).toEqual('Processing done: baz'); + expect(resultSecond?.Item?.expiration).toBeGreaterThan(Date.now() / 1000); + expect(resultSecond?.Item?.in_progress_expiration).toBeGreaterThan( + invokeStart + ); expect(resultSecond?.Item?.status).toEqual('COMPLETED'); }, TEST_CASE_TIMEOUT diff --git a/packages/idempotency/tests/unit/IdempotencyHandler.test.ts b/packages/idempotency/tests/unit/IdempotencyHandler.test.ts index be0dada29e..5ff0558189 100644 --- a/packages/idempotency/tests/unit/IdempotencyHandler.test.ts +++ b/packages/idempotency/tests/unit/IdempotencyHandler.test.ts @@ -14,6 +14,7 @@ import { BasePersistenceLayer, IdempotencyRecord } from '../../src/persistence'; import { IdempotencyHandler } from '../../src/IdempotencyHandler'; import { IdempotencyConfig } from '../../src/'; import { MAX_RETRIES } from '../../src/constants'; +import { Context } from 'aws-lambda'; class PersistenceLayerTestClass extends BasePersistenceLayer { protected _deleteRecord = jest.fn(); @@ -247,6 +248,39 @@ describe('Class IdempotencyHandler', () => { expect(mockGetRecord).toHaveBeenCalledTimes(0); expect(mockSaveSuccessfulResult).toHaveBeenCalledTimes(0); }); + + test('when lambdaContext is registered, we pass it to saveInProgress', async () => { + const mockSaveInProgress = jest.spyOn( + mockIdempotencyOptions.persistenceStore, + 'saveInProgress' + ); + + const mockLambaContext: Context = { + getRemainingTimeInMillis(): number { + return 1000; // we expect this number to be passed to saveInProgress + }, + } as Context; + const idempotencyHandlerWithContext = new IdempotencyHandler({ + functionToMakeIdempotent: mockFunctionToMakeIdempotent, + functionPayloadToBeHashed: mockFunctionPayloadToBeHashed, + persistenceStore: mockIdempotencyOptions.persistenceStore, + fullFunctionPayload: mockFullFunctionPayload, + idempotencyConfig: new IdempotencyConfig({ + lambdaContext: mockLambaContext, + }), + }); + + mockFunctionToMakeIdempotent.mockImplementation(() => + Promise.resolve('result') + ); + + await expect(idempotencyHandlerWithContext.processIdempotency()).resolves; + + expect(mockSaveInProgress).toBeCalledWith( + mockFunctionPayloadToBeHashed, + mockLambaContext.getRemainingTimeInMillis() + ); + }); }); describe('Method: getFunctionResult', () => { From be77f073e6d9246be9e806ac22b6835bf43ff28e Mon Sep 17 00:00:00 2001 From: Alexander Melnyk Date: Thu, 22 Jun 2023 11:25:19 +0200 Subject: [PATCH 2/5] add lambda context mock to tests --- .../tests/unit/idempotentDecorator.test.ts | 41 +++++++++++++++---- .../tests/unit/makeFunctionIdempotent.test.ts | 34 ++++++++++++--- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/packages/idempotency/tests/unit/idempotentDecorator.test.ts b/packages/idempotency/tests/unit/idempotentDecorator.test.ts index 2e15f8c623..0a87ccc787 100644 --- a/packages/idempotency/tests/unit/idempotentDecorator.test.ts +++ b/packages/idempotency/tests/unit/idempotentDecorator.test.ts @@ -15,6 +15,7 @@ import { IdempotencyPersistenceLayerError, } from '../../src/Exceptions'; import { IdempotencyConfig } from '../../src'; +import { Context } from 'aws-lambda'; const mockSaveInProgress = jest .spyOn(BasePersistenceLayer.prototype, 'saveInProgress') @@ -26,6 +27,12 @@ const mockGetRecord = jest .spyOn(BasePersistenceLayer.prototype, 'getRecord') .mockImplementation(); +const mockLambaContext: Context = { + getRemainingTimeInMillis(): number { + return 1000; // we expect this number to be passed to saveInProgress + }, +} as Context; + class PersistenceLayerTestClass extends BasePersistenceLayer { protected _deleteRecord = jest.fn(); protected _getRecord = jest.fn(); @@ -38,6 +45,7 @@ const functionalityToDecorate = jest.fn(); class TestinClassWithLambdaHandler { @idempotentLambdaHandler({ persistenceStore: new PersistenceLayerTestClass(), + config: new IdempotencyConfig({ lambdaContext: mockLambaContext }), }) public testing(record: Record): string { functionalityToDecorate(record); @@ -54,6 +62,7 @@ class TestingClassWithFunctionDecorator { @idempotentFunction({ persistenceStore: new PersistenceLayerTestClass(), dataKeywordArgument: 'testingKey', + config: new IdempotencyConfig({ lambdaContext: mockLambaContext }), }) public proccessRecord(record: Record): string { functionalityToDecorate(record); @@ -76,7 +85,10 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(keyValueToBeSaved); + expect(mockSaveInProgress).toBeCalledWith( + keyValueToBeSaved, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will call the function that was decorated', () => { @@ -96,7 +108,10 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will call the function that was decorated', () => { @@ -129,7 +144,10 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -166,7 +184,10 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -199,7 +220,10 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -215,7 +239,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = class TestinClassWithLambdaHandlerWithConfig { @idempotentLambdaHandler({ persistenceStore: new PersistenceLayerTestClass(), - config: new IdempotencyConfig({}), + config: new IdempotencyConfig({ lambdaContext: mockLambaContext }), }) public testing(record: Record): string { functionalityToDecorate(record); @@ -237,7 +261,10 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then an IdempotencyPersistenceLayerError is thrown', () => { diff --git a/packages/idempotency/tests/unit/makeFunctionIdempotent.test.ts b/packages/idempotency/tests/unit/makeFunctionIdempotent.test.ts index 4310a648a1..e5fb2f6037 100644 --- a/packages/idempotency/tests/unit/makeFunctionIdempotent.test.ts +++ b/packages/idempotency/tests/unit/makeFunctionIdempotent.test.ts @@ -17,6 +17,8 @@ import { IdempotencyItemAlreadyExistsError, IdempotencyPersistenceLayerError, } from '../../src/Exceptions'; +import { IdempotencyConfig } from '../../src'; +import { Context } from 'aws-lambda'; const mockSaveInProgress = jest .spyOn(BasePersistenceLayer.prototype, 'saveInProgress') @@ -25,6 +27,12 @@ const mockGetRecord = jest .spyOn(BasePersistenceLayer.prototype, 'getRecord') .mockImplementation(); +const mockLambaContext: Context = { + getRemainingTimeInMillis(): number { + return 1000; // we expect this number to be passed to saveInProgress + }, +} as Context; + class PersistenceLayerTestClass extends BasePersistenceLayer { protected _deleteRecord = jest.fn(); protected _getRecord = jest.fn(); @@ -37,6 +45,7 @@ describe('Given a function to wrap', (functionToWrap = jest.fn()) => { describe('Given options for idempotency', (options: IdempotencyFunctionOptions = { persistenceStore: new PersistenceLayerTestClass(), dataKeywordArgument: 'testingKey', + config: new IdempotencyConfig({ lambdaContext: mockLambaContext }), }) => { const keyValueToBeSaved = 'thisWillBeSaved'; const inputRecord = { @@ -51,7 +60,10 @@ describe('Given a function to wrap', (functionToWrap = jest.fn()) => { }); test('Then it will save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(keyValueToBeSaved); + expect(mockSaveInProgress).toBeCalledWith( + keyValueToBeSaved, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will call the function that was wrapped with the whole input record', () => { @@ -82,7 +94,10 @@ describe('Given a function to wrap', (functionToWrap = jest.fn()) => { }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(keyValueToBeSaved); + expect(mockSaveInProgress).toBeCalledWith( + keyValueToBeSaved, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -123,7 +138,10 @@ describe('Given a function to wrap', (functionToWrap = jest.fn()) => { }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(keyValueToBeSaved); + expect(mockSaveInProgress).toBeCalledWith( + keyValueToBeSaved, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -159,7 +177,10 @@ describe('Given a function to wrap', (functionToWrap = jest.fn()) => { }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(keyValueToBeSaved); + expect(mockSaveInProgress).toBeCalledWith( + keyValueToBeSaved, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -185,7 +206,10 @@ describe('Given a function to wrap', (functionToWrap = jest.fn()) => { }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(keyValueToBeSaved); + expect(mockSaveInProgress).toBeCalledWith( + keyValueToBeSaved, + mockLambaContext.getRemainingTimeInMillis() + ); }); test('Then an IdempotencyPersistenceLayerError is thrown', () => { From 848d97ddbe4b271ecd91d41a4f999227fc9371ec Mon Sep 17 00:00:00 2001 From: Alexander Melnyk Date: Thu, 22 Jun 2023 11:38:10 +0200 Subject: [PATCH 3/5] adjust handler signature with context --- .../tests/unit/idempotentDecorator.test.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/idempotency/tests/unit/idempotentDecorator.test.ts b/packages/idempotency/tests/unit/idempotentDecorator.test.ts index 0a87ccc787..cb3871c8f3 100644 --- a/packages/idempotency/tests/unit/idempotentDecorator.test.ts +++ b/packages/idempotency/tests/unit/idempotentDecorator.test.ts @@ -33,6 +33,8 @@ const mockLambaContext: Context = { }, } as Context; +const mockConfig: IdempotencyConfig = new IdempotencyConfig({}); + class PersistenceLayerTestClass extends BasePersistenceLayer { protected _deleteRecord = jest.fn(); protected _getRecord = jest.fn(); @@ -47,7 +49,8 @@ class TestinClassWithLambdaHandler { persistenceStore: new PersistenceLayerTestClass(), config: new IdempotencyConfig({ lambdaContext: mockLambaContext }), }) - public testing(record: Record): string { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public testing(record: Record, context: Context): string { functionalityToDecorate(record); return 'Hi'; @@ -55,14 +58,16 @@ class TestinClassWithLambdaHandler { } class TestingClassWithFunctionDecorator { - public handler(record: Record): string { + public handler(record: Record, context: Context): string { + mockConfig.registerLambdaContext(context); + return this.proccessRecord(record); } @idempotentFunction({ persistenceStore: new PersistenceLayerTestClass(), dataKeywordArgument: 'testingKey', - config: new IdempotencyConfig({ lambdaContext: mockLambaContext }), + config: mockConfig, }) public proccessRecord(record: Record): string { functionalityToDecorate(record); @@ -81,7 +86,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = describe('When wrapping a function with no previous executions', () => { beforeEach(async () => { - await classWithFunctionDecorator.handler(inputRecord); + await classWithFunctionDecorator.handler(inputRecord, mockLambaContext); }); test('Then it will save the record to INPROGRESS', () => { @@ -104,7 +109,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); describe('When wrapping a function with no previous executions', () => { beforeEach(async () => { - await classWithLambdaHandler.testing(inputRecord); + await classWithLambdaHandler.testing(inputRecord, mockLambaContext); }); test('Then it will save the record to INPROGRESS', () => { @@ -137,7 +142,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = new IdempotencyRecord(idempotencyOptions) ); try { - await classWithLambdaHandler.testing(inputRecord); + await classWithLambdaHandler.testing(inputRecord, mockLambaContext); } catch (e) { resultingError = e as Error; } @@ -177,7 +182,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = new IdempotencyRecord(idempotencyOptions) ); try { - await classWithLambdaHandler.testing(inputRecord); + await classWithLambdaHandler.testing(inputRecord, mockLambaContext); } catch (e) { resultingError = e as Error; } @@ -216,7 +221,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = mockGetRecord.mockResolvedValue( new IdempotencyRecord(idempotencyOptions) ); - await classWithLambdaHandler.testing(inputRecord); + await classWithLambdaHandler.testing(inputRecord, mockLambaContext); }); test('Then it will attempt to save the record to INPROGRESS', () => { From a35c8ed7154aa834fd50f61dc171b012f0af4874 Mon Sep 17 00:00:00 2001 From: Alexander Melnyk Date: Thu, 22 Jun 2023 11:50:46 +0200 Subject: [PATCH 4/5] revert tests to create config, if not passed, undefined temporary --- .../idempotency/src/idempotentDecorator.ts | 1 + .../tests/unit/idempotentDecorator.test.ts | 21 ++++--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/idempotency/src/idempotentDecorator.ts b/packages/idempotency/src/idempotentDecorator.ts index 020c3e2cd9..527cf2ed42 100644 --- a/packages/idempotency/src/idempotentDecorator.ts +++ b/packages/idempotency/src/idempotentDecorator.ts @@ -32,6 +32,7 @@ const idempotent = function ( const functionPayloadtoBeHashed = isFunctionOption(options) ? record[(options as IdempotencyFunctionOptions).dataKeywordArgument] : record; + const idempotencyConfig = options.config ? options.config : new IdempotencyConfig({}); diff --git a/packages/idempotency/tests/unit/idempotentDecorator.test.ts b/packages/idempotency/tests/unit/idempotentDecorator.test.ts index cb3871c8f3..71929285bf 100644 --- a/packages/idempotency/tests/unit/idempotentDecorator.test.ts +++ b/packages/idempotency/tests/unit/idempotentDecorator.test.ts @@ -47,7 +47,6 @@ const functionalityToDecorate = jest.fn(); class TestinClassWithLambdaHandler { @idempotentLambdaHandler({ persistenceStore: new PersistenceLayerTestClass(), - config: new IdempotencyConfig({ lambdaContext: mockLambaContext }), }) // eslint-disable-next-line @typescript-eslint/no-unused-vars public testing(record: Record, context: Context): string { @@ -113,10 +112,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith( - inputRecord, - mockLambaContext.getRemainingTimeInMillis() - ); + expect(mockSaveInProgress).toBeCalledWith(inputRecord, undefined); }); test('Then it will call the function that was decorated', () => { @@ -149,10 +145,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith( - inputRecord, - mockLambaContext.getRemainingTimeInMillis() - ); + expect(mockSaveInProgress).toBeCalledWith(inputRecord, undefined); }); test('Then it will get the previous execution record', () => { @@ -189,10 +182,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith( - inputRecord, - mockLambaContext.getRemainingTimeInMillis() - ); + expect(mockSaveInProgress).toBeCalledWith(inputRecord, undefined); }); test('Then it will get the previous execution record', () => { @@ -225,10 +215,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith( - inputRecord, - mockLambaContext.getRemainingTimeInMillis() - ); + expect(mockSaveInProgress).toBeCalledWith(inputRecord, undefined); }); test('Then it will get the previous execution record', () => { From 55b90817fc8627185baa0ffeb866b777c0a87a70 Mon Sep 17 00:00:00 2001 From: Alexander Melnyk Date: Thu, 22 Jun 2023 14:27:24 +0200 Subject: [PATCH 5/5] decorator can now register context too --- .../idempotency/src/idempotentDecorator.ts | 20 ++++++++- .../tests/unit/idempotentDecorator.test.ts | 43 +++++++++++-------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/packages/idempotency/src/idempotentDecorator.ts b/packages/idempotency/src/idempotentDecorator.ts index 527cf2ed42..9b97f89bbb 100644 --- a/packages/idempotency/src/idempotentDecorator.ts +++ b/packages/idempotency/src/idempotentDecorator.ts @@ -5,6 +5,16 @@ import { } from './types'; import { IdempotencyHandler } from './IdempotencyHandler'; import { IdempotencyConfig } from './IdempotencyConfig'; +import { Context } from 'aws-lambda'; + +const isContext = (arg: unknown): arg is Context => { + return ( + arg !== undefined && + arg !== null && + typeof arg === 'object' && + 'getRemainingTimeInMillis' in arg + ); +}; /** * use this function to narrow the type of options between IdempotencyHandlerOptions and IdempotencyFunctionOptions @@ -28,14 +38,20 @@ const idempotent = function ( descriptor: PropertyDescriptor ) { const childFunction = descriptor.value; - descriptor.value = function (record: GenericTempRecord) { + descriptor.value = function ( + record: GenericTempRecord, + ...args: unknown[] + ) { const functionPayloadtoBeHashed = isFunctionOption(options) ? record[(options as IdempotencyFunctionOptions).dataKeywordArgument] : record; - const idempotencyConfig = options.config ? options.config : new IdempotencyConfig({}); + const context = args[0]; + if (isContext(context)) { + idempotencyConfig.registerLambdaContext(context); + } const idempotencyHandler = new IdempotencyHandler({ functionToMakeIdempotent: childFunction, functionPayloadToBeHashed: functionPayloadtoBeHashed, diff --git a/packages/idempotency/tests/unit/idempotentDecorator.test.ts b/packages/idempotency/tests/unit/idempotentDecorator.test.ts index 71929285bf..9a1b2dc643 100644 --- a/packages/idempotency/tests/unit/idempotentDecorator.test.ts +++ b/packages/idempotency/tests/unit/idempotentDecorator.test.ts @@ -16,6 +16,7 @@ import { } from '../../src/Exceptions'; import { IdempotencyConfig } from '../../src'; import { Context } from 'aws-lambda'; +import { helloworldContext } from '@aws-lambda-powertools/commons/lib/samples/resources/contexts'; const mockSaveInProgress = jest .spyOn(BasePersistenceLayer.prototype, 'saveInProgress') @@ -27,11 +28,7 @@ const mockGetRecord = jest .spyOn(BasePersistenceLayer.prototype, 'getRecord') .mockImplementation(); -const mockLambaContext: Context = { - getRemainingTimeInMillis(): number { - return 1000; // we expect this number to be passed to saveInProgress - }, -} as Context; +const dummyContext = helloworldContext; const mockConfig: IdempotencyConfig = new IdempotencyConfig({}); @@ -85,13 +82,13 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = describe('When wrapping a function with no previous executions', () => { beforeEach(async () => { - await classWithFunctionDecorator.handler(inputRecord, mockLambaContext); + await classWithFunctionDecorator.handler(inputRecord, dummyContext); }); test('Then it will save the record to INPROGRESS', () => { expect(mockSaveInProgress).toBeCalledWith( keyValueToBeSaved, - mockLambaContext.getRemainingTimeInMillis() + dummyContext.getRemainingTimeInMillis() ); }); @@ -108,11 +105,14 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = }); describe('When wrapping a function with no previous executions', () => { beforeEach(async () => { - await classWithLambdaHandler.testing(inputRecord, mockLambaContext); + await classWithLambdaHandler.testing(inputRecord, dummyContext); }); test('Then it will save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord, undefined); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + dummyContext.getRemainingTimeInMillis() + ); }); test('Then it will call the function that was decorated', () => { @@ -138,14 +138,17 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = new IdempotencyRecord(idempotencyOptions) ); try { - await classWithLambdaHandler.testing(inputRecord, mockLambaContext); + await classWithLambdaHandler.testing(inputRecord, dummyContext); } catch (e) { resultingError = e as Error; } }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord, undefined); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + dummyContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -175,14 +178,17 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = new IdempotencyRecord(idempotencyOptions) ); try { - await classWithLambdaHandler.testing(inputRecord, mockLambaContext); + await classWithLambdaHandler.testing(inputRecord, dummyContext); } catch (e) { resultingError = e as Error; } }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord, undefined); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + dummyContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -211,11 +217,14 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = mockGetRecord.mockResolvedValue( new IdempotencyRecord(idempotencyOptions) ); - await classWithLambdaHandler.testing(inputRecord, mockLambaContext); + await classWithLambdaHandler.testing(inputRecord, dummyContext); }); test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toBeCalledWith(inputRecord, undefined); + expect(mockSaveInProgress).toBeCalledWith( + inputRecord, + dummyContext.getRemainingTimeInMillis() + ); }); test('Then it will get the previous execution record', () => { @@ -231,7 +240,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = class TestinClassWithLambdaHandlerWithConfig { @idempotentLambdaHandler({ persistenceStore: new PersistenceLayerTestClass(), - config: new IdempotencyConfig({ lambdaContext: mockLambaContext }), + config: new IdempotencyConfig({ lambdaContext: dummyContext }), }) public testing(record: Record): string { functionalityToDecorate(record); @@ -255,7 +264,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = test('Then it will attempt to save the record to INPROGRESS', () => { expect(mockSaveInProgress).toBeCalledWith( inputRecord, - mockLambaContext.getRemainingTimeInMillis() + dummyContext.getRemainingTimeInMillis() ); });