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 fad0e768af..c34c2fd01e 100644 --- a/.github/workflows/reusable-run-linting-check-and-unit-tests.yml +++ b/.github/workflows/reusable-run-linting-check-and-unit-tests.yml @@ -49,6 +49,7 @@ jobs: "packages/logger", "packages/tracer", "packages/parser", + "packages/parameters", ] fail-fast: false steps: @@ -91,12 +92,10 @@ jobs: nodeVersion: ${{ matrix.version }} - name: Run linting run: | - npm run lint -w packages/metrics \ - -w packages/parameters + npm run lint -w packages/metrics - name: Run unit tests run: | - npm t -w packages/metrics \ - -w packages/parameters + npm t -w packages/metrics check-examples: runs-on: ubuntu-latest env: diff --git a/.husky/pre-push b/.husky/pre-push index ca1c0af1f6..dbfd4143b0 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,6 +1,5 @@ npm t \ - -w packages/metrics \ - -w packages/parameters + -w packages/metrics npx vitest --run \ --exclude tests/unit/layer-publisher.test.ts \ diff --git a/packages/parameters/jest.config.cjs b/packages/parameters/jest.config.cjs deleted file mode 100644 index d06944c4d3..0000000000 --- a/packages/parameters/jest.config.cjs +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - displayName: { - name: 'Powertools for AWS Lambda (TypeScript) utility: PARAMETERS', - color: 'magenta', - }, - runner: 'groups', - preset: 'ts-jest', - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - transform: { - '^.+\\.ts?$': 'ts-jest', - }, - moduleFileExtensions: ['js', 'ts'], - collectCoverageFrom: ['**/src/**/*.ts', '!**/node_modules/**'], - testMatch: ['**/?(*.)+(spec|test).ts'], - roots: ['/src', '/tests'], - testPathIgnorePatterns: ['/node_modules/'], - testEnvironment: 'node', - coveragePathIgnorePatterns: [ - '/node_modules/', - '/types/', - '/src/docs.ts', // this file is only used for documentation - ], - coverageThreshold: { - global: { - statements: 100, - branches: 100, - functions: 100, - lines: 100, - }, - }, - coverageReporters: ['json-summary', 'text', 'lcov'], - setupFiles: ['/tests/helpers/populateEnvironmentVariables.ts'], -}; diff --git a/packages/parameters/package.json b/packages/parameters/package.json index af51a9bded..3ea24215dd 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -10,13 +10,14 @@ "access": "public" }, "scripts": { - "test": "npm run test:unit", - "test:unit": "jest --group=unit --detectOpenHandles --coverage --verbose", - "jest": "jest --detectOpenHandles --verbose", - "test:e2e:nodejs18x": "RUNTIME=nodejs18x jest --group=e2e", - "test:e2e:nodejs20x": "RUNTIME=nodejs20x jest --group=e2e", - "test:e2e": "jest --group=e2e", - "watch": "jest --watch", + "test": "vitest --run tests/unit", + "test:unit": "vitest --run tests/unit", + "test:unit:coverage": "vitest --run tests/unit --coverage.enabled --coverage.thresholds.100 --coverage.include='src/**'", + "test:unit:types": "vitest --run tests/types --typecheck", + "test:unit:watch": "vitest tests/unit", + "test:e2e:nodejs18x": "RUNTIME=nodejs18x vitest --run tests/e2e", + "test:e2e:nodejs20x": "RUNTIME=nodejs20x vitest --run tests/e2e", + "test:e2e": "vitest --run tests/e2e", "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/parameters/src/appconfig/AppConfigProvider.ts b/packages/parameters/src/appconfig/AppConfigProvider.ts index 9fc781b688..74ee59aeab 100644 --- a/packages/parameters/src/appconfig/AppConfigProvider.ts +++ b/packages/parameters/src/appconfig/AppConfigProvider.ts @@ -267,9 +267,12 @@ class AppConfigProvider extends BaseProvider { /** * Retrieving multiple configurations is not supported by AWS AppConfig. */ - public async getMultiple(path: string, _options?: unknown): Promise { + /* v8 ignore start */ public async getMultiple( + path: string, + _options?: unknown + ): Promise { await super.getMultiple(path); - } + } /* v8 ignore stop */ /** * Retrieve a configuration from AWS AppConfig. diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index 9bc4fd3f3f..1f0e0cb996 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -208,9 +208,12 @@ class SecretsProvider extends BaseProvider { /** * Retrieving multiple parameter values is not supported with AWS Secrets Manager. */ - public async getMultiple(path: string, _options?: unknown): Promise { + /* v8 ignore start */ public async getMultiple( + path: string, + _options?: unknown + ): Promise { await super.getMultiple(path); - } + } /* v8 ignore stop */ /** * Retrieve a configuration from AWS Secrets Manager. diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts index 5007636c9d..3bd7e3a9b7 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts @@ -1,8 +1,3 @@ -/** - * Test AppConfigProvider class - * - * @group e2e/parameters/appconfig/class - */ import { join } from 'node:path'; import { TestInvocationLogs, @@ -11,6 +6,7 @@ import { } from '@aws-lambda-powertools/testing-utils'; import { TestNodejsFunction } from '@aws-lambda-powertools/testing-utils/resources/lambda'; import { toBase64 } from '@smithy/util-base64'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { TestAppConfigWithProfiles } from '../helpers/resources.js'; import { RESOURCE_NAME_PREFIX, diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts index 90b9f65eed..17c56600d2 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts @@ -1,8 +1,3 @@ -/** - * Test DynamoDBProvider class - * - * @group e2e/parameters/dynamodb/class - */ import { join } from 'node:path'; import { TestInvocationLogs, @@ -11,6 +6,7 @@ import { } from '@aws-lambda-powertools/testing-utils'; import { TestNodejsFunction } from '@aws-lambda-powertools/testing-utils/resources/lambda'; import { AttributeType } from 'aws-cdk-lib/aws-dynamodb'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { TestDynamodbTableWithItems } from '../helpers/resources.js'; import { RESOURCE_NAME_PREFIX, diff --git a/packages/parameters/tests/e2e/secretsProvider.class.test.ts b/packages/parameters/tests/e2e/secretsProvider.class.test.ts index 6ba4bbd1db..4acbd65eba 100644 --- a/packages/parameters/tests/e2e/secretsProvider.class.test.ts +++ b/packages/parameters/tests/e2e/secretsProvider.class.test.ts @@ -1,8 +1,3 @@ -/** - * Test SecretsPorovider class - * - * @group e2e/parameters/secrets/class - */ import { join } from 'node:path'; import { TestInvocationLogs, @@ -11,6 +6,7 @@ import { } from '@aws-lambda-powertools/testing-utils'; import { TestNodejsFunction } from '@aws-lambda-powertools/testing-utils/resources/lambda'; import { SecretValue } from 'aws-cdk-lib'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { TestSecret } from '../helpers/resources.js'; import { RESOURCE_NAME_PREFIX, diff --git a/packages/parameters/tests/e2e/ssmProvider.class.test.ts b/packages/parameters/tests/e2e/ssmProvider.class.test.ts index bb5618a198..71d7f75a1d 100644 --- a/packages/parameters/tests/e2e/ssmProvider.class.test.ts +++ b/packages/parameters/tests/e2e/ssmProvider.class.test.ts @@ -1,8 +1,3 @@ -/** - * Test SSMProvider class - * - * @group e2e/parameters/ssm/class - */ import { join } from 'node:path'; import { TestInvocationLogs, @@ -10,6 +5,7 @@ import { invokeFunctionOnce, } from '@aws-lambda-powertools/testing-utils'; import { TestNodejsFunction } from '@aws-lambda-powertools/testing-utils/resources/lambda'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { TestSecureStringParameter, TestStringParameter, diff --git a/packages/parameters/tests/helpers/populateEnvironmentVariables.ts b/packages/parameters/tests/helpers/populateEnvironmentVariables.ts deleted file mode 100644 index acb0fd0595..0000000000 --- a/packages/parameters/tests/helpers/populateEnvironmentVariables.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Reserved variables -process.env._X_AMZN_TRACE_ID = - 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; -process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function'; -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'; -} diff --git a/packages/parameters/tests/tsconfig.json b/packages/parameters/tests/tsconfig.json index 45ba862a85..316509ab78 100644 --- a/packages/parameters/tests/tsconfig.json +++ b/packages/parameters/tests/tsconfig.json @@ -1,8 +1,12 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "rootDir": "../", + "rootDir": "../../", "noEmit": true }, - "include": ["../src/**/*", "./**/*"] -} + "include": [ + "../../testing/src/setupEnv.ts", + "../src/**/*", + "./**/*", + ] +} \ No newline at end of file diff --git a/packages/parameters/tests/types/returnTypes.test.ts b/packages/parameters/tests/types/returnTypes.test.ts new file mode 100644 index 0000000000..35d42cdd9e --- /dev/null +++ b/packages/parameters/tests/types/returnTypes.test.ts @@ -0,0 +1,212 @@ +import type { JSONValue } from '@aws-lambda-powertools/commons/types'; +import { + AppConfigDataClient, + GetLatestConfigurationCommand, + StartConfigurationSessionCommand, +} from '@aws-sdk/client-appconfigdata'; +import { + GetSecretValueCommand, + SecretsManagerClient, +} from '@aws-sdk/client-secrets-manager'; +import { + GetParameterCommand, + GetParametersByPathCommand, + SSMClient, +} from '@aws-sdk/client-ssm'; +import { toBase64 } from '@smithy/util-base64'; +import { Uint8ArrayBlobAdapter } from '@smithy/util-stream'; +import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expectTypeOf, it } from 'vitest'; +import { getAppConfig } from '../../src/appconfig/index.js'; +import { Transform } from '../../src/index.js'; +import { getSecret } from '../../src/secrets/index.js'; +import { getParameter, getParameters } from '../../src/ssm/index.js'; + +describe('Return types', () => { + const appConfigclient = mockClient(AppConfigDataClient); + const ssmClient = mockClient(SSMClient); + const secretsClient = mockClient(SecretsManagerClient); + const encoder = new TextEncoder(); + + beforeEach(() => { + appConfigclient.reset(); + ssmClient.reset(); + }); + + it('returns a string value when called with transform: `binary` option', async () => { + // Prepare + const expectedValue = 'my-value'; + const mockData = Uint8ArrayBlobAdapter.fromString( + toBase64(encoder.encode(expectedValue)) + ); + appConfigclient + .on(StartConfigurationSessionCommand) + .resolves({ + InitialConfigurationToken: 'abcdefg', + }) + .on(GetLatestConfigurationCommand) + .resolves({ + Configuration: mockData, + NextPollConfigurationToken: 'hijklmn', + }); + + // Act + const result: string | undefined = await getAppConfig('my-config', { + application: 'my-app', + environment: 'prod', + transform: Transform.BINARY, + }); + + // Assess + expectTypeOf(result).toMatchTypeOf(); + }); + + it('returns a JSON value when using the transform `json` option', async () => { + // Prepare + const expectedValue = { foo: 'my-value' }; + const mockData = Uint8ArrayBlobAdapter.fromString( + JSON.stringify(expectedValue) + ); + appConfigclient + .on(StartConfigurationSessionCommand) + .resolves({ + InitialConfigurationToken: 'abcdefg', + }) + .on(GetLatestConfigurationCommand) + .resolves({ + Configuration: mockData, + NextPollConfigurationToken: 'hijklmn', + }); + + // Act + const result: JSONValue = await getAppConfig('my-config', { + application: 'my-app', + environment: 'prod', + transform: Transform.JSON, + }); + + // Assess + expectTypeOf(result).toMatchTypeOf(); + }); + + it('sets the correct type when called and transform `JSON` is specified', async () => { + // Prepare + const parameterName = 'foo'; + const parameterValue = JSON.stringify({ hello: 'world' }); + ssmClient.on(GetParameterCommand).resolves({ + Parameter: { + Value: parameterValue, + }, + }); + + // Act + const value: Record | undefined = await getParameter( + parameterName, + { transform: 'json' } + ); + + // Assess + expectTypeOf(value).toMatchTypeOf | undefined>(); + }); + + it('casts the provided generic type when called and transform `JSON`', async () => { + // Prepare + const parameterName = 'foo'; + const parameterValue = JSON.stringify(5); + ssmClient.on(GetParameterCommand).resolves({ + Parameter: { + Value: parameterValue, + }, + }); + + // Act + const value: number | undefined = await getParameter( + parameterName, + { transform: 'json' } + ); + + // Assess + expectTypeOf(value).toMatchTypeOf(); + }); + + it('sets the correct type when called and transform `JSON` is specified', async () => { + // Prepare + const parameterPath = '/foo'; + const parameterValue = JSON.stringify({ hello: 'world' }); + ssmClient.on(GetParametersByPathCommand).resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: parameterValue, + }, + ], + }); + + // Act + const parameters: Record> | undefined = + await getParameters(parameterPath); + + // Assess + expectTypeOf(parameters).toMatchTypeOf< + Record> | undefined + >(); + }); + + it('casts the provided generic type when called and transform `JSON`', async () => { + // Prepare + const parameterPath = '/foo'; + const parameterValue = JSON.stringify(5); + ssmClient.on(GetParametersByPathCommand).resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: parameterValue, + }, + ], + }); + + // Act + const parameters: Record> | undefined = + await getParameters>(parameterPath); + + // Assess + expectTypeOf(parameters).toMatchTypeOf< + Record> | undefined + >(); + }); + + it('sets the correct type when called and transform `JSON` is specified', async () => { + // Prepare + const secretName = 'foo'; + const secretValue = JSON.stringify({ hello: 'world' }); + secretsClient.on(GetSecretValueCommand).resolves({ + SecretString: secretValue, + }); + + // Act + const value: Record | undefined = await getSecret( + secretName, + { transform: 'json' } + ); + + // Assess + expectTypeOf(value).toMatchTypeOf | undefined>(); + }); + + it('casts the provided generic type when called and transform `JSON`', async () => { + // Prepare + const secretName = 'foo'; + const secretValue = JSON.stringify(5); + secretsClient.on(GetSecretValueCommand).resolves({ + SecretString: secretValue, + }); + + // Act + const value: number | undefined = await getSecret(secretName, { + transform: 'json', + }); + + // Assess + expectTypeOf(value).toMatchTypeOf(); + }); +}); diff --git a/packages/parameters/tests/unit/AppConfigProvider.test.ts b/packages/parameters/tests/unit/AppConfigProvider.test.ts index 0a0ef35f24..c07eadf9b1 100644 --- a/packages/parameters/tests/unit/AppConfigProvider.test.ts +++ b/packages/parameters/tests/unit/AppConfigProvider.test.ts @@ -1,8 +1,3 @@ -/** - * Test AppConfigProvider class - * - * @group unit/parameters/AppConfigProvider/class - */ import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; import { AppConfigDataClient, @@ -11,31 +6,28 @@ import { } from '@aws-sdk/client-appconfigdata'; import { Uint8ArrayBlobAdapter } from '@smithy/util-stream'; import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { AppConfigProvider } from '../../src/appconfig/index.js'; import { ExpirableValue } from '../../src/base/ExpirableValue.js'; -import type { AppConfigProviderOptions } from '../../src/types/AppConfigProvider.js'; -import 'aws-sdk-client-mock-jest'; import { APPCONFIG_TOKEN_EXPIRATION } from '../../src/constants'; +import type { AppConfigProviderOptions } from '../../src/types/AppConfigProvider.js'; -jest.mock('@aws-lambda-powertools/commons', () => ({ - ...jest.requireActual('@aws-lambda-powertools/commons'), - addUserAgentMiddleware: jest.fn(), +vi.mock('@aws-lambda-powertools/commons', async (importOriginal) => ({ + ...(await importOriginal()), + addUserAgentMiddleware: vi.fn(), })); -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Class: AppConfigProvider', () => { const client = mockClient(AppConfigDataClient); beforeEach(() => { - jest.clearAllMocks(); - }); - - afterEach(() => { + vi.clearAllMocks(); client.reset(); }); describe('Method: constructor', () => { - test('when the class instantiates without SDK client and client config it has default options', async () => { + it('instantiates a new AWS SDK and adds a middleware to it', async () => { // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -54,7 +46,7 @@ describe('Class: AppConfigProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { + it('instantiates a new AWS SDK client using the provided config', async () => { // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -72,7 +64,7 @@ describe('Class: AppConfigProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - test('when the user provides an SDK client in the options, the class instantiates with it', async () => { + it('uses the provided AWS SDK client', async () => { // Prepare const awsSdkV3Client = new AppConfigDataClient({ endpoint: 'http://localhost:8000', @@ -104,7 +96,7 @@ describe('Class: AppConfigProvider', () => { environment: 'MyAppProdEnv', awsSdkV3Client: awsSdkV3Client as AppConfigDataClient, }; - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarnSpy = vi.spyOn(console, 'warn'); // Act const provider = new AppConfigProvider(options); @@ -124,7 +116,7 @@ describe('Class: AppConfigProvider', () => { }); describe('Method: _get', () => { - test('when called with name and options, it returns binary configuration', async () => { + it('returns the binary configuration as-is', async () => { // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -155,7 +147,7 @@ describe('Class: AppConfigProvider', () => { expect(result).toBe(mockData); }); - test('when called without application option, it will be retrieved from POWERTOOLS_SERVICE_NAME and provider successfully return configuration', async () => { + it('uses the application name from the POWERTOOLS_SERVICE_NAME env variable', async () => { // Prepare process.env.POWERTOOLS_SERVICE_NAME = 'MyApp'; const config = { @@ -186,7 +178,7 @@ describe('Class: AppConfigProvider', () => { expect(result).toBe(mockData); }); - test('when called without application option and POWERTOOLS_SERVICE_NAME is not set, it throws an Error', async () => { + it('throws when no application is set', async () => { // Prepare process.env.POWERTOOLS_SERVICE_NAME = ''; const options = { @@ -199,7 +191,7 @@ describe('Class: AppConfigProvider', () => { }).toThrow(); }); - test('when configuration response does not have the next token it should force a new session by removing the stored token', async () => { + it('invalidates the cached token when the response does not have a next token present, thus forcing a new session', async () => { // Prepare class AppConfigProviderMock extends AppConfigProvider { public _addToStore(key: string, value: string): void { @@ -236,7 +228,7 @@ describe('Class: AppConfigProvider', () => { expect(provider._storeHas(name)).toBe(false); }); - test('when session response does not have an initial token, it throws an error', async () => { + it('throws when the session response does not include an initial token', async () => { // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -253,7 +245,7 @@ describe('Class: AppConfigProvider', () => { await expect(provider.get(name)).rejects.toThrow(); }); - test('when session returns an empty configuration on the second call, it returns the last value', async () => { + it('returns the last value when the session returns an empty configuration on the second call', async () => { // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -295,7 +287,7 @@ describe('Class: AppConfigProvider', () => { expect(result2).toBe(mockData); }); - test('when the session token has expired, it starts a new session and retrieves the token', async () => { + it('starts a new session and fetches the token when the session token has expired', async () => { // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -332,12 +324,12 @@ describe('Class: AppConfigProvider', () => { Configuration: mockData2, NextPollConfigurationToken: fakeNextToken1, }); - jest.setSystemTime(new Date('2022-03-10')); + vi.setSystemTime(new Date('2022-03-10')); // Act const result1 = await provider.get(name, { forceFetch: true }); // Mock time skip of 24hrs - jest.setSystemTime(new Date('2022-03-11')); + vi.setSystemTime(new Date('2022-03-11')); const result2 = await provider.get(name, { forceFetch: true }); // Assess @@ -347,7 +339,7 @@ describe('Class: AppConfigProvider', () => { }); describe('Method: _getMultiple', () => { - test('when called it throws an Error, because this method is not supported by AppConfig API', async () => { + it('throws when called because of the method being unsopported', async () => { // Prepare const config = { application: 'MyApp', @@ -365,11 +357,11 @@ describe('Class: AppConfigProvider', () => { describe('Class: ExpirableValue', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('Method: constructor', () => { - test('when created, it has ttl set to at least maxAge seconds from test start', () => { + it('has TTL set to at least maxAge seconds from test start', () => { // Prepare const seconds = 10; const nowTimestamp = Date.now(); @@ -384,7 +376,7 @@ describe('Class: ExpirableValue', () => { }); describe('Method: isExpired', () => { - test('when called, it returns true when maxAge is in the future', () => { + it('returns true when maxAge is in the future', () => { // Prepare const seconds = 60; @@ -395,7 +387,7 @@ describe('Class: ExpirableValue', () => { expect(expirableValue.isExpired()).toBeFalsy(); }); - test('when called, it returns false when maxAge is in the past', () => { + it('returns false when maxAge is in the past', () => { // Prepare const seconds = -60; diff --git a/packages/parameters/tests/unit/BaseProvider.test.ts b/packages/parameters/tests/unit/BaseProvider.test.ts index 977727eac5..d69c19c969 100644 --- a/packages/parameters/tests/unit/BaseProvider.test.ts +++ b/packages/parameters/tests/unit/BaseProvider.test.ts @@ -1,9 +1,5 @@ -/** - * Test BaseProvider class - * - * @group unit/parameters/baseProvider/class - */ import { toBase64 } from '@smithy/util-base64'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { ExpirableValue } from '../../src/base/ExpirableValue.js'; import { BaseProvider, @@ -20,9 +16,9 @@ import { } from '../../src/index.js'; const encoder = new TextEncoder(); -jest.mock('@aws-lambda-powertools/commons', () => ({ - ...jest.requireActual('@aws-lambda-powertools/commons'), - addUserAgentMiddleware: jest.fn(), +vi.mock('@aws-lambda-powertools/commons', async (importOriginal) => ({ + ...(await importOriginal()), + addUserAgentMiddleware: vi.fn(), })); class TestProvider extends BaseProvider { @@ -63,11 +59,11 @@ class TestProvider extends BaseProvider { describe('Class: BaseProvider', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('Method: addToCache', () => { - test('when called with a value and maxAge equal to 0, it skips the cache entirely', () => { + it('skips the cache when maxAge is set to 0', () => { // Prepare const provider = new TestProvider(); @@ -78,7 +74,7 @@ describe('Class: BaseProvider', () => { expect(provider._getKeyTest('my-key')).toBeUndefined(); }); - test('when called with a value and maxAge, it places the value in the cache', () => { + it('sets the value in the cache when maxAge is set', () => { // Prepare const provider = new TestProvider(); @@ -95,7 +91,7 @@ describe('Class: BaseProvider', () => { }); describe('Method: get', () => { - test('when the underlying _get method throws an error, it throws a GetParameterError', async () => { + it('throws a GetParameterError when the underlying _get method throws an error', async () => { // Prepare const provider = new TestProvider(); @@ -105,7 +101,7 @@ describe('Class: BaseProvider', () => { ); }); - test('when called and a cached value is available, it returns an the cached value', async () => { + it('returns the cached value when one is available in the cache', async () => { // Prepare const provider = new TestProvider(); provider._add( @@ -120,15 +116,11 @@ describe('Class: BaseProvider', () => { expect(values).toEqual('my-value'); }); - test('when called with forceFetch, even whith cached value available, it returns the remote value', async () => { + it('skips the cache when forceFetch is set', async () => { // Prepare const mockData = 'my-remote-value'; const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_get').mockResolvedValue(mockData); provider._add( ['my-parameter', undefined].toString(), new ExpirableValue('my-value', 5000) @@ -141,17 +133,13 @@ describe('Class: BaseProvider', () => { expect(values).toEqual('my-remote-value'); }); - test('when called and cached value is expired, it returns the remote value', async () => { + it('returns the remote value when the cached one is expired', async () => { // Prepare const mockData = 'my-remote-value'; const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_get').mockResolvedValue(mockData); const expirableValue = new ExpirableValue('my-other-value', 0); - jest.spyOn(expirableValue, 'isExpired').mockImplementation(() => true); + vi.spyOn(expirableValue, 'isExpired').mockImplementation(() => true); provider._add(['my-path', undefined].toString(), expirableValue); // Act @@ -161,15 +149,11 @@ describe('Class: BaseProvider', () => { expect(values).toEqual('my-remote-value'); }); - test('when called with a json transform, and the value is a valid string representation of a JSON, it returns an object', async () => { + it('transforms a JSON object', async () => { // Prepare const mockData = JSON.stringify({ foo: 'bar' }); const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_get').mockResolvedValue(mockData); // Act const value = await provider.get('my-parameter', { transform: 'json' }); @@ -181,15 +165,11 @@ describe('Class: BaseProvider', () => { }); }); - test('when called with a json transform, and the value is NOT a valid string representation of a JSON, it throws', async () => { + it('throws when attempting to transform an invalid JSON', async () => { // Prepare const mockData = `${JSON.stringify({ foo: 'bar' })}{`; const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_get').mockResolvedValue(mockData); // Act & Assess await expect( @@ -197,15 +177,11 @@ describe('Class: BaseProvider', () => { ).rejects.toThrowError(TransformParameterError); }); - test('when called with a binary transform, and the value is a valid string representation of a binary, it returns the decoded value', async () => { + it('returns the decoded value when called with a binary transform, and the value is a valid string representation of a binary', async () => { // Prepare const mockData = toBase64(encoder.encode('my-value')); const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_get').mockResolvedValue(mockData); // Act const value = await provider.get('my-parameter', { transform: 'binary' }); @@ -215,15 +191,11 @@ describe('Class: BaseProvider', () => { expect(value).toEqual('my-value'); }); - test('when called with a binary transform, and the value is NOT a valid string representation of a binary, it throws', async () => { + it('throws when called with a binary transform, and the value is NOT a valid string representation of a binary', async () => { // Prepare const mockData = 'qw'; const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_get').mockResolvedValue(mockData); // Act & Assess await expect( @@ -231,18 +203,13 @@ describe('Class: BaseProvider', () => { ).rejects.toThrowError(TransformParameterError); }); - test('when called with no transform, and the value is a valid binary, it returns the binary as-is', async () => { + it('returns the binary as-is when called with no transform, and the value is a valid binary', async () => { // Prepare const mockData = encoder.encode('my-value'); const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => - new Promise((resolve, _reject) => - resolve(mockData as unknown as string) - ) - ); + vi.spyOn(provider, '_get').mockResolvedValue( + mockData as unknown as string + ); // Act const value = await provider.get('my-parameter'); @@ -252,18 +219,13 @@ describe('Class: BaseProvider', () => { expect(value).toEqual(mockData); }); - test('when called with a binary transform, and the value is a valid binary but NOT base64 encoded, it throws', async () => { + it('throws when called with a binary transform, and the value is a valid binary but NOT base64 encoded', async () => { // Prepare const mockData = encoder.encode('my-value'); const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => - new Promise((resolve, _reject) => - resolve(mockData as unknown as string) - ) - ); + vi.spyOn(provider, '_get').mockResolvedValue( + mockData as unknown as string + ); // Act & Assess await expect( @@ -271,15 +233,11 @@ describe('Class: BaseProvider', () => { ).rejects.toThrowError(TransformParameterError); }); - test('when called with an auto transform, and the value is a valid JSON, it returns the parsed value', async () => { + it('returns the parsed value when called with an auto transform, and the value is a valid JSON', async () => { // Prepare const mockData = JSON.stringify({ foo: 'bar' }); const provider = new TestProvider(); - jest - .spyOn(provider, '_get') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_get').mockResolvedValue(mockData); // Act const value = await provider.get('my-parameter.json', { @@ -292,15 +250,12 @@ describe('Class: BaseProvider', () => { }); describe('Method: getMultiple', () => { - test('when the underlying _getMultiple throws an error, it throws a GetParameterError', async () => { + it('throws a GetParameterError when the underlying _getMultiple throws', async () => { // Prepare const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => - new Promise((_resolve, reject) => reject(new Error('Some error.'))) - ); + vi.spyOn(provider, '_getMultiple').mockRejectedValue( + new Error('Some error.') + ); // Act & Assess await expect(provider.getMultiple('my-parameter')).rejects.toThrowError( @@ -308,15 +263,11 @@ describe('Class: BaseProvider', () => { ); }); - test('when the underlying _getMultiple does not return an object, it throws a GetParameterError', async () => { + it('throws a GetParameterError when the underlying _getMultiple does not return an object', async () => { // Prepare const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation( - () => - new Promise((resolve, _reject) => - // need to type cast to force the error - resolve('not an object' as unknown as Record) - ) + vi.spyOn(provider, '_getMultiple').mockResolvedValue( + 'not an object' as unknown as Record ); // Act & Assess @@ -325,15 +276,11 @@ describe('Class: BaseProvider', () => { ); }); - test('when called with a json transform, and all the values are a valid string representation of a JSON, it returns an object with all the values', async () => { + it('returns an object with transformed values when called with a json transform, and all the values are valid', async () => { // Prepare const mockData = { A: JSON.stringify({ foo: 'bar' }) }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act const values = await provider.getMultiple('my-path', { @@ -349,15 +296,11 @@ describe('Class: BaseProvider', () => { }); }); - test('when called, it returns an object with the values', async () => { + it('it returns an object with the transformed values', async () => { // Prepare const mockData = { A: 'foo', B: 'bar' }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act const values = await provider.getMultiple('my-path'); @@ -370,18 +313,14 @@ describe('Class: BaseProvider', () => { }); }); - test('when called with a json transform, and one of the values is NOT a valid string representation of a JSON, it returns an object with partial failures', async () => { + it('returns an object with partial failures when called with a json transform, and one of the values is NOT a valid string representation of a JSON', async () => { // Prepare const mockData = { A: JSON.stringify({ foo: 'bar' }), B: `${JSON.stringify({ foo: 'bar' })}{`, }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act const values = await provider.getMultiple('my-path', { @@ -398,15 +337,11 @@ describe('Class: BaseProvider', () => { }); }); - test('when called with a json transform and throwOnTransformError equal to TRUE, and at least ONE the values is NOT a valid string representation of a JSON, it throws', async () => { + it('throws when called with a json transform and throwOnTransformError equal to TRUE, and at least ONE the values is NOT a valid string representation of a JSON', async () => { // Prepare const mockData = { A: `${JSON.stringify({ foo: 'bar' })}{` }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act & Assess await expect( @@ -417,15 +352,11 @@ describe('Class: BaseProvider', () => { ).rejects.toThrowError(TransformParameterError); }); - test('when called with a binary transform, and all the values are a valid string representation of a binary, it returns an object with all the values', async () => { + it('returns an object with transformed values and binary transform is used on valid string representation of a binaries', async () => { // Prepare const mockData = { A: toBase64(encoder.encode('my-value')).toString() }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act const values = await provider.getMultiple('my-path', { @@ -439,15 +370,11 @@ describe('Class: BaseProvider', () => { }); }); - test('when called with a binary transform, and one of the values is NOT a valid string representation of a binary, it returns an object with partial failures', async () => { + it('returns an object with partial failures when called with a binary transform, and one of the values is NOT a valid string representation of a binary', async () => { // Prepare const mockData = { A: toBase64(encoder.encode('my-value')), B: 'qw' }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act const values = await provider.getMultiple('my-path', { @@ -462,15 +389,11 @@ describe('Class: BaseProvider', () => { }); }); - test('when called with a binary transform and throwOnTransformError equal to TRUE, and at least ONE the values is NOT a valid string representation of a binary, it throws', async () => { + it('throws when called with a binary transform and throwOnTransformError equal to TRUE, and at least ONE the values is NOT a valid string representation of a binary', async () => { // Prepare const mockData = { A: 'qw' }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act & Assess await expect( @@ -481,15 +404,11 @@ describe('Class: BaseProvider', () => { ).rejects.toThrowError(TransformParameterError); }); - test('when called with auto transform and the key of the parameter ends with `.binary`, and all the values are a valid string representation of a binary, it returns an object with all the transformed values', async () => { + it('returns an object with the transformed values when auto transform is used and the key of the parameter ends with `.binary`', async () => { // Prepare const mockData = { 'A.binary': toBase64(encoder.encode('my-value')) }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act const values = await provider.getMultiple('my-path', { @@ -497,22 +416,17 @@ describe('Class: BaseProvider', () => { }); // Assess - expect(typeof values).toBe('object'); - expect(values).toMatchObject({ + expect(values).toStrictEqual({ 'A.binary': 'my-value', }); }); - test('when called with auto transform and the key of the parameter DOES NOT end with `.binary` or `.json`, it returns an object with all the values NOT transformed', async () => { + it('leaves the values untransformed when using auto transform and the name does not end in `.binary` or `.json`', async () => { // Prepare const mockBinary = toBase64(encoder.encode('my-value')); const mockData = { 'A.foo': mockBinary }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act const values = await provider.getMultiple('my-path', { @@ -520,34 +434,28 @@ describe('Class: BaseProvider', () => { }); // Assess - expect(typeof values).toBe('object'); - expect(values).toMatchObject({ + expect(values).toStrictEqual({ 'A.foo': mockBinary, }); }); - test('when called with a binary transform, and at least ONE the values is undefined, it returns an object with one of the values undefined', async () => { + it('handles undefined values by leaving them as-is', async () => { // Prepare const mockData = { A: undefined }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); // Act const values = await provider.getMultiple('my-path', { transform: 'auto', }); - expect(typeof values).toBe('object'); - expect(values).toMatchObject({ + expect(values).toStrictEqual({ A: undefined, }); }); - test('when called and values cached are available, it returns an object with the cached values', async () => { + it('returns an object with cached values when available', async () => { // Prepare const provider = new TestProvider(); provider._add( @@ -559,38 +467,32 @@ describe('Class: BaseProvider', () => { const values = await provider.getMultiple('my-path'); // Assess - expect(typeof values).toBe('object'); - expect(values).toMatchObject({ + expect(values).toStrictEqual({ A: 'my-value', }); }); - test('when called and values cached are expired, it returns an object with the remote values', async () => { + it('returns an object with the remote values when the cached ones are expired', async () => { // Prepare const mockData = { A: 'my-value' }; const provider = new TestProvider(); - jest - .spyOn(provider, '_getMultiple') - .mockImplementation( - () => new Promise((resolve, _reject) => resolve(mockData)) - ); + vi.spyOn(provider, '_getMultiple').mockResolvedValue(mockData); const expirableValue = new ExpirableValue({ B: 'my-other-value' }, 0); - jest.spyOn(expirableValue, 'isExpired').mockImplementation(() => true); + vi.spyOn(expirableValue, 'isExpired').mockImplementation(() => true); provider._add(['my-path', undefined].toString(), expirableValue); // Act const values = await provider.getMultiple('my-path'); // Assess - expect(typeof values).toBe('object'); - expect(values).toMatchObject({ + expect(values).toStrictEqual({ A: 'my-value', }); }); }); describe('Method: clearCache', () => { - test('when called, it clears the store', () => { + it('clears the store', () => { // Prepare const provider = new TestProvider(); provider._add( @@ -608,12 +510,12 @@ describe('Class: BaseProvider', () => { }); describe('Function: clearCaches', () => { - test('when called, it clears all the caches', () => { + it('clears all the caches', () => { // Prepare const provider1 = new TestProvider(); const provider2 = new TestProvider(); - const provider1Spy = jest.spyOn(provider1, 'clearCache'); - const provider2Spy = jest.spyOn(provider2, 'clearCache'); + const provider1Spy = vi.spyOn(provider1, 'clearCache'); + const provider2Spy = vi.spyOn(provider2, 'clearCache'); DEFAULT_PROVIDERS.ssm = provider1; DEFAULT_PROVIDERS.secretsManager = provider2; @@ -627,10 +529,10 @@ describe('Function: clearCaches', () => { }); describe('Class: GetOptions', () => { - it('should set the default maxAge when not provided', () => { + it('sets the default maxAge when not provided', () => { // Prepare const envVarsService = { - getParametersMaxAge: jest.fn(), + getParametersMaxAge: vi.fn(), }; const options = new GetOptions( envVarsService as unknown as EnvironmentVariablesService @@ -642,10 +544,10 @@ describe('Class: GetOptions', () => { }); describe('Class: GetMultipleOptions', () => { - it('should set throwOnTransformError to false when not provided', () => { + it('sets throwOnTransformError to false when not provided', () => { // Prepare const envVarsService = { - getParametersMaxAge: jest.fn(), + getParametersMaxAge: vi.fn(), }; const options = new GetMultipleOptions( envVarsService as unknown as EnvironmentVariablesService diff --git a/packages/parameters/tests/unit/DynamoDBProvider.test.ts b/packages/parameters/tests/unit/DynamoDBProvider.test.ts index 1c9cf6f892..6b20665e1a 100644 --- a/packages/parameters/tests/unit/DynamoDBProvider.test.ts +++ b/packages/parameters/tests/unit/DynamoDBProvider.test.ts @@ -1,8 +1,3 @@ -/** - * Test DynamoDBProvider class - * - * @group unit/parameters/DynamoDBProvider/class - */ import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; import { DynamoDBClient, @@ -15,21 +10,24 @@ import type { } from '@aws-sdk/client-dynamodb'; import { marshall } from '@aws-sdk/util-dynamodb'; import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { DynamoDBProvider } from '../../src/dynamodb/index.js'; import type { DynamoDBProviderOptions } from '../../src/types/DynamoDBProvider.js'; -import 'aws-sdk-client-mock-jest'; -jest.mock('@aws-lambda-powertools/commons', () => ({ - ...jest.requireActual('@aws-lambda-powertools/commons'), - addUserAgentMiddleware: jest.fn(), + +vi.mock('@aws-lambda-powertools/commons', async (importOriginal) => ({ + ...(await importOriginal()), + addUserAgentMiddleware: vi.fn(), })); describe('Class: DynamoDBProvider', () => { + const client = mockClient(DynamoDBClient); + beforeEach(() => { - jest.clearAllMocks(); + client.reset(); }); describe('Method: constructor', () => { - test('when the class instantiates without SDK client and client config it has default options', async () => { + it('instantiates a new AWS SDK with default options', async () => { // Prepare const options: DynamoDBProviderOptions = { tableName: 'test-table', @@ -47,7 +45,7 @@ describe('Class: DynamoDBProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { + it('uses the provided config to instantiate a new AWS SDK', async () => { // Prepare const options: DynamoDBProviderOptions = { tableName: 'test-table', @@ -64,7 +62,7 @@ describe('Class: DynamoDBProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - test('when the user provides an SDK client in the options, the class instantiates with it', async () => { + it('uses the provided AWS SDK client', async () => { // Prepare const awsSdkV3Client = new DynamoDBClient({ endpoint: 'http://localhost:8000', @@ -94,7 +92,7 @@ describe('Class: DynamoDBProvider', () => { tableName: 'test-table', awsSdkV3Client: awsSdkV3Client as DynamoDBClient, }; - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarnSpy = vi.spyOn(console, 'warn'); // Act const provider = new DynamoDBProvider(options); @@ -114,13 +112,13 @@ describe('Class: DynamoDBProvider', () => { }); describe('Method: _get', () => { - test('when called and the sdk client returns no items, it returns undefined', async () => { + it('returns undefined when the underlying sdk client returns no items', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterPath = 'foo'; - mockClient(DynamoDBClient).on(GetItemCommand).resolves({}); + client.on(GetItemCommand).resolves({}); // Act const parameter = await provider.get(parameterPath); @@ -129,21 +127,19 @@ describe('Class: DynamoDBProvider', () => { expect(parameter).toBeUndefined(); }); - test('when called with only a name, it gets the parameter using the default attribute values and table name', async () => { + it('gets the parameter using default key and table names when called with only a name', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterName = 'foo'; const parameterValue = 'bar'; - const client = mockClient(DynamoDBClient) - .on(GetItemCommand) - .resolves({ - Item: marshall({ - id: parameterName, - value: parameterValue, - }), - }); + client.on(GetItemCommand).resolves({ + Item: marshall({ + id: parameterName, + value: parameterValue, + }), + }); // Act const parameter = await provider.get(parameterName); @@ -162,7 +158,7 @@ describe('Class: DynamoDBProvider', () => { expect(parameter).toEqual(parameterValue); }); - test('when called with only a name, it gets the parameter using the attribute values and table name provided to the constructor', async () => { + it('uses the key and table name set in the constructor', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', @@ -171,14 +167,12 @@ describe('Class: DynamoDBProvider', () => { }); const parameterName = 'foo'; const parameterValue = 'bar'; - const client = mockClient(DynamoDBClient) - .on(GetItemCommand) - .resolves({ - Item: marshall({ - key: parameterName, - val: parameterValue, - }), - }); + client.on(GetItemCommand).resolves({ + Item: marshall({ + key: parameterName, + val: parameterValue, + }), + }); // Act const parameter = await provider.get(parameterName); @@ -197,21 +191,19 @@ describe('Class: DynamoDBProvider', () => { expect(parameter).toEqual(parameterValue); }); - test('when called with name and sdkOptions, it gets the parameter using the options provided', async () => { + it('uses the provided sdkOptions', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterName = 'foo'; const parameterValue = 'bar'; - const client = mockClient(DynamoDBClient) - .on(GetItemCommand) - .resolves({ - Item: marshall({ - id: parameterName, - value: parameterValue, - }), - }); + client.on(GetItemCommand).resolves({ + Item: marshall({ + id: parameterName, + value: parameterValue, + }), + }); // Act const parameter = await provider.get(parameterName, { @@ -235,21 +227,19 @@ describe('Class: DynamoDBProvider', () => { expect(parameter).toEqual(parameterValue); }); - test('when called with sdkOptions that override arguments passed to the method, it gets the parameter using the arguments', async () => { + it('uses the sdkOptions overrides passed to the method', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterName = 'foo'; const parameterValue = 'bar'; - const client = mockClient(DynamoDBClient) - .on(GetItemCommand) - .resolves({ - Item: marshall({ - id: parameterName, - value: parameterValue, - }), - }); + client.on(GetItemCommand).resolves({ + Item: marshall({ + id: parameterName, + value: parameterValue, + }), + }); // Act await provider.get(parameterName, { @@ -277,33 +267,31 @@ describe('Class: DynamoDBProvider', () => { }); describe('Method: _getMultiple', () => { - test('when called with only a path, it gets the parameters using the default attribute values and table name', async () => { + it('uses the default key and table name when called with only a path', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterPath = 'foo'; - const client = mockClient(DynamoDBClient) - .on(QueryCommand) - .resolves({ - Items: [ - marshall({ - id: parameterPath, - sk: 'a', - value: 'parameter-a', - }), - marshall({ - id: parameterPath, - sk: 'b', - value: 'parameter-b', - }), - marshall({ - id: parameterPath, - sk: 'c', - value: 'parameter-c', - }), - ], - }); + client.on(QueryCommand).resolves({ + Items: [ + marshall({ + id: parameterPath, + sk: 'a', + value: 'parameter-a', + }), + marshall({ + id: parameterPath, + sk: 'b', + value: 'parameter-b', + }), + marshall({ + id: parameterPath, + sk: 'c', + value: 'parameter-c', + }), + ], + }); // Act const parameters = await provider.getMultiple(parameterPath); @@ -329,7 +317,7 @@ describe('Class: DynamoDBProvider', () => { }); }); - test('when called with only a path, it gets the parameter using the attribute values and table name provided to the constructor', async () => { + it('uses the key and table name set in the constructor when called with only a path', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', @@ -338,27 +326,25 @@ describe('Class: DynamoDBProvider', () => { sortAttr: 'sort', }); const parameterPath = 'foo'; - const client = mockClient(DynamoDBClient) - .on(QueryCommand) - .resolves({ - Items: [ - marshall({ - key: parameterPath, - sort: 'a', - val: 'parameter-a', - }), - marshall({ - key: parameterPath, - sort: 'b', - val: 'parameter-b', - }), - marshall({ - key: parameterPath, - sort: 'c', - val: 'parameter-c', - }), - ], - }); + client.on(QueryCommand).resolves({ + Items: [ + marshall({ + key: parameterPath, + sort: 'a', + val: 'parameter-a', + }), + marshall({ + key: parameterPath, + sort: 'b', + val: 'parameter-b', + }), + marshall({ + key: parameterPath, + sort: 'c', + val: 'parameter-c', + }), + ], + }); // Act const parameters = await provider.getMultiple(parameterPath); @@ -384,33 +370,31 @@ describe('Class: DynamoDBProvider', () => { }); }); - test('when called with a path and sdkOptions, it gets the parameters using the options provided', async () => { + it('uses the provided sdkOptions', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterPath = 'foo'; - const client = mockClient(DynamoDBClient) - .on(QueryCommand) - .resolves({ - Items: [ - marshall({ - id: parameterPath, - sk: 'a', - value: 'parameter-a', - }), - marshall({ - id: parameterPath, - sk: 'b', - value: 'parameter-b', - }), - marshall({ - id: parameterPath, - sk: 'c', - value: 'parameter-c', - }), - ], - }); + client.on(QueryCommand).resolves({ + Items: [ + marshall({ + id: parameterPath, + sk: 'a', + value: 'parameter-a', + }), + marshall({ + id: parameterPath, + sk: 'b', + value: 'parameter-b', + }), + marshall({ + id: parameterPath, + sk: 'c', + value: 'parameter-c', + }), + ], + }); // Act const parameters = await provider.getMultiple(parameterPath, { @@ -442,13 +426,13 @@ describe('Class: DynamoDBProvider', () => { }); }); - test('when multiple pages are found, it returns an object with all the parameters', async () => { + it('scrolls through the pages and aggregates the results when multiple pages are found', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterPath = 'foo'; - mockClient(DynamoDBClient) + client .on(QueryCommand) .resolvesOnce({ Items: [ @@ -494,33 +478,31 @@ describe('Class: DynamoDBProvider', () => { }); }); - test('when called with sdkOptions that override arguments or internals, it discards the ones passed in sdkOptions and leaves others untouched', async () => { + it('uses the provided sdkOptions overrides but discards internal configs that should not be overridden', async () => { // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterPath = 'foo'; - const client = mockClient(DynamoDBClient) - .on(QueryCommand) - .resolves({ - Items: [ - marshall({ - id: parameterPath, - sk: 'a', - value: 'parameter-a', - }), - marshall({ - id: parameterPath, - sk: 'b', - value: 'parameter-b', - }), - marshall({ - id: parameterPath, - sk: 'c', - value: 'parameter-c', - }), - ], - }); + client.on(QueryCommand).resolves({ + Items: [ + marshall({ + id: parameterPath, + sk: 'a', + value: 'parameter-a', + }), + marshall({ + id: parameterPath, + sk: 'b', + value: 'parameter-b', + }), + marshall({ + id: parameterPath, + sk: 'c', + value: 'parameter-c', + }), + ], + }); // Act await provider.getMultiple(parameterPath, { diff --git a/packages/parameters/tests/unit/EnvironmentVariablesService.test.ts b/packages/parameters/tests/unit/EnvironmentVariablesService.test.ts index c8bca2a8e0..56dc20d82e 100644 --- a/packages/parameters/tests/unit/EnvironmentVariablesService.test.ts +++ b/packages/parameters/tests/unit/EnvironmentVariablesService.test.ts @@ -1,15 +1,10 @@ -/** - * Test Parameters EnvironmentVariablesService class - * - * @group unit/parameters/config - */ +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { EnvironmentVariablesService } from '../../src/config/EnvironmentVariablesService.js'; describe('Class: EnvironmentVariablesService', () => { const ENVIRONMENT_VARIABLES = process.env; beforeEach(() => { - jest.resetModules(); process.env = { ...ENVIRONMENT_VARIABLES }; }); @@ -18,7 +13,7 @@ describe('Class: EnvironmentVariablesService', () => { }); describe('Method: getParametersMaxAge', () => { - test('it returns undefined if the POWERTOOLS_PARAMETERS_MAX_AGE is empty', () => { + it('returns undefined if the POWERTOOLS_PARAMETERS_MAX_AGE is empty', () => { // Prepare const service = new EnvironmentVariablesService(); @@ -29,7 +24,7 @@ describe('Class: EnvironmentVariablesService', () => { expect(value).toEqual(undefined); }); - test('it returns a number if the POWERTOOLS_PARAMETERS_MAX_AGE has a numeric value', () => { + it('returns a number if the POWERTOOLS_PARAMETERS_MAX_AGE has a numeric value', () => { // Prepare process.env.POWERTOOLS_PARAMETERS_MAX_AGE = '36'; const service = new EnvironmentVariablesService(); @@ -41,11 +36,11 @@ describe('Class: EnvironmentVariablesService', () => { expect(value).toEqual(36); }); - test('it logs a warning if the POWERTOOLS_PARAMETERS_MAX_AGE has a non-numeric value', () => { + it('logs a warning if the POWERTOOLS_PARAMETERS_MAX_AGE has a non-numeric value', () => { // Prepare process.env.POWERTOOLS_PARAMETERS_MAX_AGE = 'invalid'; const service = new EnvironmentVariablesService(); - const warnLogspy = jest.spyOn(console, 'warn').mockImplementation(); + const warnLogspy = vi.spyOn(console, 'warn'); // Act const value = service.getParametersMaxAge(); @@ -59,7 +54,7 @@ describe('Class: EnvironmentVariablesService', () => { }); describe('Method: getSSMDecrypt', () => { - test('it returns the value of the environment variable POWERTOOLS_PARAMETERS_SSM_DECRYPT', () => { + it('returns the value of the environment variable POWERTOOLS_PARAMETERS_SSM_DECRYPT', () => { // Prepare process.env.POWERTOOLS_PARAMETERS_SSM_DECRYPT = 'true'; const service = new EnvironmentVariablesService(); diff --git a/packages/parameters/tests/unit/SSMProvider.test.ts b/packages/parameters/tests/unit/SSMProvider.test.ts index dfa2599258..386c3bf739 100644 --- a/packages/parameters/tests/unit/SSMProvider.test.ts +++ b/packages/parameters/tests/unit/SSMProvider.test.ts @@ -1,8 +1,4 @@ -/** - * Test SSMProvider class - * - * @group unit/parameters/ssm/class - */ +import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; import { GetParameterCommand, GetParametersByPathCommand, @@ -11,12 +7,11 @@ import { SSMClient, } from '@aws-sdk/client-ssm'; import type { GetParametersCommandOutput } from '@aws-sdk/client-ssm'; -import { mockClient } from 'aws-sdk-client-mock'; -import { SSMProvider } from '../../src/ssm/index.js'; -import 'aws-sdk-client-mock-jest'; -import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; import { toBase64 } from '@smithy/util-base64'; +import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ExpirableValue } from '../../src/base/ExpirableValue.js'; +import { SSMProvider } from '../../src/ssm/index.js'; import type { SSMGetParametersByNameFromCacheOutputType, SSMGetParametersByNameOptions, @@ -27,21 +22,21 @@ import type { } from '../../src/types/SSMProvider.js'; const encoder = new TextEncoder(); -jest.mock('@aws-lambda-powertools/commons', () => ({ - ...jest.requireActual('@aws-lambda-powertools/commons'), - addUserAgentMiddleware: jest.fn(), +vi.mock('@aws-lambda-powertools/commons', async (importOriginal) => ({ + ...(await importOriginal()), + addUserAgentMiddleware: vi.fn(), })); describe('Class: SSMProvider', () => { const ENVIRONMENT_VARIABLES = process.env; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); process.env = { ...ENVIRONMENT_VARIABLES }; }); describe('Method: constructor', () => { - test('when the class instantiates without SDK client and client config it has default options', async () => { + it('adds the middleware to the created AWS SDK', async () => { // Prepare const options: SSMProviderOptions = {}; @@ -57,7 +52,7 @@ describe('Class: SSMProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { + it('creates a new AWS SDK using the provided client config', async () => { // Prepare const options: SSMProviderOptions = { clientConfig: { @@ -73,7 +68,7 @@ describe('Class: SSMProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - test('when the user provides an SDK client in the options, the class instantiates with it', async () => { + it('uses the provided AWS SDK client', async () => { // Prepare const awsSdkV3Client = new SSMClient({ endpoint: 'http://localhost:3000', @@ -101,7 +96,7 @@ describe('Class: SSMProvider', () => { const options: SSMProviderOptions = { awsSdkV3Client: awsSdkV3Client as SSMClient, }; - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarnSpy = vi.spyOn(console, 'warn'); // Act const provider = new SSMProvider(options); @@ -122,11 +117,11 @@ describe('Class: SSMProvider', () => { describe('Method: getParametersByName', () => { class SSMProviderMock extends SSMProvider { - public getParametersBatchByName = jest.fn(); - public getParametersByNameWithDecryptOption = jest.fn(); + public getParametersBatchByName = vi.fn(); + public getParametersByNameWithDecryptOption = vi.fn(); } - test('when called with no parameters to decrypt, it calls both getParametersByNameWithDecryptOption and getParametersBatchByName, then returns', async () => { + it('calls both getParametersByNameWithDecryptOption and getParametersBatchByName when called with no parameters to decrypt', async () => { // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -165,7 +160,7 @@ describe('Class: SSMProvider', () => { expect(provider.getParametersBatchByName).toHaveBeenCalledTimes(1); }); - test('when called with all parameters to decrypt, it calls only getParametersBatchByName', async () => { + it('calls only getParametersBatchByName when called with all parameters to decrypt', async () => { // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -199,7 +194,7 @@ describe('Class: SSMProvider', () => { ).not.toHaveBeenCalled(); }); - test('when called with some parameters to decrypt, it calls both getParametersByNameWithDecryptOption and getParametersBatchByName, then returns', async () => { + it('calls both getParametersByNameWithDecryptOption and getParametersBatchByName when called with some parameters to decrypt', async () => { // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -258,7 +253,7 @@ describe('Class: SSMProvider', () => { expect(provider.getParametersBatchByName).toHaveBeenCalledTimes(1); }); - test('when called and getParametersBatchByName returns an error and throwOnError is false, it returns the errors', async () => { + it('handles the error and returns it when getParametersBatchByName throws and throwOnError is false', async () => { // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -293,7 +288,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when called and getParametersBatchByName returns an error and throwOnError is false, it returns the errors', async () => { + it('handles the error and returns it when getParametersBatchByName throws and throwOnError is false', async () => { // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -323,7 +318,7 @@ describe('Class: SSMProvider', () => { }); describe('Method: _get', () => { - test('when called without any options but with POWERTOOLS_PARAMETERS_SSM_DECRYPT env var enabled, it gets the parameter with decryption', async () => { + it('decrypts the parameter when configured via env variable', async () => { // Prepare process.env.POWERTOOLS_PARAMETERS_SSM_DECRYPT = 'true'; const provider = new SSMProvider(); @@ -348,7 +343,7 @@ describe('Class: SSMProvider', () => { expect(value).toBe(parameterValue); }); - test('when called without sdkOptions, it gets the parameter using the name and with no decryption', async () => { + it('gets the parameter using default config', async () => { // Prepare const provider = new SSMProvider(); const parameterName = 'foo'; @@ -371,7 +366,7 @@ describe('Class: SSMProvider', () => { expect(value).toBe(parameterValue); }); - test('when called with sdkOptions, it gets the parameter using the parameters', async () => { + it('uses the provided sdkOptions when getting a paramter', async () => { // Prepare const provider = new SSMProvider(); const client = mockClient(SSMClient).on(GetParameterCommand).resolves({}); @@ -389,7 +384,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when called with the decrypt option, the WithDecryption parameter is passed to the sdk client', async () => { + it('decrypts the parameter when enabling decrypt', async () => { // Prepare const provider = new SSMProvider(); const client = mockClient(SSMClient).on(GetParameterCommand).resolves({}); @@ -407,7 +402,7 @@ describe('Class: SSMProvider', () => { }); describe('Method: _getMultiple', () => { - test('when called with only a path, it passes it to the sdk', async () => { + it('uses the provided path and passes it to the underlying sdk', async () => { // Prepare const provider = new SSMProvider(); const client = mockClient(SSMClient) @@ -424,7 +419,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when called with a path and sdkOptions, it passes them to the sdk', async () => { + it('passes down the provided sdkOptions to the underlying SDK', async () => { // Prepare const provider = new SSMProvider(); const client = mockClient(SSMClient) @@ -446,7 +441,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when called with no options, it uses the default sdk options', async () => { + it('uses the default options when no options are provided', async () => { // Prepare const provider = new SSMProvider(); const client = mockClient(SSMClient) @@ -465,7 +460,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when called with decrypt or recursive, it passes them to the sdk', async () => { + it('uses the provided config when recursive or decrypt are enabled', async () => { // Prepare const provider = new SSMProvider(); const client = mockClient(SSMClient) @@ -489,7 +484,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when multiple parameters that share the same path as suffix are retrieved, it returns an object with the names only', async () => { + it('returns an object with the names when multiple parameters that share the same path as suffix are retrieved', async () => { // Prepare const provider = new SSMProvider(); mockClient(SSMClient) @@ -518,7 +513,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when multiple pages are found, it returns an object with all the parameters', async () => { + it('scrolls through the pages and aggregates the results when when multiple pages are found', async () => { // Prepare const provider = new SSMProvider(); mockClient(SSMClient) @@ -555,7 +550,7 @@ describe('Class: SSMProvider', () => { describe('Method: _getParametersByName', () => { class SSMProviderMock extends SSMProvider { - public transformAndCacheGetParametersResponse = jest.fn(); + public transformAndCacheGetParametersResponse = vi.fn(); public _getParametersByName( parameters: Record, @@ -566,7 +561,7 @@ describe('Class: SSMProvider', () => { } } - test('when called with a list of parameters, it passes them to the sdk', async () => { + it('passes down the config when called with a list of parameters', async () => { // Prepare const provider = new SSMProviderMock(); const client = mockClient(SSMClient) @@ -642,8 +637,8 @@ describe('Class: SSMProvider', () => { describe('Method: getParametersBatchByName', () => { class SSMProviderMock extends SSMProvider { - public getParametersByNameFromCache = jest.fn(); - public getParametersByNameInChunks = jest.fn(); + public getParametersByNameFromCache = vi.fn(); + public getParametersByNameInChunks = vi.fn(); public getParametersBatchByName( parameters: Record, @@ -658,7 +653,7 @@ describe('Class: SSMProvider', () => { } } - test('when called with a list of parameters, if they are all cached, it returns them immediately', async () => { + it('returns parameters from cache when present', async () => { // Prepare const provider = new SSMProviderMock(); provider.getParametersByNameFromCache.mockResolvedValueOnce({ @@ -691,7 +686,7 @@ describe('Class: SSMProvider', () => { expect(provider.getParametersByNameInChunks).toHaveBeenCalledTimes(0); }); - test('when called with a list of parameters, if none of them are cached, it retrieves them and then returns', async () => { + it('retrieves the parameters from remote when not present in the cache', async () => { // Prepare const provider = new SSMProviderMock(); provider.getParametersByNameFromCache.mockResolvedValueOnce({ @@ -739,7 +734,7 @@ describe('Class: SSMProvider', () => { ); }); - test('when called with a list of parameters, if some of them are cached, it retrieves the missing ones, and then returns them all', async () => { + it('retrieves the parameters not present in the cache and returns them, together with the cached ones', async () => { // Prepare const provider = new SSMProviderMock(); provider.getParametersByNameFromCache.mockResolvedValueOnce({ @@ -801,7 +796,7 @@ describe('Class: SSMProvider', () => { } } - test('when called with a batch of parameters, it returns an object with parameters split by cached and to fetch', async () => { + it('returns an object with parameters split by cached and to fetch', async () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -829,7 +824,7 @@ describe('Class: SSMProvider', () => { describe('Method: getParametersByNameInChunks', () => { class SSMProviderMock extends SSMProvider { - public _getParametersByName = jest.fn(); + public _getParametersByName = vi.fn(); public maxGetParametersItems = 1; public async getParametersByNameInChunks( @@ -845,7 +840,7 @@ describe('Class: SSMProvider', () => { } } - test('when called with a batch of parameters to retrieve, it splits them in chunks and retrieves them', async () => { + it('splits the parameters in chunks and retrieves them', async () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -879,7 +874,7 @@ describe('Class: SSMProvider', () => { expect(provider._getParametersByName).toHaveBeenCalledTimes(2); }); - test('when retrieving parameters, if throwOnError is true, it throws an error if any parameter is not found', async () => { + it('throws if any parameter is not found', async () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -900,7 +895,7 @@ describe('Class: SSMProvider', () => { ).rejects.toThrowError('Parameter not found'); }); - test('when retrieving parameters, if throwOnError is false, it returns an object with the parameters values and the errors', async () => { + it('returns an object with params and errors when throwOnError is false', async () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -935,7 +930,7 @@ describe('Class: SSMProvider', () => { describe('Method: getParametersByNameWithDecryptOption', () => { class SSMProviderMock extends SSMProvider { - public _get = jest.fn(); + public _get = vi.fn(); public async getParametersByNameWithDecryptOption( parameters: Record, @@ -948,7 +943,7 @@ describe('Class: SSMProvider', () => { } } - test('when called with a batch of parameters to retrieve, it returns an object with the parameters values', async () => { + it('returns an object with the parameters values', async () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -969,7 +964,7 @@ describe('Class: SSMProvider', () => { expect(errors).toEqual([]); }); - test('when called with a batch of parameters to retrieve, and throwOnError is set to false, it returns an object with the parameters values and the errors', async () => { + it('returns an object with the params and the errors when throwOnError is set to false', async () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -991,7 +986,7 @@ describe('Class: SSMProvider', () => { expect(errors).toEqual(['/foo/baz']); }); - test('when called with a batch of parameters to retrieve, and throwOnError is set to true, it throws an error if any parameter retrieval throws', async () => { + it('throws an error if any parameter retrieval throws and throwOnError is set to true', async () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1022,7 +1017,7 @@ describe('Class: SSMProvider', () => { } } - test('when called without any errors, it does not throw and returns an empty errors array', () => { + it('does not throw and returns an empty errors array when no error is thrown', () => { // Prepare const provider = new SSMProviderMock(); const result = { @@ -1036,7 +1031,7 @@ describe('Class: SSMProvider', () => { expect(errors).toEqual([]); }); - test('when called with errors, and throwOnError is set to false, it returns the errors array', () => { + it('returns the errors array when called with errors', () => { // Prepare const provider = new SSMProviderMock(); const result = { @@ -1050,7 +1045,7 @@ describe('Class: SSMProvider', () => { expect(errors).toEqual(['/foo/bar', '/foo/baz']); }); - test('when called with errors, and throwOnError is set to true, it throws an error', () => { + it('throws an error when throwOnError is set to true and there are errors', () => { // Prepare const provider = new SSMProviderMock(); const result = { @@ -1074,7 +1069,7 @@ describe('Class: SSMProvider', () => { } } - test('when called with a batch of parameters, and none has decrypt set to TRUE, it returns an object with all the parameters in batch', () => { + it('returns an object with all the parameters in batch and none has decrypt set to TRUE', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1098,7 +1093,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when called with a batch of parameters, it returns an object with parameters split by decrypt and not to decrypt', () => { + it('returns an object with parameters split by decrypt and not to decrypt', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1129,7 +1124,7 @@ describe('Class: SSMProvider', () => { }); }); - test('when called with a batch of parameters, it respects any local overrides by giving them precedence over global config', () => { + it('respects any local overrides by giving them precedence over global config', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1181,7 +1176,7 @@ describe('Class: SSMProvider', () => { } } - test('when called and no parameter is named _errors, it does not throw', () => { + it('does not throw when called and no parameter is named _errors', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1195,7 +1190,7 @@ describe('Class: SSMProvider', () => { ).not.toThrow(); }); - test('when called and a parameter is named _errors, and throwOnError is set to false (graceful error mode), it does not throw', () => { + it('does not throw when called and a parameter is named _errors, and throwOnError is set to false (graceful error mode)', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1212,7 +1207,7 @@ describe('Class: SSMProvider', () => { ); }); - test('when called and a parameter is named _errors, and throwOnError is set to true (fail fast mode), it throws an error', () => { + it('throws when called and a parameter is named _errors, and throwOnError is set to true (fail fast mode)', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1243,7 +1238,7 @@ describe('Class: SSMProvider', () => { } } - test('when called with a response that has no Parameters list, it returns an empty object', () => { + it('returns an empty object when called with a response that has no Parameters list', () => { // Prepare const provider = new SSMProviderMock(); const response = {}; @@ -1259,7 +1254,7 @@ describe('Class: SSMProvider', () => { expect(parameters).toEqual({}); }); - test('when called with an empty response, it returns an empty object', () => { + it('returns an empty object when called with an empty response', () => { // Prepare const provider = new SSMProviderMock(); const response = { @@ -1277,7 +1272,7 @@ describe('Class: SSMProvider', () => { expect(parameters).toEqual({}); }); - test('when called with a response, it returns an object with the parameters', () => { + it('returns an object with the parameters when called with a response', () => { // Prepare const provider = new SSMProviderMock(); const response = { @@ -1314,7 +1309,7 @@ describe('Class: SSMProvider', () => { }); describe('Method: set', () => { - test('sets a parameter successfully', async () => { + it('sets a parameter successfully', async () => { const provider: SSMProvider = new SSMProvider(); const client = mockClient(SSMClient) .on(PutParameterCommand) @@ -1331,7 +1326,7 @@ describe('Class: SSMProvider', () => { }); }); - test('sets a parameter with sdk options successfully', async () => { + it('sets a parameter with sdk options successfully', async () => { const provider: SSMProvider = new SSMProvider(); const client = mockClient(SSMClient) .on(PutParameterCommand) @@ -1352,7 +1347,7 @@ describe('Class: SSMProvider', () => { }); }); - test('throws an error if setting a parameter fails', async () => { + it('throws an error if setting a parameter fails', async () => { const provider: SSMProvider = new SSMProvider(); mockClient(SSMClient) .on(PutParameterCommand) @@ -1365,7 +1360,7 @@ describe('Class: SSMProvider', () => { ); }); - test.each([ + it.each([ ['overwrite', true, 'Overwrite'], ['description', 'my-description', 'Description'], ['parameterType', 'SecureString', 'Type'], diff --git a/packages/parameters/tests/unit/SecretsProvider.test.ts b/packages/parameters/tests/unit/SecretsProvider.test.ts index 3bc6a90b2e..e9eab5fe30 100644 --- a/packages/parameters/tests/unit/SecretsProvider.test.ts +++ b/packages/parameters/tests/unit/SecretsProvider.test.ts @@ -1,34 +1,29 @@ -/** - * Test SecretsProvider class - * - * @group unit/parameters/SecretsProvider/class - */ +import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; import { GetSecretValueCommand, SecretsManagerClient, } from '@aws-sdk/client-secrets-manager'; import type { GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { SecretsProvider } from '../../src/secrets/index.js'; import type { SecretsProviderOptions } from '../../src/types/SecretsProvider.js'; -import 'aws-sdk-client-mock-jest'; -import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons'; const encoder = new TextEncoder(); -jest.mock('@aws-lambda-powertools/commons', () => ({ - ...jest.requireActual('@aws-lambda-powertools/commons'), - addUserAgentMiddleware: jest.fn(), +vi.mock('@aws-lambda-powertools/commons', async (importOriginal) => ({ + ...(await importOriginal()), + addUserAgentMiddleware: vi.fn(), })); describe('Class: SecretsProvider', () => { const client = mockClient(SecretsManagerClient); beforeEach(() => { - jest.clearAllMocks(); + client.reset(); }); describe('Method: constructor', () => { - test('when the class instantiates without SDK client and client config it has default options', async () => { + it('instantiates a new AWS SDK with default options', async () => { // Prepare const options: SecretsProviderOptions = {}; @@ -44,7 +39,7 @@ describe('Class: SecretsProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { + it('uses the provided config to instantiate a new AWS SDK', async () => { // Prepare const options: SecretsProviderOptions = { clientConfig: { @@ -60,7 +55,7 @@ describe('Class: SecretsProvider', () => { expect(addUserAgentMiddleware).toHaveBeenCalled(); }); - test('when the user provides an SDK client in the options, the class instantiates with it', async () => { + it('uses the provided AWS SDK client', async () => { // Prepare const awsSdkV3Client = new SecretsManagerClient({ endpoint: 'http://localhost:3000', @@ -88,7 +83,7 @@ describe('Class: SecretsProvider', () => { const options: SecretsProviderOptions = { awsSdkV3Client: awsSdkV3Client as SecretsManagerClient, }; - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarnSpy = vi.spyOn(console, 'warn'); // Act const provider = new SecretsProvider(options); @@ -108,7 +103,7 @@ describe('Class: SecretsProvider', () => { }); describe('Method: _get', () => { - test('when called with only a name, it gets the secret string', async () => { + it('gets the secret string when called with only a name', async () => { // Prepare const provider = new SecretsProvider(); const secretName = 'foo'; @@ -123,7 +118,7 @@ describe('Class: SecretsProvider', () => { expect(result).toBe('bar'); }); - test('when called with only a name, it gets the secret binary', async () => { + it('gets the secret binary when called with only a name', async () => { // Prepare const provider = new SecretsProvider(); const secretName = 'foo'; @@ -139,7 +134,7 @@ describe('Class: SecretsProvider', () => { expect(result).toBe(mockData); }); - test('when called with a name and sdkOptions, it gets the secret using the options provided', async () => { + it('gets the secret using the options provided', async () => { // Prepare const provider = new SecretsProvider(); const secretName = 'foo'; @@ -161,7 +156,7 @@ describe('Class: SecretsProvider', () => { }); }); - test('when called with sdkOptions that override arguments passed to the method, it gets the secret using the arguments', async () => { + it('gets the secret using the sdkOptions overrides provided', async () => { // Prepare const provider = new SecretsProvider(); const secretName = 'foo'; @@ -184,7 +179,7 @@ describe('Class: SecretsProvider', () => { }); describe('Method: _getMultiple', () => { - test('when called, it throws an error', async () => { + it('throws when when called', async () => { // Prepare const provider = new SecretsProvider(); diff --git a/packages/parameters/tests/unit/getAppConfig.test.ts b/packages/parameters/tests/unit/getAppConfig.test.ts index 8204252ae0..cbdf29d2e8 100644 --- a/packages/parameters/tests/unit/getAppConfig.test.ts +++ b/packages/parameters/tests/unit/getAppConfig.test.ts @@ -1,31 +1,22 @@ -/** - * Test getAppConfig function - * - * @group unit/parameters/AppConfigProvider/getAppConfig/function - */ import { AppConfigDataClient, GetLatestConfigurationCommand, StartConfigurationSessionCommand, } from '@aws-sdk/client-appconfigdata'; +import { Uint8ArrayBlobAdapter } from '@smithy/util-stream'; import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { AppConfigProvider, getAppConfig } from '../../src/appconfig/index.js'; import { DEFAULT_PROVIDERS } from '../../src/base/DefaultProviders.js'; -import { Transform } from '../../src/index.js'; -import 'aws-sdk-client-mock-jest'; -import type { JSONValue } from '@aws-lambda-powertools/commons/types'; -import { toBase64 } from '@smithy/util-base64'; -import { Uint8ArrayBlobAdapter } from '@smithy/util-stream'; describe('Function: getAppConfig', () => { const client = mockClient(AppConfigDataClient); - const encoder = new TextEncoder(); beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { + it('instantiates a new client and returns the value when no default provider exists', async () => { // Prepare const mockData = Uint8ArrayBlobAdapter.fromString('myAppConfiguration'); client @@ -40,7 +31,7 @@ describe('Function: getAppConfig', () => { }); // Act - const result: Uint8Array | undefined = await getAppConfig('my-config', { + const result = await getAppConfig('my-config', { application: 'my-app', environment: 'prod', }); @@ -49,7 +40,7 @@ describe('Function: getAppConfig', () => { expect(result).toBe(mockData); }); - test('when called and a default provider exists, it uses it and returns the value', async () => { + it('uses the cached provider when one is present in the cache', async () => { // Prepare const provider = new AppConfigProvider({ application: 'my-app', @@ -69,7 +60,7 @@ describe('Function: getAppConfig', () => { }); // Act - const result: Uint8Array | undefined = await getAppConfig('my-config', { + const result = await getAppConfig('my-config', { application: 'my-app', environment: 'prod', }); @@ -77,60 +68,4 @@ describe('Function: getAppConfig', () => { // Assess expect(result).toBe(mockData); }); - - test('when called with transform: `binary` option, it returns string value', async () => { - // Prepare - const expectedValue = 'my-value'; - const mockData = Uint8ArrayBlobAdapter.fromString( - toBase64(encoder.encode(expectedValue)) - ); - client - .on(StartConfigurationSessionCommand) - .resolves({ - InitialConfigurationToken: 'abcdefg', - }) - .on(GetLatestConfigurationCommand) - .resolves({ - Configuration: mockData, - NextPollConfigurationToken: 'hijklmn', - }); - - // Act - const result: string | undefined = await getAppConfig('my-config', { - application: 'my-app', - environment: 'prod', - transform: Transform.BINARY, - }); - - // Assess - expect(result).toBe(expectedValue); - }); - - test('when called with transform: `json` option, it returns a JSON value', async () => { - // Prepare - const expectedValue = { foo: 'my-value' }; - const mockData = Uint8ArrayBlobAdapter.fromString( - JSON.stringify(expectedValue) - ); - client - .on(StartConfigurationSessionCommand) - .resolves({ - InitialConfigurationToken: 'abcdefg', - }) - .on(GetLatestConfigurationCommand) - .resolves({ - Configuration: mockData, - NextPollConfigurationToken: 'hijklmn', - }); - - // Act - const result: JSONValue = await getAppConfig('my-config', { - application: 'my-app', - environment: 'prod', - transform: Transform.JSON, - }); - - // Assess - expect(result).toStrictEqual(expectedValue); - }); }); diff --git a/packages/parameters/tests/unit/getParameter.test.ts b/packages/parameters/tests/unit/getParameter.test.ts index 9eed76dcec..e1bd035103 100644 --- a/packages/parameters/tests/unit/getParameter.test.ts +++ b/packages/parameters/tests/unit/getParameter.test.ts @@ -1,37 +1,28 @@ -/** - * Test getParameter function - * - * @group unit/parameters/ssm/getParameter/function - */ import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { DEFAULT_PROVIDERS } from '../../src/base/index.js'; import { SSMProvider, getParameter } from '../../src/ssm/index.js'; -import 'aws-sdk-client-mock-jest'; -/** - * Note that the following tests include type annotations on the results of each call. This is to ensure that the - * generic types defined in the utility are working as expected. If they are not, the tests will fail to compile. - */ describe('Function: getParameter', () => { + const client = mockClient(SSMClient); + beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { + it('instantiates a new client and returns the value when no default provider exists', async () => { // Prepare const parameterName = 'foo'; const parameterValue = 'foo'; - const client = mockClient(SSMClient) - .on(GetParameterCommand) - .resolves({ - Parameter: { - Value: parameterValue, - }, - }); + client.on(GetParameterCommand).resolves({ + Parameter: { + Value: parameterValue, + }, + }); // Act - const value: string | undefined = await getParameter(parameterName); + const value = await getParameter(parameterName); // Assess expect(client).toReceiveCommandWith(GetParameterCommand, { @@ -40,22 +31,20 @@ describe('Function: getParameter', () => { expect(value).toBe(parameterValue); }); - test('when called and a default provider exists, it uses it and returns the value', async () => { + it('uses the cached provider when one is present in the cache', async () => { // Prepare const provider = new SSMProvider(); DEFAULT_PROVIDERS.ssm = provider; const parameterName = 'foo'; const parameterValue = 'foo'; - const client = mockClient(SSMClient) - .on(GetParameterCommand) - .resolves({ - Parameter: { - Value: parameterValue, - }, - }); + client.on(GetParameterCommand).resolves({ + Parameter: { + Value: parameterValue, + }, + }); // Act - const value: string | undefined = await getParameter(parameterName); + const value = await getParameter(parameterName); // Assess expect(client).toReceiveCommandWith(GetParameterCommand, { @@ -64,58 +53,4 @@ describe('Function: getParameter', () => { expect(value).toBe(parameterValue); expect(DEFAULT_PROVIDERS.ssm).toBe(provider); }); - - test('when called and transform `JSON` is specified, it returns an object with correct type', async () => { - // Prepare - const provider = new SSMProvider(); - DEFAULT_PROVIDERS.ssm = provider; - const parameterName = 'foo'; - const parameterValue = JSON.stringify({ hello: 'world' }); - const client = mockClient(SSMClient) - .on(GetParameterCommand) - .resolves({ - Parameter: { - Value: parameterValue, - }, - }); - - // Act - const value: Record | undefined = await getParameter( - parameterName, - { transform: 'json' } - ); - - // Assess - expect(client).toReceiveCommandWith(GetParameterCommand, { - Name: parameterName, - }); - expect(value).toStrictEqual(JSON.parse(parameterValue)); - }); - - test('when called and transform `JSON` is specified as well as an explicit `K` type, it returns a result with correct type', async () => { - // Prepare - const provider = new SSMProvider(); - DEFAULT_PROVIDERS.ssm = provider; - const parameterName = 'foo'; - const parameterValue = JSON.stringify(5); - const client = mockClient(SSMClient) - .on(GetParameterCommand) - .resolves({ - Parameter: { - Value: parameterValue, - }, - }); - - // Act - const value: number | undefined = await getParameter( - parameterName, - { transform: 'json' } - ); - - // Assess - expect(client).toReceiveCommandWith(GetParameterCommand, { - Name: parameterName, - }); - expect(value).toBe(JSON.parse(parameterValue)); - }); }); diff --git a/packages/parameters/tests/unit/getParameters.test.ts b/packages/parameters/tests/unit/getParameters.test.ts index ec0e98f17c..8058fb7972 100644 --- a/packages/parameters/tests/unit/getParameters.test.ts +++ b/packages/parameters/tests/unit/getParameters.test.ts @@ -1,28 +1,19 @@ -/** - * Test getParameters function - * - * @group unit/parameters/ssm/getParameters/function - */ import { GetParametersByPathCommand, SSMClient } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { DEFAULT_PROVIDERS } from '../../src/base/index.js'; import { SSMProvider, getParameters } from '../../src/ssm/index.js'; -import 'aws-sdk-client-mock-jest'; -/** - * Note that the following tests include type annotations on the results of each call. This is to ensure that the - * generic types defined in the utility are working as expected. If they are not, the tests will fail to compile. - */ describe('Function: getParameters', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { + it('instantiates a new client and returns the value when no default provider exists', async () => { // Prepare const parameterPath = '/foo'; const parameterValue = 'bar'; - const client = mockClient(SSMClient) + mockClient(SSMClient) .on(GetParametersByPathCommand) .resolves({ Parameters: [ @@ -38,21 +29,18 @@ describe('Function: getParameters', () => { await getParameters(parameterPath); // Assess - expect(client).toReceiveCommandWith(GetParametersByPathCommand, { - Path: parameterPath, - }); expect(parameters).toEqual({ bar: parameterValue, }); }); - test('when called and a default provider exists, it uses it and returns the value', async () => { + it('uses the cached provider when one is present in the cache', async () => { // Prepare const provider = new SSMProvider(); DEFAULT_PROVIDERS.ssm = provider; const parameterPath = '/foo'; const parameterValue = 'bar'; - const client = mockClient(SSMClient) + mockClient(SSMClient) .on(GetParametersByPathCommand) .resolves({ Parameters: [ @@ -68,74 +56,9 @@ describe('Function: getParameters', () => { await getParameters(parameterPath); // Assess - expect(client).toReceiveCommandWith(GetParametersByPathCommand, { - Path: parameterPath, - }); expect(parameters).toEqual({ bar: parameterValue, }); expect(DEFAULT_PROVIDERS.ssm).toBe(provider); }); - - test('when called and transform `JSON` is specified, it returns an object with correct type', async () => { - // Prepare - const provider = new SSMProvider(); - DEFAULT_PROVIDERS.ssm = provider; - const parameterPath = '/foo'; - const parameterValue = JSON.stringify({ hello: 'world' }); - const client = mockClient(SSMClient) - .on(GetParametersByPathCommand) - .resolves({ - Parameters: [ - { - Name: '/foo/bar', - Value: parameterValue, - }, - ], - }); - - // Act - const parameters: Record> | undefined = - await getParameters(parameterPath); - - // Assess - expect(client).toReceiveCommandWith(GetParametersByPathCommand, { - Path: parameterPath, - }); - expect(parameters).toStrictEqual({ - bar: parameterValue, - }); - expect(DEFAULT_PROVIDERS.ssm).toBe(provider); - }); - - test('when called and transform `JSON` is specified as well as an explicit `K` type, it returns a result with correct type', async () => { - // Prepare - const provider = new SSMProvider(); - DEFAULT_PROVIDERS.ssm = provider; - const parameterPath = '/foo'; - const parameterValue = JSON.stringify(5); - const client = mockClient(SSMClient) - .on(GetParametersByPathCommand) - .resolves({ - Parameters: [ - { - Name: '/foo/bar', - Value: parameterValue, - }, - ], - }); - - // Act - const parameters: Record> | undefined = - await getParameters>(parameterPath); - - // Assess - expect(client).toReceiveCommandWith(GetParametersByPathCommand, { - Path: parameterPath, - }); - expect(parameters).toStrictEqual({ - bar: parameterValue, - }); - expect(DEFAULT_PROVIDERS.ssm).toBe(provider); - }); }); diff --git a/packages/parameters/tests/unit/getParametersByName.test.ts b/packages/parameters/tests/unit/getParametersByName.test.ts index e423dc3502..a31d41959e 100644 --- a/packages/parameters/tests/unit/getParametersByName.test.ts +++ b/packages/parameters/tests/unit/getParametersByName.test.ts @@ -1,18 +1,19 @@ -/** - * Test getParametersByName function - * - * @group unit/parameters/ssm/getParametersByName/function - */ +import { GetParametersCommand, SSMClient } from '@aws-sdk/client-ssm'; +import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { DEFAULT_PROVIDERS } from '../../src/base/index.js'; import { SSMProvider, getParametersByName } from '../../src/ssm/index.js'; import type { SSMGetParametersByNameOptions } from '../../src/types/SSMProvider.js'; describe('Function: getParametersByName', () => { + const client = mockClient(SSMClient); + beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); + client.reset(); }); - test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { + it('instantiates a new client and returns the value when no default provider exists', async () => { // Prepare const parameters: Record = { '/foo/bar': { @@ -23,9 +24,24 @@ describe('Function: getParametersByName', () => { transform: 'json', }, }; - const getParametersByNameSpy = jest - .spyOn(SSMProvider.prototype, 'getParametersByName') - .mockImplementation(); + mockClient(SSMClient) + .on(GetParametersCommand) + .resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: 'bar', + }, + { + Name: '/foo/baz', + Value: '{"baz": "qux"}', + }, + ], + }); + const getParametersByNameSpy = vi.spyOn( + SSMProvider.prototype, + 'getParametersByName' + ); // Act await getParametersByName(parameters); @@ -34,7 +50,7 @@ describe('Function: getParametersByName', () => { expect(getParametersByNameSpy).toHaveBeenCalledWith(parameters, undefined); }); - test('when called and a default provider exists, it uses it and returns the value', async () => { + it('uses the cached provider when one is present in the cache', async () => { // Prepare const provider = new SSMProvider(); DEFAULT_PROVIDERS.ssm = provider; @@ -46,9 +62,21 @@ describe('Function: getParametersByName', () => { maxAge: 2000, }, }; - const getParametersByNameSpy = jest - .spyOn(provider, 'getParametersByName') - .mockImplementation(); + mockClient(SSMClient) + .on(GetParametersCommand) + .resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: 'bar', + }, + { + Name: '/foo/baz', + Value: 'baz', + }, + ], + }); + const getParametersByNameSpy = vi.spyOn(provider, 'getParametersByName'); // Act await getParametersByName(parameters); diff --git a/packages/parameters/tests/unit/getSecret.test.ts b/packages/parameters/tests/unit/getSecret.test.ts index c480d9ffbd..22d2f7743d 100644 --- a/packages/parameters/tests/unit/getSecret.test.ts +++ b/packages/parameters/tests/unit/getSecret.test.ts @@ -1,16 +1,11 @@ -/** - * Test getSecret function - * - * @group unit/parameters/SecretsProvider/getSecret/function - */ import { GetSecretValueCommand, SecretsManagerClient, } from '@aws-sdk/client-secrets-manager'; import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it } from 'vitest'; import { DEFAULT_PROVIDERS } from '../../src/base/index.js'; import { SecretsProvider, getSecret } from '../../src/secrets/index.js'; -import 'aws-sdk-client-mock-jest'; const encoder = new TextEncoder(); @@ -18,10 +13,10 @@ describe('Function: getSecret', () => { const client = mockClient(SecretsManagerClient); beforeEach(() => { - jest.clearAllMocks(); + client.reset(); }); - test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { + it('instantiates a new client and returns the value when no default provider exists', async () => { // Prepare const secretName = 'foo'; const secretValue = 'bar'; @@ -39,7 +34,7 @@ describe('Function: getSecret', () => { expect(result).toBe(secretValue); }); - test('when called and a default provider exists, it uses it and returns the value', async () => { + it('uses the cached provider when one is present in the cache', async () => { // Prepare const provider = new SecretsProvider(); DEFAULT_PROVIDERS.secrets = provider; @@ -60,53 +55,4 @@ describe('Function: getSecret', () => { expect(result).toStrictEqual(binary); expect(DEFAULT_PROVIDERS.secrets).toBe(provider); }); - - test('when called and transform `JSON` is specified, it returns an object with correct type', async () => { - // Prepare - const provider = new SecretsProvider(); - DEFAULT_PROVIDERS.secrets = provider; - const secretName = 'foo'; - const secretValue = JSON.stringify({ hello: 'world' }); - const client = mockClient(SecretsManagerClient) - .on(GetSecretValueCommand) - .resolves({ - SecretString: secretValue, - }); - - // Act - const value: Record | undefined = await getSecret( - secretName, - { transform: 'json' } - ); - - // Assess - expect(client).toReceiveCommandWith(GetSecretValueCommand, { - SecretId: secretName, - }); - expect(value).toStrictEqual(JSON.parse(secretValue)); - }); - - test('when called and transform `JSON` is specified as well as an explicit `K` type, it returns a result with correct type', async () => { - // Prepare - const provider = new SecretsProvider(); - DEFAULT_PROVIDERS.secrets = provider; - const secretName = 'foo'; - const secretValue = JSON.stringify(5); - const client = mockClient(SecretsManagerClient) - .on(GetSecretValueCommand) - .resolves({ - SecretString: secretValue, - }); - - // Act - const value: number | undefined = await getSecret(secretName, { - transform: 'json', - }); - - // Assess - expect(client).toReceiveCommandWith(GetSecretValueCommand, { - SecretId: secretName, - }); - expect(value).toBe(JSON.parse(secretValue)); - }); }); diff --git a/packages/parameters/tests/unit/setParameter.test.ts b/packages/parameters/tests/unit/setParameter.test.ts index a7c6ad68af..ac5b97ed7a 100644 --- a/packages/parameters/tests/unit/setParameter.test.ts +++ b/packages/parameters/tests/unit/setParameter.test.ts @@ -1,14 +1,9 @@ -/** - * Test setParameter function - * - * @group unit/parameters/ssm/setParameter/function - */ import { PutParameterCommand, SSMClient } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; +import { beforeEach, describe, expect, it } from 'vitest'; import { DEFAULT_PROVIDERS } from '../../src/base/index.js'; -import { setParameter } from '../../src/ssm/index.js'; import { SSMProvider } from '../../src/ssm/SSMProvider.js'; -import 'aws-sdk-client-mock-jest'; +import { setParameter } from '../../src/ssm/index.js'; import type { SSMSetOptions } from '../../src/types/SSMProvider.js'; describe('Function: setParameter', () => { @@ -16,14 +11,10 @@ describe('Function: setParameter', () => { const client = mockClient(SSMClient); beforeEach(() => { - jest.clearAllMocks(); - }); - - afterEach(() => { client.reset(); }); - test('when called and a default provider does not exist, it instantiates one and sets the parameter', async () => { + it('instantiates a new client when called and a default provider does not exist', async () => { // Prepare const options: SSMSetOptions = { value: 'my-value' }; client.on(PutParameterCommand).resolves({ Version: 1 }); @@ -39,7 +30,7 @@ describe('Function: setParameter', () => { expect(version).toBe(1); }); - test('when called and a default provider exists, it uses it and sets the parameter', async () => { + it('uses the existing provider when called and a default one exists in the cache', async () => { // Prepare const provider = new SSMProvider(); DEFAULT_PROVIDERS.ssm = provider; @@ -58,7 +49,7 @@ describe('Function: setParameter', () => { expect(DEFAULT_PROVIDERS.ssm).toBe(provider); }); - test('when called and the sdk client throws an error a custom error should be thrown from the function', async () => { + it('rethrows the error thrown by the underlying sdk client', async () => { // Prepare const options: SSMSetOptions = { value: 'my-value' }; client.on(PutParameterCommand).rejects(new Error('Could not send command')); @@ -71,7 +62,7 @@ describe('Function: setParameter', () => { ); }); - test('when called with additional sdk options, it sets the parameter with the sdk options successfully', async () => { + it('uses the provided sdkOptions when provided', async () => { // Prepare const options: SSMSetOptions = { value: 'my-value', @@ -91,14 +82,14 @@ describe('Function: setParameter', () => { expect(version).toBe(1); }); - test.each([ + it.each([ ['overwrite', true, 'Overwrite'], ['description', 'my-description', 'Description'], ['parameterType', 'SecureString', 'Type'], ['tier', 'Advanced', 'Tier'], ['kmsKeyId', 'my-key-id', 'KeyId'], ])( - 'when called with %s option, it sets the parameter with the option successfully', + 'sets the parameter with the option when called with %s option', async (option, value, sdkOption) => { //Prepare const options: SSMSetOptions = { value: 'my-value', [option]: value }; diff --git a/packages/parameters/vitest.config.ts b/packages/parameters/vitest.config.ts new file mode 100644 index 0000000000..9f1196ef1f --- /dev/null +++ b/packages/parameters/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + environment: 'node', + setupFiles: ['../testing/src/setupEnv.ts'], + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts index 5f320c2df3..06d877a213 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ 'packages/jmespath/src/types.ts', 'packages/logger/src/types/**', 'packages/metrics/**', - 'packages/parameters/**', + 'packages/parameters/src/types/**', 'packages/parser/src/types/**', 'layers/**', 'packages/testing/**',