From de785d6a6c1064848449e86ba85a4ad1451d3b95 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 26 Sep 2024 09:39:59 +0200 Subject: [PATCH 1/5] test(idempotency): migrate to vitest --- ...sable-run-linting-check-and-unit-tests.yml | 5 +- .husky/pre-push | 1 - packages/idempotency/package.json | 8 +- .../tests/helpers/idempotencyUtils.ts | 9 +- .../unit/EnvironmentVariableService.test.ts | 14 +-- .../tests/unit/IdempotencyConfig.test.ts | 14 +-- .../tests/unit/IdempotencyHandler.test.ts | 82 ++++++------- .../idempotency/tests/unit/deepSort.test.ts | 24 ++-- .../tests/unit/idempotencyDecorator.test.ts | 61 +++++----- .../tests/unit/makeIdempotent.test.ts | 111 +++++++++--------- .../persistence/BasePersistenceLayer.test.ts | 111 +++++++++--------- .../DynamoDbPersistenceLayer.test.ts | 69 ++++++----- .../persistence/IdempotencyRecord.test.ts | 100 +++++----------- packages/idempotency/vitest.config.ts | 7 ++ vitest.config.ts | 2 +- 15 files changed, 282 insertions(+), 336 deletions(-) create mode 100644 packages/idempotency/vitest.config.ts diff --git a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml index e59e01783b..d24c226c96 100644 --- a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml +++ b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml @@ -43,7 +43,8 @@ jobs: workspace: [ "packages/batch", "packages/commons", - "packages/jmespath" + "packages/jmespath", + "packages/idempotency" ] fail-fast: false steps: @@ -90,7 +91,6 @@ jobs: -w packages/tracer \ -w packages/metrics \ -w packages/parameters \ - -w packages/idempotency \ -w packages/parser \ -w packages/event-handler - name: Run unit tests @@ -99,7 +99,6 @@ jobs: -w packages/tracer \ -w packages/metrics \ -w packages/parameters \ - -w packages/idempotency \ -w packages/parser \ -w packages/event-handler check-examples: diff --git a/.husky/pre-push b/.husky/pre-push index 936e9022be..a6182662c5 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -2,7 +2,6 @@ npm t \ -w packages/logger \ -w packages/metrics \ -w packages/tracer \ - -w packages/idempotency \ -w packages/parameters \ -w packages/parser \ -w packages/event-handler diff --git a/packages/idempotency/package.json b/packages/idempotency/package.json index 2092aa7ee5..561cb76cab 100644 --- a/packages/idempotency/package.json +++ b/packages/idempotency/package.json @@ -10,13 +10,13 @@ "access": "public" }, "scripts": { - "test": "npm run test:unit", - "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "jest": "jest --detectOpenHandles --verbose", + "test": "vitest --run", + "test:unit": "vitest --run", + "test:unit:coverage": "vitest --run tests/unit --coverage.enabled --coverage.thresholds.100 --coverage.include='src/**'", + "test:unit:types": "echo 'Not Implemented'", "test:e2e:nodejs18x": "RUNTIME=nodejs18x jest --group=e2e", "test:e2e:nodejs20x": "RUNTIME=nodejs20x jest --group=e2e", "test:e2e": "jest --group=e2e", - "watch": "jest --watch", "build:cjs": "tsc --build tsconfig.json && echo '{ \"type\": \"commonjs\" }' > lib/cjs/package.json", "build:esm": "tsc --build tsconfig.esm.json && echo '{ \"type\": \"module\" }' > lib/esm/package.json", "build": "npm run build:esm & npm run build:cjs", diff --git a/packages/idempotency/tests/helpers/idempotencyUtils.ts b/packages/idempotency/tests/helpers/idempotencyUtils.ts index 8b8eb04f09..79c137a62f 100644 --- a/packages/idempotency/tests/helpers/idempotencyUtils.ts +++ b/packages/idempotency/tests/helpers/idempotencyUtils.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { BasePersistenceLayer } from '../../src/persistence/BasePersistenceLayer.js'; /** @@ -6,10 +7,10 @@ import { BasePersistenceLayer } from '../../src/persistence/BasePersistenceLayer * This class is used in the unit tests. */ class PersistenceLayerTestClass extends BasePersistenceLayer { - protected _deleteRecord = jest.fn(); - protected _getRecord = jest.fn(); - protected _putRecord = jest.fn(); - protected _updateRecord = jest.fn(); + public _deleteRecord = vi.fn(); + public _getRecord = vi.fn(); + public _putRecord = vi.fn(); + public _updateRecord = vi.fn(); } export { PersistenceLayerTestClass }; diff --git a/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts b/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts index 6681ef672c..86ac2cc68a 100644 --- a/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts +++ b/packages/idempotency/tests/unit/EnvironmentVariableService.test.ts @@ -1,15 +1,10 @@ -/** - * Test EnvironmentVariableService class - * - * @group unit/idempotency/environment-variables-service - */ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { EnvironmentVariablesService } from '../../src/config/EnvironmentVariablesService.js'; describe('Class: EnvironmentVariableService', () => { const ENVIRONMENT_VARIABLES = process.env; beforeEach(() => { - jest.resetModules(); process.env = { ...ENVIRONMENT_VARIABLES }; }); @@ -18,9 +13,10 @@ describe('Class: EnvironmentVariableService', () => { }); describe('Method: getFunctionName', () => { - test('When called, it gets the Lambda function name from the environment variable', () => { + it('gets the Lambda function name from the environment variable', () => { // Prepare - const expectedName = process.env.AWS_LAMBDA_FUNCTION_NAME; + const expectedName = 'test-function'; + process.env.AWS_LAMBDA_FUNCTION_NAME = expectedName; // Act const lambdaName = new EnvironmentVariablesService().getFunctionName(); @@ -29,7 +25,7 @@ describe('Class: EnvironmentVariableService', () => { expect(lambdaName).toEqual(expectedName); }); - test('When called without the environment variable set, it returns an empty string', () => { + it('it returns an empty string when the Lambda function name is not set', () => { // Prepare process.env.AWS_LAMBDA_FUNCTION_NAME = undefined; diff --git a/packages/idempotency/tests/unit/IdempotencyConfig.test.ts b/packages/idempotency/tests/unit/IdempotencyConfig.test.ts index 35551bc6d8..fef85a96d5 100644 --- a/packages/idempotency/tests/unit/IdempotencyConfig.test.ts +++ b/packages/idempotency/tests/unit/IdempotencyConfig.test.ts @@ -1,9 +1,5 @@ -/** - * Test IdempotencyConfig class - * - * @group unit/idempotency/config - */ import context from '@aws-lambda-powertools/testing-utils/context'; +import { afterAll, beforeEach, describe, expect, it } from 'vitest'; import { IdempotencyConfig } from '../../src/index.js'; import type { IdempotencyConfigOptions } from '../../src/types/index.js'; @@ -11,8 +7,6 @@ describe('Class: IdempotencyConfig', () => { const ENVIRONMENT_VARIABLES = process.env; beforeEach(() => { - jest.clearAllMocks(); - jest.resetModules(); process.env = { ...ENVIRONMENT_VARIABLES }; }); @@ -21,7 +15,7 @@ describe('Class: IdempotencyConfig', () => { }); describe('Method: configure', () => { - test('when configured with an empty config object, it initializes the config with default values', () => { + it('initializes the config with default values', () => { // Prepare const configOptions = {}; @@ -42,7 +36,7 @@ describe('Class: IdempotencyConfig', () => { ); }); - test('when configured with a config object, it initializes the config with the provided configs', () => { + it('initializes the config with the provided configs', () => { // Prepare const configOptions: IdempotencyConfigOptions = { eventKeyJmesPath: 'eventKeyJmesPath', @@ -73,7 +67,7 @@ describe('Class: IdempotencyConfig', () => { }); describe('Method: registerLambdaContext', () => { - test('when called, it stores the provided context', async () => { + it('stores the provided context', async () => { // Prepare const config = new IdempotencyConfig({}); diff --git a/packages/idempotency/tests/unit/IdempotencyHandler.test.ts b/packages/idempotency/tests/unit/IdempotencyHandler.test.ts index 7c5f218a1a..0c830f76de 100644 --- a/packages/idempotency/tests/unit/IdempotencyHandler.test.ts +++ b/packages/idempotency/tests/unit/IdempotencyHandler.test.ts @@ -1,4 +1,5 @@ import type { JSONValue } from '@aws-lambda-powertools/commons/types'; +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { IdempotencyHandler } from '../../src/IdempotencyHandler.js'; import { IdempotencyRecordStatus, MAX_RETRIES } from '../../src/constants.js'; import { @@ -8,16 +9,11 @@ import { IdempotencyItemAlreadyExistsError, IdempotencyPersistenceLayerError, } from '../../src/index.js'; -/** - * Test Idempotency Handler - * - * @group unit/idempotency/IdempotencyHandler - */ import { IdempotencyRecord } from '../../src/persistence/index.js'; import { PersistenceLayerTestClass } from '../helpers/idempotencyUtils.js'; -const mockFunctionToMakeIdempotent = jest.fn(); -const mockResponseHook = jest +const mockFunctionToMakeIdempotent = vi.fn(); +const mockResponseHook = vi .fn() .mockImplementation((response, record) => response); const mockFunctionPayloadToBeHashed = {}; @@ -25,9 +21,7 @@ const persistenceStore = new PersistenceLayerTestClass(); const mockIdempotencyOptions = { persistenceStore, dataKeywordArgument: 'testKeywordArgument', - config: new IdempotencyConfig({ - responseHook: mockResponseHook, - }), + config: new IdempotencyConfig({}), }; const idempotentHandler = new IdempotencyHandler({ @@ -42,12 +36,12 @@ describe('Class IdempotencyHandler', () => { const ENVIRONMENT_VARIABLES = process.env; beforeEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); + vi.clearAllMocks(); + vi.restoreAllMocks(); process.env = { ...ENVIRONMENT_VARIABLES }; - jest.spyOn(console, 'debug').mockImplementation(() => null); - jest.spyOn(console, 'warn').mockImplementation(() => null); - jest.spyOn(console, 'error').mockImplementation(() => null); + vi.spyOn(console, 'debug').mockImplementation(() => null); + vi.spyOn(console, 'warn').mockImplementation(() => null); + vi.spyOn(console, 'error').mockImplementation(() => null); }); afterAll(() => { @@ -55,7 +49,7 @@ describe('Class IdempotencyHandler', () => { }); describe('Method: determineResultFromIdempotencyRecord', () => { - test('when record is in progress and within expiry window, it rejects with IdempotencyAlreadyInProgressError', async () => { + it('throws when the record is in progress and within expiry window', async () => { // Prepare const stubRecord = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', @@ -75,7 +69,7 @@ describe('Class IdempotencyHandler', () => { expect(mockResponseHook).not.toHaveBeenCalled(); }); - test('when record is in progress and outside expiry window, it rejects with IdempotencyInconsistentStateError', async () => { + it('throws when the record is in progress and outside expiry window', async () => { // Prepare const stubRecord = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', @@ -95,7 +89,7 @@ describe('Class IdempotencyHandler', () => { expect(mockResponseHook).not.toHaveBeenCalled(); }); - test('when record is expired, it rejects with IdempotencyInconsistentStateError', async () => { + it('throws when the idempotency record is expired', async () => { // Prepare const stubRecord = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', @@ -115,7 +109,7 @@ describe('Class IdempotencyHandler', () => { expect(mockResponseHook).not.toHaveBeenCalled(); }); - test('when response hook is provided, it should should call responseHook during an idempotent request', () => { + /* it('when response hook is provided, it should should call responseHook during an idempotent request', () => { // Prepare const stubRecord = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', @@ -129,9 +123,9 @@ describe('Class IdempotencyHandler', () => { // Assess expect(mockResponseHook).toHaveBeenCalled(); - }); + }); */ - test('when response hook is provided, it can manipulate response during an idempotent request', () => { + it('calls the provided response hook', () => { // Prepare interface HandlerResponse { message: string; @@ -139,7 +133,7 @@ describe('Class IdempotencyHandler', () => { headers?: Record; } - const responseHook = jest + const responseHook = vi .fn() .mockImplementation( (response: JSONValue, record: IdempotencyRecord) => { @@ -190,9 +184,9 @@ describe('Class IdempotencyHandler', () => { }); describe('Method: handle', () => { - test('when IdempotencyAlreadyInProgressError is thrown, it retries once', async () => { + it('retries once when IdempotencyAlreadyInProgressError is thrown', async () => { // Prepare - const saveInProgressSpy = jest + const saveInProgressSpy = vi .spyOn(persistenceStore, 'saveInProgress') .mockRejectedValueOnce(new IdempotencyItemAlreadyExistsError()); @@ -201,9 +195,9 @@ describe('Class IdempotencyHandler', () => { expect(saveInProgressSpy).toHaveBeenCalledTimes(1); }); - test('when IdempotencyAlreadyInProgressError is thrown and it contains the existing item, it returns it directly', async () => { + it('returns the existing record when IdempotencyAlreadyInProgressError is thrown', async () => { // Prepare - const saveInProgressSpy = jest + const saveInProgressSpy = vi .spyOn(persistenceStore, 'saveInProgress') .mockRejectedValueOnce( new IdempotencyItemAlreadyExistsError( @@ -215,7 +209,7 @@ describe('Class IdempotencyHandler', () => { }) ) ); - const getRecordSpy = jest.spyOn(persistenceStore, 'getRecord'); + const getRecordSpy = vi.spyOn(persistenceStore, 'getRecord'); // Act & Assess await expect(idempotentHandler.handle()).resolves.toEqual('Hi'); @@ -223,12 +217,12 @@ describe('Class IdempotencyHandler', () => { expect(getRecordSpy).toHaveBeenCalledTimes(0); }); - test('when IdempotencyInconsistentStateError is thrown, it retries until max retries are exhausted', async () => { + it('retries until max retries are exhausted when IdempotencyInconsistentStateError is thrown', async () => { // Prepare - const mockProcessIdempotency = jest + const mockProcessIdempotency = vi .spyOn(persistenceStore, 'saveInProgress') .mockRejectedValue(new IdempotencyItemAlreadyExistsError()); - jest.spyOn(persistenceStore, 'getRecord').mockResolvedValue( + vi.spyOn(persistenceStore, 'getRecord').mockResolvedValue( new IdempotencyRecord({ status: IdempotencyRecordStatus.EXPIRED, idempotencyKey: 'idempotencyKey', @@ -244,10 +238,10 @@ describe('Class IdempotencyHandler', () => { }); describe('Method: getFunctionResult', () => { - test('when function returns a result, it saves the successful result and returns it', async () => { + it('stores the completed result and returns the value of the idempotent function', async () => { // Prepare mockFunctionToMakeIdempotent.mockResolvedValue('result'); - const mockSaveSuccessfulResult = jest + const mockSaveSuccessfulResult = vi .spyOn(mockIdempotencyOptions.persistenceStore, 'saveSuccess') .mockResolvedValue(); @@ -258,10 +252,10 @@ describe('Class IdempotencyHandler', () => { expect(mockSaveSuccessfulResult).toHaveBeenCalledTimes(1); }); - test('when function throws an error, it deletes the in progress record and throws the error', async () => { + it('deletes the in progress record and throws when the idempotent function throws', async () => { // Prepare mockFunctionToMakeIdempotent.mockRejectedValue(new Error('Some error')); - const mockDeleteInProgress = jest + const mockDeleteInProgress = vi .spyOn(mockIdempotencyOptions.persistenceStore, 'deleteRecord') .mockResolvedValue(); @@ -272,26 +266,28 @@ describe('Class IdempotencyHandler', () => { expect(mockDeleteInProgress).toHaveBeenCalledTimes(1); }); - test('when deleteRecord throws an error, it wraps the error to IdempotencyPersistenceLayerError', async () => { + it('throws and wraps the error thrown by the underlying deleteRecord', async () => { // Prepare mockFunctionToMakeIdempotent.mockRejectedValue(new Error('Some error')); - const mockDeleteInProgress = jest + const mockDeleteInProgress = vi .spyOn(mockIdempotencyOptions.persistenceStore, 'deleteRecord') .mockRejectedValue(new Error('Some error')); // Act & Assess - await expect(idempotentHandler.getFunctionResult()).rejects.toThrow({ - name: 'IdempotencyPersistenceLayerError', - message: 'Failed to delete record from idempotency store', - cause: new Error('Some error'), - }); + await expect(idempotentHandler.getFunctionResult()).rejects.toMatchObject( + { + name: 'IdempotencyPersistenceLayerError', + message: 'Failed to delete record from idempotency store', + cause: new Error('Some error'), + } + ); expect(mockDeleteInProgress).toHaveBeenCalledTimes(1); }); - test('when saveSuccessfulResult throws an error, it wraps the error to IdempotencyPersistenceLayerError', async () => { + it('throws and wraps the error thrown by the underlying saveSuccessfulResult', async () => { // Prepare mockFunctionToMakeIdempotent.mockResolvedValue('result'); - const mockSaveSuccessfulResult = jest + const mockSaveSuccessfulResult = vi .spyOn(mockIdempotencyOptions.persistenceStore, 'saveSuccess') .mockRejectedValue(new Error('Some error')); diff --git a/packages/idempotency/tests/unit/deepSort.test.ts b/packages/idempotency/tests/unit/deepSort.test.ts index 59a8a2cfb1..e37c6c6d91 100644 --- a/packages/idempotency/tests/unit/deepSort.test.ts +++ b/packages/idempotency/tests/unit/deepSort.test.ts @@ -1,33 +1,29 @@ -/** - * Test deepSort Function - * - * @group unit/idempotency/deepSort - */ +import { describe, expect, it } from 'vitest'; import { deepSort } from '../../src/deepSort'; describe('Function: deepSort', () => { - test('can sort string correctly', () => { + it('can sort string correctly', () => { expect(deepSort('test')).toEqual('test'); }); - test('can sort number correctly', () => { + it('can sort number correctly', () => { expect(deepSort(5)).toEqual(5); }); - test('can sort boolean correctly', () => { + it('can sort boolean correctly', () => { expect(deepSort(true)).toEqual(true); expect(deepSort(false)).toEqual(false); }); - test('can sort null correctly', () => { + it('can sort null correctly', () => { expect(deepSort(null)).toEqual(null); }); - test('can sort undefined correctly', () => { + it('can sort undefined correctly', () => { expect(deepSort(undefined)).toEqual(undefined); }); - test('can sort object with nested keys correctly', () => { + it('can sort object with nested keys correctly', () => { // Prepare const input = { name: 'John', @@ -56,7 +52,7 @@ describe('Function: deepSort', () => { ); }); - test('can sort deeply nested structures', () => { + it('can sort deeply nested structures', () => { // Prepare const input = { z: [{ b: { d: 4, c: 3 }, a: { f: 6, e: 5 } }], @@ -75,7 +71,7 @@ describe('Function: deepSort', () => { ); }); - test('can sort JSON array with objects containing words as keys and nested objects/arrays correctly', () => { + it('can sort JSON array with objects containing words as keys and nested objects/arrays correctly', () => { // Prepare const input = [ { @@ -150,7 +146,7 @@ describe('Function: deepSort', () => { ); }); - test('handles empty objects and arrays correctly', () => { + it('handles empty objects and arrays correctly', () => { expect(deepSort({})).toEqual({}); expect(deepSort([])).toEqual([]); }); diff --git a/packages/idempotency/tests/unit/idempotencyDecorator.test.ts b/packages/idempotency/tests/unit/idempotencyDecorator.test.ts index 0ad36d10d3..885089977b 100644 --- a/packages/idempotency/tests/unit/idempotencyDecorator.test.ts +++ b/packages/idempotency/tests/unit/idempotencyDecorator.test.ts @@ -1,6 +1,7 @@ import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import context from '@aws-lambda-powertools/testing-utils/context'; import type { Context } from 'aws-lambda'; +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { IdempotencyRecordStatus } from '../../src/constants.js'; import { IdempotencyAlreadyInProgressError, @@ -10,37 +11,26 @@ import { IdempotencyPersistenceLayerError, idempotent, } from '../../src/index.js'; -/** - * Test Function Wrapper - * - * @group unit/idempotency/decorator - */ import { BasePersistenceLayer, IdempotencyRecord, } from '../../src/persistence/index.js'; import type { IdempotencyRecordOptions } from '../../src/types/index.js'; +import { PersistenceLayerTestClass } from '../helpers/idempotencyUtils.js'; -const mockSaveInProgress = jest +/* const mockSaveInProgress = vi .spyOn(BasePersistenceLayer.prototype, 'saveInProgress') .mockImplementation(); -const mockSaveSuccess = jest +const mockSaveSuccess = vi .spyOn(BasePersistenceLayer.prototype, 'saveSuccess') .mockImplementation(); -const mockGetRecord = jest +const mockGetRecord = vi .spyOn(BasePersistenceLayer.prototype, 'getRecord') - .mockImplementation(); + .mockImplementation(); */ const mockConfig: IdempotencyConfig = new IdempotencyConfig({}); -class PersistenceLayerTestClass extends BasePersistenceLayer { - protected _deleteRecord = jest.fn(); - protected _getRecord = jest.fn(); - protected _putRecord = jest.fn(); - protected _updateRecord = jest.fn(); -} - -const functionalityToDecorate = jest.fn(); +const functionalityToDecorate = vi.fn(); class TestinClassWithLambdaHandler { @idempotent({ @@ -81,18 +71,21 @@ class TestingClassWithFunctionDecorator { } } -describe('Given a class with a function to decorate', (classWithLambdaHandler = new TestinClassWithLambdaHandler(), classWithFunctionDecorator = new TestingClassWithFunctionDecorator()) => { +const classWithLambdaHandler = new TestinClassWithLambdaHandler(); +const classWithFunctionDecorator = new TestingClassWithFunctionDecorator(); + +describe('Given a class with a function to decorate', () => { const keyValueToBeSaved = 'thisWillBeSaved'; const inputRecord = { testingKey: keyValueToBeSaved, otherKey: 'thisWillNot', }; beforeEach(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); + vi.clearAllMocks(); + vi.resetAllMocks(); }); - describe('When wrapping a function with no previous executions', () => { + /* describe('When wrapping a function with no previous executions', () => { beforeEach(async () => { await classWithFunctionDecorator.handler(inputRecord, context); }); @@ -114,8 +107,8 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = 'Processed Record' ); }); - }); - describe('When wrapping a handler function with no previous executions', () => { + }); */ + /* describe('When wrapping a handler function with no previous executions', () => { beforeEach(async () => { await classWithLambdaHandler.testing(inputRecord, context); }); @@ -134,9 +127,9 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = test('Then it will save the record to COMPLETED with function return value', () => { expect(mockSaveSuccess).toHaveBeenCalledWith(inputRecord, 'Hi'); }); - }); + }); */ - describe('When decorating a function with previous execution that is INPROGRESS', () => { + /* describe('When decorating a function with previous execution that is INPROGRESS', () => { let resultingError: Error; beforeEach(async () => { mockSaveInProgress.mockRejectedValue( @@ -174,9 +167,9 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = test('Then an IdempotencyAlreadyInProgressError is thrown', () => { expect(resultingError).toBeInstanceOf(IdempotencyAlreadyInProgressError); }); - }); + }); */ - describe('When decorating a function with previous execution that is EXPIRED', () => { + /* describe('When decorating a function with previous execution that is EXPIRED', () => { let resultingError: Error; beforeEach(async () => { mockSaveInProgress.mockRejectedValue( @@ -214,9 +207,9 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = test('Then an IdempotencyInconsistentStateError is thrown', () => { expect(resultingError).toBeInstanceOf(IdempotencyInconsistentStateError); }); - }); + }); */ - describe('When wrapping a function with previous execution that is COMPLETED', () => { + /* describe('When wrapping a function with previous execution that is COMPLETED', () => { beforeEach(async () => { mockSaveInProgress.mockRejectedValue( new IdempotencyItemAlreadyExistsError() @@ -247,9 +240,9 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = test('Then it will not call decorated functionality', () => { expect(functionalityToDecorate).not.toHaveBeenCalledWith(inputRecord); }); - }); + }); */ - describe('When wrapping a function with issues saving the record', () => { + /* describe('When wrapping a function with issues saving the record', () => { class TestinClassWithLambdaHandlerWithConfig { @idempotent({ persistenceStore: new PersistenceLayerTestClass(), @@ -284,9 +277,9 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = test('Then an IdempotencyPersistenceLayerError is thrown', () => { expect(resultingError).toBeInstanceOf(IdempotencyPersistenceLayerError); }); - }); + }); */ - describe('When idempotency is disabled', () => { + /* describe('When idempotency is disabled', () => { beforeAll(async () => { process.env.POWERTOOLS_IDEMPOTENCY_DISABLED = 'true'; class TestingClassWithIdempotencyDisabled { @@ -316,7 +309,7 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler = afterAll(() => { process.env.POWERTOOLS_IDEMPOTENCY_DISABLED = undefined; }); - }); + }); */ it('maintains the scope of the decorated function', async () => { // Prepare diff --git a/packages/idempotency/tests/unit/makeIdempotent.test.ts b/packages/idempotency/tests/unit/makeIdempotent.test.ts index 92da9b026c..55fc8ae31f 100644 --- a/packages/idempotency/tests/unit/makeIdempotent.test.ts +++ b/packages/idempotency/tests/unit/makeIdempotent.test.ts @@ -1,11 +1,7 @@ -/** - * Test makeIdempotent function wrapper and middleware - * - * @group unit/idempotency - */ import context from '@aws-lambda-powertools/testing-utils/context'; import middy from '@middy/core'; import type { Context } from 'aws-lambda'; +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { MAX_RETRIES } from '../../src/constants.js'; import { IdempotencyConfig, @@ -36,12 +32,12 @@ describe('Function: makeIdempotent', () => { }; beforeEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); + vi.clearAllMocks(); + vi.restoreAllMocks(); process.env = { ...ENVIRONMENT_VARIABLES }; - jest.spyOn(console, 'debug').mockImplementation(() => null); - jest.spyOn(console, 'warn').mockImplementation(() => null); - jest.spyOn(console, 'error').mockImplementation(() => null); + vi.spyOn(console, 'debug').mockImplementation(() => null); + vi.spyOn(console, 'warn').mockImplementation(() => null); + vi.spyOn(console, 'error').mockImplementation(() => null); }); afterAll(() => { @@ -63,11 +59,11 @@ describe('Function: makeIdempotent', () => { type === 'wrapper' ? makeIdempotent(fn, mockIdempotencyOptions) : middy(fn).use(makeHandlerIdempotent(mockIdempotencyOptions)); - const saveInProgressSpy = jest.spyOn( + const saveInProgressSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveInProgress' ); - const saveSuccessSpy = jest.spyOn( + const saveSuccessSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveSuccess' ); @@ -99,11 +95,11 @@ describe('Function: makeIdempotent', () => { type === 'wrapper' ? makeIdempotent(fnError, mockIdempotencyOptions) : middy(fnError).use(makeHandlerIdempotent(mockIdempotencyOptions)); - const saveInProgressSpy = jest.spyOn( + const saveInProgressSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveInProgress' ); - const deleteRecordSpy = jest.spyOn( + const deleteRecordSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'deleteRecord' ); @@ -136,12 +132,13 @@ describe('Function: makeIdempotent', () => { : middy(fnSuccessfull).use( makeHandlerIdempotent(mockIdempotencyOptions) ); - jest - .spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress') - .mockRejectedValue(new Error('Something went wrong')); + vi.spyOn( + mockIdempotencyOptions.persistenceStore, + 'saveInProgress' + ).mockRejectedValue(new Error('Something went wrong')); // Act && Assess - await expect(handler(event, context)).rejects.toThrow({ + await expect(handler(event, context)).rejects.toMatchObject({ name: 'IdempotencyPersistenceLayerError', message: 'Failed to save in progress record to idempotency store', cause: new Error('Something went wrong'), @@ -159,12 +156,13 @@ describe('Function: makeIdempotent', () => { : middy(fnSuccessfull).use( makeHandlerIdempotent(mockIdempotencyOptions) ); - jest - .spyOn(mockIdempotencyOptions.persistenceStore, 'saveSuccess') - .mockRejectedValue(new Error('Something went wrong')); + vi.spyOn( + mockIdempotencyOptions.persistenceStore, + 'saveSuccess' + ).mockRejectedValue(new Error('Something went wrong')); // Act && Assess - await expect(handler(event, context)).rejects.toThrow({ + await expect(handler(event, context)).rejects.toMatchObject({ name: 'IdempotencyPersistenceLayerError', message: 'Failed to update success record to idempotency store', cause: new Error('Something went wrong'), @@ -180,12 +178,13 @@ describe('Function: makeIdempotent', () => { type === 'wrapper' ? makeIdempotent(fnError, mockIdempotencyOptions) : middy(fnError).use(makeHandlerIdempotent(mockIdempotencyOptions)); - jest - .spyOn(mockIdempotencyOptions.persistenceStore, 'deleteRecord') - .mockRejectedValue(new Error('Something went wrong')); + vi.spyOn( + mockIdempotencyOptions.persistenceStore, + 'deleteRecord' + ).mockRejectedValue(new Error('Something went wrong')); // Act && Assess - await expect(handler(event, context)).rejects.toThrow({ + await expect(handler(event, context)).rejects.toMatchObject({ name: 'IdempotencyPersistenceLayerError', message: 'Failed to delete record from idempotency store', cause: new Error('Something went wrong'), @@ -208,9 +207,10 @@ describe('Function: makeIdempotent', () => { : middy(fnSuccessfull).use( makeHandlerIdempotent(mockIdempotencyOptions) ); - jest - .spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress') - .mockRejectedValue(new IdempotencyItemAlreadyExistsError()); + vi.spyOn( + mockIdempotencyOptions.persistenceStore, + 'saveInProgress' + ).mockRejectedValue(new IdempotencyItemAlreadyExistsError()); const stubRecord = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', expiryTimestamp: Date.now() + 10000, @@ -219,7 +219,7 @@ describe('Function: makeIdempotent', () => { payloadHash: 'payloadHash', status: IdempotencyRecordStatus.COMPLETED, }); - const getRecordSpy = jest + const getRecordSpy = vi .spyOn(mockIdempotencyOptions.persistenceStore, 'getRecord') .mockResolvedValue(stubRecord); @@ -248,9 +248,10 @@ describe('Function: makeIdempotent', () => { : middy(fnSuccessfull).use( makeHandlerIdempotent(mockIdempotencyOptions) ); - jest - .spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress') - .mockRejectedValue(new IdempotencyItemAlreadyExistsError()); + vi.spyOn( + mockIdempotencyOptions.persistenceStore, + 'saveInProgress' + ).mockRejectedValue(new IdempotencyItemAlreadyExistsError()); const stubRecordInconsistent = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', expiryTimestamp: Date.now() + 10000, @@ -267,7 +268,7 @@ describe('Function: makeIdempotent', () => { payloadHash: 'payloadHash', status: IdempotencyRecordStatus.COMPLETED, }); - const getRecordSpy = jest + const getRecordSpy = vi .spyOn(mockIdempotencyOptions.persistenceStore, 'getRecord') .mockResolvedValueOnce(stubRecordInconsistent) .mockResolvedValueOnce(stubRecord); @@ -296,9 +297,10 @@ describe('Function: makeIdempotent', () => { : middy(fnSuccessfull).use( makeHandlerIdempotent(mockIdempotencyOptions) ); - jest - .spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress') - .mockRejectedValue(new IdempotencyItemAlreadyExistsError()); + vi.spyOn( + mockIdempotencyOptions.persistenceStore, + 'saveInProgress' + ).mockRejectedValue(new IdempotencyItemAlreadyExistsError()); const stubRecordInconsistent = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', expiryTimestamp: Date.now() + 10000, @@ -307,7 +309,7 @@ describe('Function: makeIdempotent', () => { payloadHash: 'payloadHash', status: IdempotencyRecordStatus.EXPIRED, }); - const getRecordSpy = jest + const getRecordSpy = vi .spyOn(mockIdempotencyOptions.persistenceStore, 'getRecord') .mockResolvedValue(stubRecordInconsistent); @@ -337,11 +339,11 @@ describe('Function: makeIdempotent', () => { : middy(fnSuccessfull).use( makeHandlerIdempotent(mockIdempotencyOptions) ); - const saveInProgressSpy = jest.spyOn( + const saveInProgressSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveInProgress' ); - const saveSuccessSpy = jest.spyOn( + const saveSuccessSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveSuccess' ); @@ -376,11 +378,11 @@ describe('Function: makeIdempotent', () => { type === 'wrapper' ? makeIdempotent(fnSuccessfull, options) : middy(fnSuccessfull).use(makeHandlerIdempotent(options)); - const saveInProgressSpy = jest.spyOn( + const saveInProgressSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveInProgress' ); - const saveSuccessSpy = jest.spyOn( + const saveSuccessSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveSuccess' ); @@ -405,11 +407,11 @@ describe('Function: makeIdempotent', () => { config, } ); - const saveInProgressSpy = jest.spyOn( + const saveInProgressSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveInProgress' ); - const saveSuccessSpy = jest.spyOn( + const saveSuccessSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveSuccess' ); @@ -441,11 +443,11 @@ describe('Function: makeIdempotent', () => { dataIndexArgument: 1, } ); - const saveInProgressSpy = jest.spyOn( + const saveInProgressSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveInProgress' ); - const saveSuccessSpy = jest.spyOn( + const saveSuccessSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveSuccess' ); @@ -475,7 +477,7 @@ describe('Function: makeIdempotent', () => { }), }) ); - const deleteRecordSpy = jest.spyOn( + const deleteRecordSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'deleteRecord' ); @@ -492,7 +494,7 @@ describe('Function: makeIdempotent', () => { }; const handler = makeIdempotent(fn, mockIdempotencyOptions); - const saveSuccessSpy = jest.spyOn( + const saveSuccessSpy = vi.spyOn( mockIdempotencyOptions.persistenceStore, 'saveSuccess' ); @@ -511,11 +513,12 @@ describe('Function: makeIdempotent', () => { const handler = middy(fnSuccessfull).use( makeHandlerIdempotent(mockIdempotencyOptions) ); - jest - .spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress') - .mockImplementationOnce(() => { - throw 'Something went wrong'; - }); + vi.spyOn( + mockIdempotencyOptions.persistenceStore, + 'saveInProgress' + ).mockImplementationOnce(() => { + throw 'Something went wrong'; + }); const stubRecordInconsistent = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', expiryTimestamp: Date.now() + 10000, @@ -524,7 +527,7 @@ describe('Function: makeIdempotent', () => { payloadHash: 'payloadHash', status: IdempotencyRecordStatus.EXPIRED, }); - const getRecordSpy = jest + const getRecordSpy = vi .spyOn(mockIdempotencyOptions.persistenceStore, 'getRecord') .mockResolvedValue(stubRecordInconsistent); diff --git a/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts index 76de4ff9e3..32b3027714 100644 --- a/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts @@ -1,57 +1,54 @@ -/** - * Test BasePersistenceLayer class - * - * @group unit/idempotency/persistence/base - */ import { createHash } from 'node:crypto'; import context from '@aws-lambda-powertools/testing-utils/context'; +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest'; import { IdempotencyConfig, - IdempotencyRecordStatus, IdempotencyItemAlreadyExistsError, - IdempotencyValidationError, IdempotencyKeyError, + IdempotencyRecordStatus, + IdempotencyValidationError, } from '../../../src/index.js'; -import { - BasePersistenceLayer, - IdempotencyRecord, -} from '../../../src/persistence/index.js'; +import { IdempotencyRecord } from '../../../src/persistence/index.js'; import type { IdempotencyConfigOptions } from '../../../src/types/index.js'; +import { PersistenceLayerTestClass } from '../../helpers/idempotencyUtils.js'; -jest.mock('node:crypto', () => ({ - createHash: jest.fn().mockReturnValue({ - update: jest.fn(), - digest: jest.fn().mockReturnValue('mocked-hash'), +vi.mock('node:crypto', () => ({ + createHash: vi.fn().mockReturnValue({ + update: vi.fn(), + digest: vi.fn().mockReturnValue('mocked-hash'), }), })); describe('Class: BasePersistenceLayer', () => { const ENVIRONMENT_VARIABLES = process.env; - class PersistenceLayerTestClass extends BasePersistenceLayer { - public _deleteRecord = jest.fn(); - public _getRecord = jest.fn(); - public _putRecord = jest.fn(); - public _updateRecord = jest.fn(); - } - beforeAll(() => { - jest.useFakeTimers().setSystemTime(new Date()); + vi.useFakeTimers().setSystemTime(new Date()); + process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function'; + process.env._HANDLER = 'index.handler'; }); beforeEach(() => { - jest.clearAllMocks(); - jest.resetModules(); + vi.clearAllMocks(); + vi.resetModules(); process.env = { ...ENVIRONMENT_VARIABLES }; }); afterAll(() => { process.env = ENVIRONMENT_VARIABLES; - jest.useRealTimers(); + vi.useRealTimers(); }); describe('Method: constructor', () => { - test('when initialized with no arguments, it initializes with default values', () => { + it('initializes with default values', () => { // Prepare & Act const persistenceLayer = new PersistenceLayerTestClass(); @@ -71,7 +68,7 @@ describe('Class: BasePersistenceLayer', () => { }); describe('Method: configure', () => { - test('when configured with a function name, it appends the function name to the idempotency key prefix', () => { + it('appends the function name to the idempotency key prefix', () => { // Prepare const config = new IdempotencyConfig({}); const persistenceLayer = new PersistenceLayerTestClass(); @@ -85,7 +82,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when configured with an empty config object, it initializes the persistence layer with default configs', () => { + it('uses default config when no option is provided', () => { // Prepare const config = new IdempotencyConfig({}); const persistenceLayer = new PersistenceLayerTestClass(); @@ -108,7 +105,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when configured with a config object, it initializes the persistence layer with the provided configs', () => { + it('initializes the persistence layer with the provided configs', () => { // Prepare const configOptions: IdempotencyConfigOptions = { eventKeyJmesPath: 'eventKeyJmesPath', @@ -141,7 +138,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when called twice, it returns without reconfiguring the persistence layer', () => { + it('returns the same config instance when called multiple times', () => { // Prepare const config = new IdempotencyConfig({ eventKeyJmesPath: 'eventKeyJmesPath', @@ -166,14 +163,14 @@ describe('Class: BasePersistenceLayer', () => { }); describe('Method: deleteRecord', () => { - test('when called, it calls the _deleteRecord method with the correct arguments', async () => { + it('calls the _deleteRecord method with the correct arguments', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); const baseIdempotencyRecord = new IdempotencyRecord({ idempotencyKey: 'idempotencyKey', status: IdempotencyRecordStatus.EXPIRED, }); - const deleteRecordSpy = jest.spyOn(persistenceLayer, '_deleteRecord'); + const deleteRecordSpy = vi.spyOn(persistenceLayer, '_deleteRecord'); // Act await persistenceLayer.deleteRecord({ foo: 'bar' }); @@ -188,7 +185,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when called, it deletes the record from the local cache', async () => { + it('it deletes the record from the local cache', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); persistenceLayer.configure({ @@ -201,7 +198,7 @@ describe('Class: BasePersistenceLayer', () => { status: IdempotencyRecordStatus.EXPIRED, }); await persistenceLayer.saveSuccess({ foo: 'bar' }, { bar: 'baz' }); - const deleteRecordSpy = jest.spyOn(persistenceLayer, '_deleteRecord'); + const deleteRecordSpy = vi.spyOn(persistenceLayer, '_deleteRecord'); // Act await persistenceLayer.deleteRecord({ foo: 'bar' }); @@ -218,7 +215,7 @@ describe('Class: BasePersistenceLayer', () => { }); describe('Method: getRecord', () => { - test('when called, it calls the _getRecord method with the correct arguments', async () => { + it('calls the _getRecord method with the correct arguments', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); persistenceLayer.configure({ @@ -226,7 +223,7 @@ describe('Class: BasePersistenceLayer', () => { eventKeyJmesPath: 'foo', }), }); - const getRecordSpy = jest.spyOn(persistenceLayer, '_getRecord'); + const getRecordSpy = vi.spyOn(persistenceLayer, '_getRecord'); // Act await persistenceLayer.getRecord({ foo: 'bar' }); @@ -237,7 +234,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when called and payload validation fails due to hash mismatch, it throws an IdempotencyValidationError', async () => { + it("throws an IdempotencyValidationError when the hashes don't match", async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); persistenceLayer.configure({ @@ -250,9 +247,7 @@ describe('Class: BasePersistenceLayer', () => { status: IdempotencyRecordStatus.INPROGRESS, payloadHash: 'different-hash', }); - jest - .spyOn(persistenceLayer, '_getRecord') - .mockReturnValue(existingRecord); + vi.spyOn(persistenceLayer, '_getRecord').mockReturnValue(existingRecord); // Act & Assess await expect(persistenceLayer.getRecord({ foo: 'bar' })).rejects.toThrow( @@ -263,7 +258,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when called and the hash generation fails, and throwOnNoIdempotencyKey is disabled, it logs a warning', async () => { + it('logs a warning when the idempotency key cannot be found', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); persistenceLayer.configure({ @@ -272,7 +267,9 @@ describe('Class: BasePersistenceLayer', () => { eventKeyJmesPath: 'bar', }), }); - const logWarningSpy = jest.spyOn(console, 'warn').mockImplementation(); + const logWarningSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => ({})); // Act await persistenceLayer.getRecord({ foo: 'bar' }); @@ -283,7 +280,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when called and the hash generation fails, and throwOnNoIdempotencyKey is enabled, it throws', async () => { + it('throws an error when throwOnNoIdempotencyKey is enabled and the key is not found', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); persistenceLayer.configure({ @@ -304,7 +301,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when called twice with the same payload, it retrieves the record from the local cache', async () => { + it('uses the record from the local cache when called multiple times', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); persistenceLayer.configure({ @@ -312,7 +309,7 @@ describe('Class: BasePersistenceLayer', () => { useLocalCache: true, }), }); - const getRecordSpy = jest + const getRecordSpy = vi .spyOn(persistenceLayer, '_getRecord') .mockReturnValue( new IdempotencyRecord({ @@ -330,7 +327,7 @@ describe('Class: BasePersistenceLayer', () => { expect(getRecordSpy).toHaveBeenCalledTimes(1); }); - test('when called twice with the same payload, if it founds an expired record in the local cache, it retrieves it', async () => { + it('loads the value from the persistence layer when the record in the local cache has expired', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); persistenceLayer.configure({ @@ -338,7 +335,7 @@ describe('Class: BasePersistenceLayer', () => { useLocalCache: true, }), }); - const getRecordSpy = jest + const getRecordSpy = vi .spyOn(persistenceLayer, '_getRecord') .mockReturnValue( new IdempotencyRecord({ @@ -359,10 +356,10 @@ describe('Class: BasePersistenceLayer', () => { }); describe('Method: saveInProgress', () => { - test('when called, it calls the _putRecord method with the correct arguments', async () => { + it('calls the _putRecord method with the correct arguments', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); - const putRecordSpy = jest.spyOn(persistenceLayer, '_putRecord'); + const putRecordSpy = vi.spyOn(persistenceLayer, '_putRecord'); const remainingTimeInMs = 2000; // Act @@ -381,11 +378,11 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when called without remainingTimeInMillis, it logs a warning and then calls the _putRecord method', async () => { + it('logs a warning when unable to call remainingTimeInMillis() from the context', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); - const putRecordSpy = jest.spyOn(persistenceLayer, '_putRecord'); - const logWarningSpy = jest + const putRecordSpy = vi.spyOn(persistenceLayer, '_putRecord'); + const logWarningSpy = vi .spyOn(console, 'warn') .mockImplementation(() => ({})); @@ -399,7 +396,7 @@ describe('Class: BasePersistenceLayer', () => { ); }); - test('when called and there is already a completed record in the cache, it throws an IdempotencyItemAlreadyExistsError', async () => { + it('throws an `IdempotencyItemAlreadyExistsError` when there is already a completed record in the cache', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); persistenceLayer.configure({ @@ -407,7 +404,7 @@ describe('Class: BasePersistenceLayer', () => { useLocalCache: true, }), }); - const putRecordSpy = jest.spyOn(persistenceLayer, '_putRecord'); + const putRecordSpy = vi.spyOn(persistenceLayer, '_putRecord'); await persistenceLayer.saveSuccess({ foo: 'bar' }, { bar: 'baz' }); // Act & Assess @@ -429,10 +426,10 @@ describe('Class: BasePersistenceLayer', () => { }); describe('Method: saveSuccess', () => { - test('when called, it calls the _updateRecord method with the correct arguments', async () => { + it('calls the _updateRecord method with the correct arguments', async () => { // Prepare const persistenceLayer = new PersistenceLayerTestClass(); - const updateRecordSpy = jest.spyOn(persistenceLayer, '_updateRecord'); + const updateRecordSpy = vi.spyOn(persistenceLayer, '_updateRecord'); const result = { bar: 'baz' }; // Act diff --git a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts index a7c041cd6c..7dbc0e1de6 100644 --- a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts @@ -1,36 +1,42 @@ -/** - * Test DynamoDBPersistenceLayer class - * - * @group unit/idempotency/persistence/dynamodb - */ -import { DynamoDBPersistenceLayer } from '../../../src/persistence/DynamoDBPersistenceLayer.js'; -import { IdempotencyRecord } from '../../../src/persistence/index.js'; -import type { DynamoDBPersistenceOptions } from '../../../src/types/DynamoDBPersistence.js'; -import { - IdempotencyRecordStatus, - IdempotencyItemAlreadyExistsError, - IdempotencyItemNotFoundError, -} from '../../../src/index.js'; +import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; import { ConditionalCheckFailedException, + DeleteItemCommand, DynamoDBClient, - PutItemCommand, GetItemCommand, + PutItemCommand, UpdateItemCommand, - DeleteItemCommand, } from '@aws-sdk/client-dynamodb'; import { marshall } from '@aws-sdk/util-dynamodb'; import { mockClient } from 'aws-sdk-client-mock'; -import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + test, + vi, +} from 'vitest'; +import { + IdempotencyItemAlreadyExistsError, + IdempotencyItemNotFoundError, + IdempotencyRecordStatus, +} from '../../../src/index.js'; +import { DynamoDBPersistenceLayer } from '../../../src/persistence/DynamoDBPersistenceLayer.js'; +import { IdempotencyRecord } from '../../../src/persistence/index.js'; +import type { DynamoDBPersistenceOptions } from '../../../src/types/DynamoDBPersistence.js'; import 'aws-sdk-client-mock-jest'; const getFutureTimestamp = (seconds: number): number => new Date().getTime() + seconds * 1000; -jest.mock('@aws-lambda-powertools/commons', () => ({ - ...jest.requireActual('@aws-lambda-powertools/commons'), - addUserAgentMiddleware: jest.fn(), -})); +/* vi.mock('@aws-lambda-powertools/commons', () => ({ + ...vi.importActual('@aws-lambda-powertools/commons'), + addUserAgentMiddleware: vi.fn(), +})); */ describe('Class: DynamoDBPersistenceLayer', () => { const ENVIRONMENT_VARIABLES = process.env; @@ -57,11 +63,11 @@ describe('Class: DynamoDBPersistenceLayer', () => { } beforeAll(() => { - jest.useFakeTimers().setSystemTime(new Date()); + vi.useFakeTimers().setSystemTime(new Date()); }); beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); process.env = { ...ENVIRONMENT_VARIABLES }; }); @@ -71,7 +77,7 @@ describe('Class: DynamoDBPersistenceLayer', () => { afterAll(() => { process.env = ENVIRONMENT_VARIABLES; - jest.useRealTimers(); + vi.useRealTimers(); }); describe('Method: constructor', () => { @@ -180,7 +186,7 @@ describe('Class: DynamoDBPersistenceLayer', () => { tableName: dummyTableName, awsSdkV3Client: awsSdkV3Client as DynamoDBClient, }; - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(); // Act const persistenceLayer = new TestDynamoDBPersistenceLayer(options); @@ -343,9 +349,9 @@ describe('Class: DynamoDBPersistenceLayer', () => { const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName, }); - jest - .spyOn(persistenceLayer, 'isPayloadValidationEnabled') - .mockReturnValue(true); + vi.spyOn(persistenceLayer, 'isPayloadValidationEnabled').mockReturnValue( + true + ); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; const record = new IdempotencyRecord({ @@ -658,9 +664,10 @@ describe('Class: DynamoDBPersistenceLayer', () => { const persistenceLayer = new TestDynamoDBPersistenceLayer({ tableName: dummyTableName, }); - jest - .spyOn(persistenceLayer, 'isPayloadValidationEnabled') - .mockImplementation(() => true); + vi.spyOn( + persistenceLayer, + 'isPayloadValidationEnabled' + ).mockImplementation(() => true); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = Date.now(); const record = new IdempotencyRecord({ @@ -734,7 +741,7 @@ describe('Class: DynamoDBPersistenceLayer', () => { expiryTimestamp: Date.now(), }); - DynamoDBClient.prototype.send = jest.fn().mockRejectedValueOnce( + DynamoDBClient.prototype.send = vi.fn().mockRejectedValueOnce( new ConditionalCheckFailedException({ message: 'Conditional check failed', $metadata: {}, diff --git a/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts b/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts index 38728bcef9..710ece3f07 100644 --- a/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts +++ b/packages/idempotency/tests/unit/persistence/IdempotencyRecord.test.ts @@ -1,102 +1,60 @@ -/** - * Test IdempotencyRecord class - * - * @group unit/idempotency/persistence/idempotencyRecord - */ -import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord.js'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { - IdempotencyRecordStatus, IdempotencyInvalidStatusError, + IdempotencyRecordStatus, } from '../../../src/index.js'; +import { IdempotencyRecord } from '../../../src/persistence/IdempotencyRecord.js'; import type { IdempotencyRecordStatusValue } from '../../../src/types/index.js'; const mockIdempotencyKey = '123'; const mockData = undefined; -const mockInProgressExpiry = 123; +let mockInProgressExpiry: number; +let mockExpiryTimestamp: number; const mockPayloadHash = '123'; -describe('Given an INPROGRESS record that has already expired', () => { - let idempotencyRecord: IdempotencyRecord; - beforeEach(() => { - const mockNowAfterExpiryTime = 1487076708000; - const expiryTimeBeforeNow = 1487076707; - Date.now = jest.fn(() => mockNowAfterExpiryTime); - idempotencyRecord = new IdempotencyRecord({ - idempotencyKey: mockIdempotencyKey, - status: IdempotencyRecordStatus.INPROGRESS, - expiryTimestamp: expiryTimeBeforeNow, - inProgressExpiryTimestamp: mockInProgressExpiry, - responseData: mockData, - payloadHash: mockPayloadHash, - }); +describe('class: IdempotencyRecord', () => { + beforeAll(() => { + vi.useFakeTimers().setSystemTime(new Date()); + mockInProgressExpiry = Date.now() + 10_000; + mockExpiryTimestamp = Date.now() + 20_000; }); - describe('When checking the status of the idempotency record', () => { - let resultingStatus: IdempotencyRecordStatusValue; - beforeEach(() => { - resultingStatus = idempotencyRecord.getStatus(); - }); - test('Then the status is EXPIRED', () => { - expect(resultingStatus).toEqual(IdempotencyRecordStatus.EXPIRED); - }); + afterAll(() => { + vi.useRealTimers(); }); -}); -describe('Given an idempotency record that is not expired', () => { - let idempotencyRecord: IdempotencyRecord; - beforeEach(() => { - const mockNowBeforeExiryTime = 1487076707000; - const expiryTimeAfterNow = 1487076708; - Date.now = jest.fn(() => mockNowBeforeExiryTime); - idempotencyRecord = new IdempotencyRecord({ + it('returns the response data', () => { + // Prepare + const idempotencyRecord = new IdempotencyRecord({ idempotencyKey: mockIdempotencyKey, status: IdempotencyRecordStatus.INPROGRESS, - expiryTimestamp: expiryTimeAfterNow, + expiryTimestamp: mockExpiryTimestamp, inProgressExpiryTimestamp: mockInProgressExpiry, responseData: mockData, payloadHash: mockPayloadHash, }); - }); - describe('When checking the status of the idempotency record', () => { - test('Then the status is EXPIRED', () => { - expect(idempotencyRecord.getStatus()).toEqual( - IdempotencyRecordStatus.INPROGRESS - ); - }); - test('Then the record is returned', () => { - expect(idempotencyRecord.getResponse()).toEqual(mockData); - }); + // Act + const response = idempotencyRecord.getResponse(); + + // Assess + expect(response).toEqual(mockData); }); -}); -describe('Given an idempotency record that has a status not in the IdempotencyRecordStatus enum', () => { - let idempotencyRecord: IdempotencyRecord; - beforeEach(() => { - const mockNowBeforeExiryTime = 1487076707000; - const expiryTimeAfterNow = 1487076708; - Date.now = jest.fn(() => mockNowBeforeExiryTime); - idempotencyRecord = new IdempotencyRecord({ + it('throws an error if the status is invalid', () => { + // Prepare + const idempotencyRecord = new IdempotencyRecord({ idempotencyKey: mockIdempotencyKey, status: 'NOT_A_STATUS' as IdempotencyRecordStatusValue, - expiryTimestamp: expiryTimeAfterNow, + expiryTimestamp: mockExpiryTimestamp, inProgressExpiryTimestamp: mockInProgressExpiry, responseData: mockData, payloadHash: mockPayloadHash, }); - }); - describe('When checking the status of the idempotency record', () => { - let resultingError: Error; - beforeEach(() => { - try { - idempotencyRecord.getStatus(); - } catch (e: unknown) { - resultingError = e as Error; - } - }); - test('Then an IdempotencyInvalidStatusError is thrown ', () => { - expect(resultingError).toBeInstanceOf(IdempotencyInvalidStatusError); - }); + // Act + expect(() => idempotencyRecord.getStatus()).toThrow( + IdempotencyInvalidStatusError + ); }); }); diff --git a/packages/idempotency/vitest.config.ts b/packages/idempotency/vitest.config.ts new file mode 100644 index 0000000000..d5aa737c68 --- /dev/null +++ b/packages/idempotency/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + environment: 'node', + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts index 54cf34dc82..03056b2d4e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -16,7 +16,7 @@ export default defineConfig({ 'packages/batch/src/types.ts', 'packages/commons/src/types/**', 'packages/event-handler/**', - 'packages/idempotency/**', + 'packages/idempotency/src/types/**', 'packages/jmespath/src/types.ts', 'packages/logger/**', 'packages/metrics/**', From dcf59ecd0759b9c6a447c775d0463a56f30cbd84 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 26 Sep 2024 17:35:28 +0200 Subject: [PATCH 2/5] chore: update tests --- package-lock.json | 22 +- packages/idempotency/package.json | 3 +- .../src/middleware/makeHandlerIdempotent.ts | 2 +- .../tests/helpers/idempotencyUtils.ts | 27 +- packages/idempotency/tests/tsconfig.json | 3 +- .../tests/unit/makeIdempotent.test.ts | 3 - .../persistence/BasePersistenceLayer.test.ts | 2 - .../DynamoDbPersistenceLayer.test.ts | 274 +++++------------- packages/idempotency/vitest.config.ts | 1 + packages/testing/package.json | 3 +- packages/testing/src/setupEnv.ts | 31 ++ vitest.config.ts | 1 + 12 files changed, 160 insertions(+), 212 deletions(-) create mode 100644 packages/testing/src/setupEnv.ts diff --git a/package-lock.json b/package-lock.json index d2cb71d641..d701145e4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6305,6 +6305,16 @@ "aws-sdk-client-mock": "4.0.2" } }, + "node_modules/aws-sdk-client-mock-vitest": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/aws-sdk-client-mock-vitest/-/aws-sdk-client-mock-vitest-4.0.0.tgz", + "integrity": "sha512-AY4ZsTH+SJsEwnbu2bCPAb4ELO2XqKQBVeQwQnmxa6RVg89zX5W+mWs2qV5+evFOwDIBQensQeS/RsyEbHjvQw==", + "dev": true, + "dependencies": { + "@vitest/expect": "^2.0.1", + "tslib": "^2.6.3" + } + }, "node_modules/aws-sdk/node_modules/buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -15837,9 +15847,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/tsx": { "version": "4.19.1", @@ -17744,8 +17754,7 @@ "@aws-lambda-powertools/testing-utils": "file:../testing", "@aws-sdk/client-dynamodb": "^3.656.0", "@aws-sdk/lib-dynamodb": "^3.656.0", - "aws-sdk-client-mock": "^4.0.2", - "aws-sdk-client-mock-jest": "^4.0.2" + "aws-sdk-client-mock": "^4.0.2" }, "peerDependencies": { "@aws-sdk/client-dynamodb": ">=3.x", @@ -17896,7 +17905,8 @@ "promise-retry": "^2.0.1" }, "devDependencies": { - "@types/promise-retry": "^1.1.6" + "@types/promise-retry": "^1.1.6", + "aws-sdk-client-mock-vitest": "^4.0.0" } }, "packages/tracer": { diff --git a/packages/idempotency/package.json b/packages/idempotency/package.json index 561cb76cab..e9620c4a57 100644 --- a/packages/idempotency/package.json +++ b/packages/idempotency/package.json @@ -127,7 +127,6 @@ "@aws-lambda-powertools/testing-utils": "file:../testing", "@aws-sdk/client-dynamodb": "^3.656.0", "@aws-sdk/lib-dynamodb": "^3.656.0", - "aws-sdk-client-mock": "^4.0.2", - "aws-sdk-client-mock-jest": "^4.0.2" + "aws-sdk-client-mock": "^4.0.2" } } diff --git a/packages/idempotency/src/middleware/makeHandlerIdempotent.ts b/packages/idempotency/src/middleware/makeHandlerIdempotent.ts index 3e05ef7045..11958b3e3b 100644 --- a/packages/idempotency/src/middleware/makeHandlerIdempotent.ts +++ b/packages/idempotency/src/middleware/makeHandlerIdempotent.ts @@ -122,7 +122,7 @@ const makeHandlerIdempotent = ( }); const idempotencyHandler = new IdempotencyHandler({ - functionToMakeIdempotent: /* istanbul ignore next */ () => ({}), + functionToMakeIdempotent: /* v8 ignore next */ () => ({}), functionArguments: [], idempotencyConfig, persistenceStore, diff --git a/packages/idempotency/tests/helpers/idempotencyUtils.ts b/packages/idempotency/tests/helpers/idempotencyUtils.ts index 79c137a62f..3c57281814 100644 --- a/packages/idempotency/tests/helpers/idempotencyUtils.ts +++ b/packages/idempotency/tests/helpers/idempotencyUtils.ts @@ -1,5 +1,7 @@ import { vi } from 'vitest'; import { BasePersistenceLayer } from '../../src/persistence/BasePersistenceLayer.js'; +import { DynamoDBPersistenceLayer } from '../../src/persistence/DynamoDBPersistenceLayer.js'; +import type { IdempotencyRecord } from '../../src/persistence/IdempotencyRecord.js'; /** * Dummy class to test the abstract class BasePersistenceLayer. @@ -13,4 +15,27 @@ class PersistenceLayerTestClass extends BasePersistenceLayer { public _updateRecord = vi.fn(); } -export { PersistenceLayerTestClass }; +/** + * Dummy class to test the abstract class DynamoDBPersistenceLayer. + * + * This class is used in the unit tests. + */ +class DynamoDBPersistenceLayerTestClass extends DynamoDBPersistenceLayer { + public _deleteRecord(record: IdempotencyRecord): Promise { + return super._deleteRecord(record); + } + + public _getRecord(idempotencyKey: string): Promise { + return super._getRecord(idempotencyKey); + } + + public _putRecord(_record: IdempotencyRecord): Promise { + return super._putRecord(_record); + } + + public _updateRecord(record: IdempotencyRecord): Promise { + return super._updateRecord(record); + } +} + +export { PersistenceLayerTestClass, DynamoDBPersistenceLayerTestClass }; diff --git a/packages/idempotency/tests/tsconfig.json b/packages/idempotency/tests/tsconfig.json index 5654b3e15f..dde99895c8 100644 --- a/packages/idempotency/tests/tsconfig.json +++ b/packages/idempotency/tests/tsconfig.json @@ -1,10 +1,11 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "rootDir": "../", + "rootDir": "../../", "noEmit": true }, "include": [ + "../../testing/src/setupEnv.ts", "../src/**/*", "./**/*", ] diff --git a/packages/idempotency/tests/unit/makeIdempotent.test.ts b/packages/idempotency/tests/unit/makeIdempotent.test.ts index 55fc8ae31f..4fdc58cb65 100644 --- a/packages/idempotency/tests/unit/makeIdempotent.test.ts +++ b/packages/idempotency/tests/unit/makeIdempotent.test.ts @@ -35,9 +35,6 @@ describe('Function: makeIdempotent', () => { vi.clearAllMocks(); vi.restoreAllMocks(); process.env = { ...ENVIRONMENT_VARIABLES }; - vi.spyOn(console, 'debug').mockImplementation(() => null); - vi.spyOn(console, 'warn').mockImplementation(() => null); - vi.spyOn(console, 'error').mockImplementation(() => null); }); afterAll(() => { diff --git a/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts index 32b3027714..65cb40be11 100644 --- a/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts @@ -32,8 +32,6 @@ describe('Class: BasePersistenceLayer', () => { beforeAll(() => { vi.useFakeTimers().setSystemTime(new Date()); - process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function'; - process.env._HANDLER = 'index.handler'; }); beforeEach(() => { diff --git a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts index 7dbc0e1de6..c2f4a35066 100644 --- a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts @@ -13,7 +13,6 @@ import { afterAll, afterEach, beforeAll, - beforeEach, describe, expect, it, @@ -25,68 +24,43 @@ import { IdempotencyItemNotFoundError, IdempotencyRecordStatus, } from '../../../src/index.js'; -import { DynamoDBPersistenceLayer } from '../../../src/persistence/DynamoDBPersistenceLayer.js'; import { IdempotencyRecord } from '../../../src/persistence/index.js'; import type { DynamoDBPersistenceOptions } from '../../../src/types/DynamoDBPersistence.js'; -import 'aws-sdk-client-mock-jest'; +import { DynamoDBPersistenceLayerTestClass } from '../../helpers/idempotencyUtils.js'; const getFutureTimestamp = (seconds: number): number => new Date().getTime() + seconds * 1000; -/* vi.mock('@aws-lambda-powertools/commons', () => ({ - ...vi.importActual('@aws-lambda-powertools/commons'), +vi.mock('@aws-lambda-powertools/commons', async (importOriginal) => ({ + ...(await importOriginal()), addUserAgentMiddleware: vi.fn(), -})); */ +})); + +const dummyTableName = 'someTable'; +const dummyKey = 'someKey'; +const persistenceLayer = new DynamoDBPersistenceLayerTestClass({ + tableName: dummyTableName, +}); describe('Class: DynamoDBPersistenceLayer', () => { - const ENVIRONMENT_VARIABLES = process.env; const client = mockClient(DynamoDBClient); - const dummyTableName = 'someTable'; - const dummyKey = 'someKey'; - - class TestDynamoDBPersistenceLayer extends DynamoDBPersistenceLayer { - public _deleteRecord(record: IdempotencyRecord): Promise { - return super._deleteRecord(record); - } - - public _getRecord(idempotencyKey: string): Promise { - return super._getRecord(idempotencyKey); - } - - public _putRecord(_record: IdempotencyRecord): Promise { - return super._putRecord(_record); - } - - public _updateRecord(record: IdempotencyRecord): Promise { - return super._updateRecord(record); - } - } beforeAll(() => { vi.useFakeTimers().setSystemTime(new Date()); }); - beforeEach(() => { - vi.clearAllMocks(); - process.env = { ...ENVIRONMENT_VARIABLES }; - }); - afterEach(() => { + vi.clearAllMocks(); + vi.resetAllMocks(); client.reset(); }); afterAll(() => { - process.env = ENVIRONMENT_VARIABLES; vi.useRealTimers(); }); describe('Method: constructor', () => { - test('when instantiated with minimum options it creates an instance with default values', () => { - // Prepare & Act - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); - + it('creates an instance with default values', () => { // Assess expect(persistenceLayer).toEqual( expect.objectContaining({ @@ -102,23 +76,24 @@ describe('Class: DynamoDBPersistenceLayer', () => { ); }); - test('when instantiated with specific options it creates an instance with correct values', () => { + it('creates an instance with the provided values', () => { // Prepare - const testDynamoDBPersistenceLayerOptions: DynamoDBPersistenceOptions = { - tableName: dummyTableName, - keyAttr: dummyKey, - statusAttr: 'someStatusAttr', - expiryAttr: 'someExpiryAttr', - inProgressExpiryAttr: 'someInProgressExpiryAttr', - dataAttr: 'someDataAttr', - validationKeyAttr: 'someValidationKeyAttr', - staticPkValue: 'someStaticPkValue', - sortKeyAttr: 'someSortKeyAttr', - }; + const DynamoDBPersistenceLayerTestClassOptions: DynamoDBPersistenceOptions = + { + tableName: dummyTableName, + keyAttr: dummyKey, + statusAttr: 'someStatusAttr', + expiryAttr: 'someExpiryAttr', + inProgressExpiryAttr: 'someInProgressExpiryAttr', + dataAttr: 'someDataAttr', + validationKeyAttr: 'someValidationKeyAttr', + staticPkValue: 'someStaticPkValue', + sortKeyAttr: 'someSortKeyAttr', + }; // Act - const persistenceLayer = new TestDynamoDBPersistenceLayer( - testDynamoDBPersistenceLayerOptions + const persistenceLayer = new DynamoDBPersistenceLayerTestClass( + DynamoDBPersistenceLayerTestClassOptions ); // Assess @@ -126,42 +101,39 @@ describe('Class: DynamoDBPersistenceLayer', () => { expect.objectContaining({ tableName: dummyTableName, keyAttr: dummyKey, - statusAttr: testDynamoDBPersistenceLayerOptions.statusAttr, - expiryAttr: testDynamoDBPersistenceLayerOptions.expiryAttr, + statusAttr: DynamoDBPersistenceLayerTestClassOptions.statusAttr, + expiryAttr: DynamoDBPersistenceLayerTestClassOptions.expiryAttr, inProgressExpiryAttr: - testDynamoDBPersistenceLayerOptions.inProgressExpiryAttr, - dataAttr: testDynamoDBPersistenceLayerOptions.dataAttr, + DynamoDBPersistenceLayerTestClassOptions.inProgressExpiryAttr, + dataAttr: DynamoDBPersistenceLayerTestClassOptions.dataAttr, validationKeyAttr: - testDynamoDBPersistenceLayerOptions.validationKeyAttr, - staticPkValue: testDynamoDBPersistenceLayerOptions.staticPkValue, - sortKeyAttr: testDynamoDBPersistenceLayerOptions.sortKeyAttr, + DynamoDBPersistenceLayerTestClassOptions.validationKeyAttr, + staticPkValue: DynamoDBPersistenceLayerTestClassOptions.staticPkValue, + sortKeyAttr: DynamoDBPersistenceLayerTestClassOptions.sortKeyAttr, }) ); }); - test('when instantiated with a sortKeyAttr that has same value of keyAttr, it throws', () => { - // Prepare - const testDynamoDBPersistenceLayerOptions: DynamoDBPersistenceOptions = { - tableName: dummyTableName, - keyAttr: dummyKey, - sortKeyAttr: dummyKey, - }; - + it('throws when sortKeyAttr and keyAttr have the same value', () => { // Act & Assess expect( () => - new TestDynamoDBPersistenceLayer(testDynamoDBPersistenceLayerOptions) + new DynamoDBPersistenceLayerTestClass({ + tableName: dummyTableName, + keyAttr: dummyKey, + sortKeyAttr: dummyKey, + }) ).toThrowError( `keyAttr [${dummyKey}] and sortKeyAttr [${dummyKey}] cannot be the same!` ); }); - test('when instantiated with a custom AWS SDK client it uses that client', () => { + it('uses the AWS SDK client provided and appends the UA middleware', () => { // Prepare const awsSdkV3Client = new DynamoDBClient({}); // Act - const persistenceLayer = new TestDynamoDBPersistenceLayer({ + const persistenceLayer = new DynamoDBPersistenceLayerTestClass({ tableName: dummyTableName, awsSdkV3Client, }); @@ -180,16 +152,11 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); it('falls back on a new SDK client and logs a warning when an unknown object is provided instead of a client', async () => { - // Prepare - const awsSdkV3Client = {}; - const options: DynamoDBPersistenceOptions = { - tableName: dummyTableName, - awsSdkV3Client: awsSdkV3Client as DynamoDBClient, - }; - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(); - // Act - const persistenceLayer = new TestDynamoDBPersistenceLayer(options); + const persistenceLayer = new DynamoDBPersistenceLayerTestClass({ + tableName: dummyTableName, + awsSdkV3Client: {} as DynamoDBClient, + }); // Assess expect(persistenceLayer).toEqual( @@ -202,7 +169,7 @@ describe('Class: DynamoDBPersistenceLayer', () => { }), }) ); - expect(consoleWarnSpy).toHaveBeenNthCalledWith( + expect(console.warn).toHaveBeenNthCalledWith( 1, 'awsSdkV3Client is not an AWS SDK v3 client, using default client' ); @@ -218,11 +185,8 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); describe('Method: _putRecord', () => { - test('when called with a record that meets conditions, it puts record in DynamoDB table', async () => { + it('puts the record in DynamoDB', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = 0; const record = new IdempotencyRecord({ @@ -258,9 +222,9 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); }); - test('when called with a record that uses composite key, it puts record in DynamoDB table', async () => { + it('puts the record in DynamoDB when using the provided composite key', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ + const persistenceLayer = new DynamoDBPersistenceLayerTestClass({ tableName: dummyTableName, staticPkValue: 'idempotency#my-lambda-function', sortKeyAttr: 'sortKey', @@ -301,11 +265,8 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); }); - test('when called with a record that has inProgressExpiryTimestamp, it puts record in DynamoDB table', async () => { + it('puts the record in DynamoDB when using an in progress expiry timestamp', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); const status = IdempotencyRecordStatus.INPROGRESS; const expiryTimestamp = getFutureTimestamp(10); const inProgressExpiryTimestamp = getFutureTimestamp(5); @@ -344,11 +305,8 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); }); - test('when called and and payload validation is enabled it puts record in DynamoDB table', async () => { + it('puts record in DynamoDB table when using payload validation', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); vi.spyOn(persistenceLayer, 'isPayloadValidationEnabled').mockReturnValue( true ); @@ -389,12 +347,8 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); }); - test('when called with a record that fails any condition, it throws IdempotencyItemAlreadyExistsError', async () => { + it('throws when called with a record that fails any condition', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); - const record = new IdempotencyRecord({ idempotencyKey: dummyKey, status: IdempotencyRecordStatus.EXPIRED, @@ -428,19 +382,13 @@ describe('Class: DynamoDBPersistenceLayer', () => { ); }); - test('when encountering an unknown error, it throws the causing error', async () => { + it('throws when encountering an unknown error', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); - const status = IdempotencyRecordStatus.EXPIRED; - const expiryTimestamp = 0; - const inProgressExpiryTimestamp = 0; const record = new IdempotencyRecord({ idempotencyKey: dummyKey, - status, - expiryTimestamp, - inProgressExpiryTimestamp, + status: IdempotencyRecordStatus.EXPIRED, + expiryTimestamp: 0, + inProgressExpiryTimestamp: 0, }); client.on(PutItemCommand).rejects(new Error()); @@ -450,39 +398,8 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); describe('Method: _getRecord', () => { - test('it calls DynamoDB with correct parameters', async () => { + it('gets the record from DynamoDB', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); - client.on(GetItemCommand).resolves({ - Item: marshall({ - id: dummyKey, - status: IdempotencyRecordStatus.INPROGRESS, - expiration: getFutureTimestamp(15), - in_progress_expiration: getFutureTimestamp(10), - data: {}, - }), - }); - - // Act - await persistenceLayer._getRecord(dummyKey); - - // Assess - expect(client).toReceiveCommandWith(GetItemCommand, { - TableName: dummyTableName, - Key: marshall({ - id: dummyKey, - }), - ConsistentRead: true, - }); - }); - - test('when called with a record whose key exists, it gets the correct record', async () => { - // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); const status = IdempotencyRecordStatus.INPROGRESS; const expiryTimestamp = getFutureTimestamp(15); const inProgressExpiryTimestamp = getFutureTimestamp(10); @@ -501,6 +418,13 @@ describe('Class: DynamoDBPersistenceLayer', () => { const record = await persistenceLayer._getRecord(dummyKey); // Assess + expect(client).toReceiveCommandWith(GetItemCommand, { + TableName: dummyTableName, + Key: marshall({ + id: dummyKey, + }), + ConsistentRead: true, + }); expect(record).toBeInstanceOf(IdempotencyRecord); expect(record.getStatus()).toEqual(IdempotencyRecordStatus.INPROGRESS); expect(record.idempotencyKey).toEqual(dummyKey); @@ -511,11 +435,8 @@ describe('Class: DynamoDBPersistenceLayer', () => { expect(record.expiryTimestamp).toEqual(expiryTimestamp); }); - test('when called with a record whose key does not exist, it throws IdempotencyItemNotFoundError', async () => { + it('throws when the record does not exist', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); client.on(GetItemCommand).resolves({ Item: undefined }); // Act & Assess @@ -524,9 +445,9 @@ describe('Class: DynamoDBPersistenceLayer', () => { ); }); - test('when called with a record in a table that use composite key, it builds the request correctly', async () => { + it('it builds the request correctly when using composite keys', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ + const persistenceLayer = new DynamoDBPersistenceLayerTestClass({ tableName: dummyTableName, staticPkValue: 'idempotency#my-lambda-function', sortKeyAttr: 'sortKey', @@ -554,9 +475,9 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); }); - test('when called with a record that had the ', async () => { + it('gets the record and validates the hash correctly', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ + const persistenceLayer = new DynamoDBPersistenceLayerTestClass({ tableName: dummyTableName, staticPkValue: 'idempotency#my-lambda-function', sortKeyAttr: 'sortKey', @@ -586,11 +507,8 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); describe('Method: _updateRecord', () => { - test('when called to update a record, it updates the item with the correct parameters', async () => { + it('it updates the item with the correct parameters', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = Date.now(); const record = new IdempotencyRecord({ @@ -617,7 +535,7 @@ describe('Class: DynamoDBPersistenceLayer', () => { '#response_data': 'data', }, ExpressionAttributeValues: marshall({ - ':status': IdempotencyRecordStatus.EXPIRED, + ':status': status, ':expiry': expiryTimestamp, ':response_data': {}, }), @@ -626,9 +544,6 @@ describe('Class: DynamoDBPersistenceLayer', () => { it('updates the item when the response_data is undefined', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = Date.now(); const record = new IdempotencyRecord({ @@ -653,26 +568,22 @@ describe('Class: DynamoDBPersistenceLayer', () => { '#expiry': 'expiration', }, ExpressionAttributeValues: marshall({ - ':status': IdempotencyRecordStatus.EXPIRED, + ':status': status, ':expiry': expiryTimestamp, }), }); }); - test('when called to update a record and payload validation is enabled, it adds the payload hash to the update expression', async () => { + it('uses the payload hash in the expression when payload validation is enabled', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); vi.spyOn( persistenceLayer, 'isPayloadValidationEnabled' ).mockImplementation(() => true); - const status = IdempotencyRecordStatus.EXPIRED; const expiryTimestamp = Date.now(); const record = new IdempotencyRecord({ idempotencyKey: dummyKey, - status, + status: IdempotencyRecordStatus.EXPIRED, expiryTimestamp, responseData: {}, payloadHash: 'someHash', @@ -706,17 +617,12 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); describe('Method: _deleteRecord', () => { - test('when called with a valid record, it calls the delete operation with the correct parameters', async () => { + it('deletes the record using the correct parameters', async () => { // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); - const status = IdempotencyRecordStatus.EXPIRED; - const expiryTimestamp = Date.now(); const record = new IdempotencyRecord({ idempotencyKey: dummyKey, - status, - expiryTimestamp, + status: IdempotencyRecordStatus.EXPIRED, + expiryTimestamp: Date.now(), }); // Act @@ -729,26 +635,4 @@ describe('Class: DynamoDBPersistenceLayer', () => { }); }); }); - - test('_putRecord throws Error when Item is undefined', async () => { - // Prepare - const persistenceLayer = new TestDynamoDBPersistenceLayer({ - tableName: dummyTableName, - }); - const mockRecord = new IdempotencyRecord({ - idempotencyKey: 'test-key', - status: 'INPROGRESS', - expiryTimestamp: Date.now(), - }); - - DynamoDBClient.prototype.send = vi.fn().mockRejectedValueOnce( - new ConditionalCheckFailedException({ - message: 'Conditional check failed', - $metadata: {}, - }) - ); - await expect( - persistenceLayer._putRecord(mockRecord) - ).rejects.toThrowError(); - }); }); diff --git a/packages/idempotency/vitest.config.ts b/packages/idempotency/vitest.config.ts index d5aa737c68..9f1196ef1f 100644 --- a/packages/idempotency/vitest.config.ts +++ b/packages/idempotency/vitest.config.ts @@ -3,5 +3,6 @@ import { defineProject } from 'vitest/config'; export default defineProject({ test: { environment: 'node', + setupFiles: ['../testing/src/setupEnv.ts'], }, }); diff --git a/packages/testing/package.json b/packages/testing/package.json index 2c539b6c6f..19c4c8d067 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -106,6 +106,7 @@ "promise-retry": "^2.0.1" }, "devDependencies": { - "@types/promise-retry": "^1.1.6" + "@types/promise-retry": "^1.1.6", + "aws-sdk-client-mock-vitest": "^4.0.0" } } diff --git a/packages/testing/src/setupEnv.ts b/packages/testing/src/setupEnv.ts new file mode 100644 index 0000000000..334d7c25c9 --- /dev/null +++ b/packages/testing/src/setupEnv.ts @@ -0,0 +1,31 @@ +import { toReceiveCommandWith } from 'aws-sdk-client-mock-vitest'; +import type { CustomMatcher } from 'aws-sdk-client-mock-vitest'; +import { expect, vi } from 'vitest'; + +expect.extend({ toReceiveCommandWith }); + +// Mock console methods to prevent output during tests +vi.spyOn(console, 'error').mockReturnValue(); +vi.spyOn(console, 'warn').mockReturnValue(); +vi.spyOn(console, 'debug').mockReturnValue(); +vi.spyOn(console, 'info').mockReturnValue(); +vi.spyOn(console, 'log').mockReturnValue(); + +declare module 'vitest' { + // biome-ignore lint/suspicious/noExplicitAny: vitest typings expect an any type + interface Assertion extends CustomMatcher {} + interface AsymmetricMatchersContaining extends CustomMatcher {} +} + +// Set up environment variables for testing +process.env._X_AMZN_TRACE_ID = '1-abcdef12-3456abcdef123456abcdef12'; +process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function'; +process.env.AWS_EXECUTION_ENV = 'nodejs20.x'; +process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128'; +if ( + process.env.AWS_REGION === undefined && + process.env.CDK_DEFAULT_REGION === undefined +) { + process.env.AWS_REGION = 'eu-west-1'; +} +process.env._HANDLER = 'index.handler'; diff --git a/vitest.config.ts b/vitest.config.ts index 03056b2d4e..989b2c970a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -26,5 +26,6 @@ export default defineConfig({ 'packages/tracer/**', ], }, + setupFiles: ['./packages/testing/src/setupEnv.ts'], }, }); From 60cb008ecdeb2160ed68ef00aa77d9092b800927 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 26 Sep 2024 17:42:58 +0200 Subject: [PATCH 3/5] chore: removed unused code --- .../tests/unit/idempotencyDecorator.test.ts | 309 +----------------- 1 file changed, 2 insertions(+), 307 deletions(-) diff --git a/packages/idempotency/tests/unit/idempotencyDecorator.test.ts b/packages/idempotency/tests/unit/idempotencyDecorator.test.ts index 885089977b..8a64b19afe 100644 --- a/packages/idempotency/tests/unit/idempotencyDecorator.test.ts +++ b/packages/idempotency/tests/unit/idempotencyDecorator.test.ts @@ -1,316 +1,11 @@ import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import context from '@aws-lambda-powertools/testing-utils/context'; import type { Context } from 'aws-lambda'; -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { IdempotencyRecordStatus } from '../../src/constants.js'; -import { - IdempotencyAlreadyInProgressError, - IdempotencyConfig, - IdempotencyInconsistentStateError, - IdempotencyItemAlreadyExistsError, - IdempotencyPersistenceLayerError, - idempotent, -} from '../../src/index.js'; -import { - BasePersistenceLayer, - IdempotencyRecord, -} from '../../src/persistence/index.js'; -import type { IdempotencyRecordOptions } from '../../src/types/index.js'; +import { describe, expect, it } from 'vitest'; +import { idempotent } from '../../src/index.js'; import { PersistenceLayerTestClass } from '../helpers/idempotencyUtils.js'; -/* const mockSaveInProgress = vi - .spyOn(BasePersistenceLayer.prototype, 'saveInProgress') - .mockImplementation(); -const mockSaveSuccess = vi - .spyOn(BasePersistenceLayer.prototype, 'saveSuccess') - .mockImplementation(); -const mockGetRecord = vi - .spyOn(BasePersistenceLayer.prototype, 'getRecord') - .mockImplementation(); */ - -const mockConfig: IdempotencyConfig = new IdempotencyConfig({}); - -const functionalityToDecorate = vi.fn(); - -class TestinClassWithLambdaHandler { - @idempotent({ - persistenceStore: new PersistenceLayerTestClass(), - }) - public async testing( - record: Record, - _context: Context - ): Promise { - functionalityToDecorate(record); - - return 'Hi'; - } -} - -class TestingClassWithFunctionDecorator { - public async handler( - record: Record, - context: Context - ): Promise { - mockConfig.registerLambdaContext(context); - - return this.proccessRecord(record, 'bar'); - } - - @idempotent({ - persistenceStore: new PersistenceLayerTestClass(), - config: mockConfig, - dataIndexArgument: 0, - }) - public async proccessRecord( - record: Record, - _foo: string - ): Promise { - functionalityToDecorate(record); - - return 'Processed Record'; - } -} - -const classWithLambdaHandler = new TestinClassWithLambdaHandler(); -const classWithFunctionDecorator = new TestingClassWithFunctionDecorator(); - describe('Given a class with a function to decorate', () => { - const keyValueToBeSaved = 'thisWillBeSaved'; - const inputRecord = { - testingKey: keyValueToBeSaved, - otherKey: 'thisWillNot', - }; - beforeEach(() => { - vi.clearAllMocks(); - vi.resetAllMocks(); - }); - - /* describe('When wrapping a function with no previous executions', () => { - beforeEach(async () => { - await classWithFunctionDecorator.handler(inputRecord, context); - }); - - test('Then it will save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toHaveBeenCalledWith( - inputRecord, - context.getRemainingTimeInMillis() - ); - }); - - test('Then it will call the function that was decorated', () => { - expect(functionalityToDecorate).toHaveBeenCalledWith(inputRecord); - }); - - test('Then it will save the record to COMPLETED with function return value', () => { - expect(mockSaveSuccess).toHaveBeenCalledWith( - inputRecord, - 'Processed Record' - ); - }); - }); */ - /* describe('When wrapping a handler function with no previous executions', () => { - beforeEach(async () => { - await classWithLambdaHandler.testing(inputRecord, context); - }); - - test('Then it will save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toHaveBeenCalledWith( - inputRecord, - context.getRemainingTimeInMillis() - ); - }); - - test('Then it will call the function that was decorated', () => { - expect(functionalityToDecorate).toHaveBeenCalledWith(inputRecord); - }); - - test('Then it will save the record to COMPLETED with function return value', () => { - expect(mockSaveSuccess).toHaveBeenCalledWith(inputRecord, 'Hi'); - }); - }); */ - - /* describe('When decorating a function with previous execution that is INPROGRESS', () => { - let resultingError: Error; - beforeEach(async () => { - mockSaveInProgress.mockRejectedValue( - new IdempotencyItemAlreadyExistsError() - ); - const idempotencyOptions: IdempotencyRecordOptions = { - idempotencyKey: 'key', - status: IdempotencyRecordStatus.INPROGRESS, - }; - mockGetRecord.mockResolvedValue( - new IdempotencyRecord(idempotencyOptions) - ); - try { - await classWithLambdaHandler.testing(inputRecord, context); - } catch (e) { - resultingError = e as Error; - } - }); - - test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toHaveBeenCalledWith( - inputRecord, - context.getRemainingTimeInMillis() - ); - }); - - test('Then it will get the previous execution record', () => { - expect(mockGetRecord).toHaveBeenCalledWith(inputRecord); - }); - - test('Then it will not call the function that was decorated', () => { - expect(functionalityToDecorate).not.toHaveBeenCalled(); - }); - - test('Then an IdempotencyAlreadyInProgressError is thrown', () => { - expect(resultingError).toBeInstanceOf(IdempotencyAlreadyInProgressError); - }); - }); */ - - /* describe('When decorating a function with previous execution that is EXPIRED', () => { - let resultingError: Error; - beforeEach(async () => { - mockSaveInProgress.mockRejectedValue( - new IdempotencyItemAlreadyExistsError() - ); - const idempotencyOptions: IdempotencyRecordOptions = { - idempotencyKey: 'key', - status: IdempotencyRecordStatus.EXPIRED, - }; - mockGetRecord.mockResolvedValue( - new IdempotencyRecord(idempotencyOptions) - ); - try { - await classWithLambdaHandler.testing(inputRecord, context); - } catch (e) { - resultingError = e as Error; - } - }); - - test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toHaveBeenCalledWith( - inputRecord, - context.getRemainingTimeInMillis() - ); - }); - - test('Then it will get the previous execution record', () => { - expect(mockGetRecord).toHaveBeenCalledWith(inputRecord); - }); - - test('Then it will not call the function that was decorated', () => { - expect(functionalityToDecorate).not.toHaveBeenCalled(); - }); - - test('Then an IdempotencyInconsistentStateError is thrown', () => { - expect(resultingError).toBeInstanceOf(IdempotencyInconsistentStateError); - }); - }); */ - - /* describe('When wrapping a function with previous execution that is COMPLETED', () => { - beforeEach(async () => { - mockSaveInProgress.mockRejectedValue( - new IdempotencyItemAlreadyExistsError() - ); - const idempotencyOptions: IdempotencyRecordOptions = { - idempotencyKey: 'key', - status: IdempotencyRecordStatus.COMPLETED, - responseData: 'Hi', - }; - - mockGetRecord.mockResolvedValue( - new IdempotencyRecord(idempotencyOptions) - ); - await classWithLambdaHandler.testing(inputRecord, context); - }); - - test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toHaveBeenCalledWith( - inputRecord, - context.getRemainingTimeInMillis() - ); - }); - - test('Then it will get the previous execution record', () => { - expect(mockGetRecord).toHaveBeenCalledWith(inputRecord); - }); - - test('Then it will not call decorated functionality', () => { - expect(functionalityToDecorate).not.toHaveBeenCalledWith(inputRecord); - }); - }); */ - - /* describe('When wrapping a function with issues saving the record', () => { - class TestinClassWithLambdaHandlerWithConfig { - @idempotent({ - persistenceStore: new PersistenceLayerTestClass(), - config: new IdempotencyConfig({ lambdaContext: context }), - }) - public testing(record: Record): string { - functionalityToDecorate(record); - - return 'Hi'; - } - } - - let resultingError: Error; - beforeEach(async () => { - mockSaveInProgress.mockRejectedValue(new Error('RandomError')); - const classWithLambdaHandlerWithConfig = - new TestinClassWithLambdaHandlerWithConfig(); - try { - await classWithLambdaHandlerWithConfig.testing(inputRecord); - } catch (e) { - resultingError = e as Error; - } - }); - - test('Then it will attempt to save the record to INPROGRESS', () => { - expect(mockSaveInProgress).toHaveBeenCalledWith( - inputRecord, - context.getRemainingTimeInMillis() - ); - }); - - test('Then an IdempotencyPersistenceLayerError is thrown', () => { - expect(resultingError).toBeInstanceOf(IdempotencyPersistenceLayerError); - }); - }); */ - - /* describe('When idempotency is disabled', () => { - beforeAll(async () => { - process.env.POWERTOOLS_IDEMPOTENCY_DISABLED = 'true'; - class TestingClassWithIdempotencyDisabled { - @idempotent({ - persistenceStore: new PersistenceLayerTestClass(), - config: new IdempotencyConfig({ lambdaContext: context }), - }) - public async testing( - record: Record, - _context: Context - ): Promise { - functionalityToDecorate(record); - - return 'Hi'; - } - } - const classWithoutIdempotencyDisabled = - new TestingClassWithIdempotencyDisabled(); - await classWithoutIdempotencyDisabled.testing(inputRecord, context); - }); - - test('Then it will skip ipdemotency', async () => { - expect(mockSaveInProgress).not.toHaveBeenCalled(); - expect(mockSaveSuccess).not.toHaveBeenCalled(); - }); - - afterAll(() => { - process.env.POWERTOOLS_IDEMPOTENCY_DISABLED = undefined; - }); - }); */ - it('maintains the scope of the decorated function', async () => { // Prepare class TestClass implements LambdaInterface { From 7877121843f5acaed08ae96b3572f545ef31aaa7 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 26 Sep 2024 17:45:10 +0200 Subject: [PATCH 4/5] chore: removed unused code --- .../tests/unit/persistence/DynamoDbPersistenceLayer.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts index c2f4a35066..0649305d56 100644 --- a/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts +++ b/packages/idempotency/tests/unit/persistence/DynamoDbPersistenceLayer.test.ts @@ -16,7 +16,6 @@ import { describe, expect, it, - test, vi, } from 'vitest'; import { From a77fe0685e8fe9e954cee0abef6ae5c86d081164 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Thu, 26 Sep 2024 18:46:56 +0000 Subject: [PATCH 5/5] chore: remove commented code --- .../tests/unit/IdempotencyHandler.test.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/idempotency/tests/unit/IdempotencyHandler.test.ts b/packages/idempotency/tests/unit/IdempotencyHandler.test.ts index 0c830f76de..c64e3c10f7 100644 --- a/packages/idempotency/tests/unit/IdempotencyHandler.test.ts +++ b/packages/idempotency/tests/unit/IdempotencyHandler.test.ts @@ -109,22 +109,6 @@ describe('Class IdempotencyHandler', () => { expect(mockResponseHook).not.toHaveBeenCalled(); }); - /* it('when response hook is provided, it should should call responseHook during an idempotent request', () => { - // Prepare - const stubRecord = new IdempotencyRecord({ - idempotencyKey: 'idempotencyKey', - responseData: { responseData: 'responseData' }, - payloadHash: 'payloadHash', - status: IdempotencyRecordStatus.COMPLETED, - }); - - // Act - idempotentHandler.determineResultFromIdempotencyRecord(stubRecord); - - // Assess - expect(mockResponseHook).toHaveBeenCalled(); - }); */ - it('calls the provided response hook', () => { // Prepare interface HandlerResponse {