diff --git a/packages/parameters/.eslintrc.js b/packages/parameters/.eslintrc.js new file mode 100644 index 0000000000..2e4ca18fb2 --- /dev/null +++ b/packages/parameters/.eslintrc.js @@ -0,0 +1,72 @@ +module.exports = { + env: { + browser: false, + es2020: true, + jest: true, + node: true, + }, + ignorePatterns: ['coverage', 'lib'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'prettier'], + settings: { + 'import/resolver': { + node: {}, + typescript: { + project: './tsconfig.json', + alwaysTryTypes: true, + }, + }, + }, + rules: { + '@typescript-eslint/explicit-function-return-type': [ + 'error', + { allowExpressions: true }, + ], // Enforce return type definitions for functions + '@typescript-eslint/explicit-member-accessibility': 'error', // Enforce explicit accessibility modifiers on class properties and methods (public, private, protected) + '@typescript-eslint/member-ordering': [ + // Standardize the order of class members + 'error', + { + default: { + memberTypes: [ + 'signature', + 'public-field', + 'protected-field', + 'private-field', + 'constructor', + 'public-method', + 'protected-method', + 'private-method', + ], + order: 'alphabetically', + }, + }, + ], + '@typescript-eslint/no-explicit-any': 'error', // Disallow usage of the any type + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // Disallow unused variables, except for variables starting with an underscore + '@typescript-eslint/no-use-before-define': ['off'], // Check if this rule is needed + 'no-unused-vars': 'off', // Disable eslint core rule, since it's replaced by @typescript-eslint/no-unused-vars + // Rules from eslint core https://eslint.org/docs/latest/rules/ + 'array-bracket-spacing': ['error', 'never'], // Disallow spaces inside of array brackets + 'computed-property-spacing': ['error', 'never'], // Disallow spaces inside of computed properties + 'func-style': ['warn', 'expression'], // Enforce function expressions instead of function declarations + 'keyword-spacing': 'error', // Enforce spaces after keywords and before parenthesis, e.g. if (condition) instead of if(condition) + 'padding-line-between-statements': [ + // Require an empty line before return statements + 'error', + { blankLine: 'always', prev: '*', next: 'return' }, + ], + 'no-console': 0, // Allow console.log statements + 'no-multi-spaces': ['error', { ignoreEOLComments: false }], // Disallow multiple spaces except for comments + 'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }], // Enforce no empty line at the beginning & end of files and max 1 empty line between consecutive statements + 'no-throw-literal': 'error', // Disallow throwing literals as exceptions, e.g. throw 'error' instead of throw new Error('error') + 'object-curly-spacing': ['error', 'always'], // Enforce spaces inside of curly braces in objects + 'prefer-arrow-callback': 'error', // Enforce arrow functions instead of anonymous functions for callbacks + quotes: ['error', 'single', { allowTemplateLiterals: true }], // Enforce single quotes except for template strings + semi: ['error', 'always'], // Require semicolons instead of ASI (automatic semicolon insertion) at the end of statements + }, +}; diff --git a/packages/parameters/jest.config.js b/packages/parameters/jest.config.js index d7114e8417..d157d50b4d 100644 --- a/packages/parameters/jest.config.js +++ b/packages/parameters/jest.config.js @@ -3,44 +3,30 @@ module.exports = { name: 'AWS Lambda Powertools utility: PARAMETERS', color: 'magenta', }, - 'runner': 'groups', - 'preset': 'ts-jest', - 'transform': { + runner: 'groups', + preset: 'ts-jest', + 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': [ + 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, + coverageThreshold: { + global: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, }, }, - 'coverageReporters': [ - 'json-summary', - 'text', - 'lcov' - ], - 'setupFiles': [ - '/tests/helpers/populateEnvironmentVariables.ts' - ] -}; \ No newline at end of file + coverageReporters: ['json-summary', 'text', 'lcov'], + setupFiles: ['/tests/helpers/populateEnvironmentVariables.ts'], +}; diff --git a/packages/parameters/package.json b/packages/parameters/package.json index 38750b75db..521168d991 100644 --- a/packages/parameters/package.json +++ b/packages/parameters/package.json @@ -20,8 +20,8 @@ "test:e2e": "jest --group=e2e", "watch": "jest --watch", "build": "tsc", - "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", - "lint-fix": "eslint --fix --ext .ts --no-error-on-unmatched-pattern src tests", + "lint": "eslint --ext .ts,.js --no-error-on-unmatched-pattern .", + "lint-fix": "eslint --fix --ext .ts,.js --no-error-on-unmatched-pattern .", "package": "mkdir -p dist/ && npm pack && mv *.tgz dist/", "package-bundle": "../../package-bundler.sh parameters-bundle ./dist", "prepare": "npm run build", @@ -29,7 +29,8 @@ "postpack": "rm release_patch_package_json.js" }, "lint-staged": { - "*.ts": "npm run lint-fix" + "*.ts": "npm run lint-fix", + "*.js": "npm run lint-fix" }, "homepage": "https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/packages/parameters#readme", "license": "MIT-0", @@ -68,4 +69,4 @@ "@aws-lambda-powertools/commons": "^1.8.0", "@aws-sdk/util-base64-node": "^3.209.0" } -} +} \ No newline at end of file diff --git a/packages/parameters/src/BaseProvider.ts b/packages/parameters/src/BaseProvider.ts index 46f7e5acc1..3d58720724 100644 --- a/packages/parameters/src/BaseProvider.ts +++ b/packages/parameters/src/BaseProvider.ts @@ -9,7 +9,7 @@ import type { BaseProviderInterface, GetMultipleOptionsInterface, GetOptionsInterface, - TransformOptions + TransformOptions, } from './types'; // These providers are dinamycally intialized on first use of the helper functions @@ -17,20 +17,20 @@ const DEFAULT_PROVIDERS: Record = {}; /** * Base class for all providers. - * + * * As an abstract class, it should not be used directly, but rather extended by other providers. - * + * * It implements the common logic for all providers, such as caching, transformation, etc. * Each provider that extends this class must implement the `_get` and `_getMultiple` abstract methods. * * These methods are responsible for retrieving the values from the underlying parameter store. They are * called by the `get` and `getMultiple` methods, which are responsible for caching and transformation. - * + * * If there are multiple calls to the same parameter but in a different transform, they will be stored multiple times. * This allows us to optimize by transforming the data only once per retrieval, thus there is no need to transform cached values multiple times. - * + * * However, this means that we need to make multiple calls to the underlying parameter store if we need to return it in different transforms. - * + * * Since the number of supported transform is small and the probability that a given parameter will always be used in a specific transform, * this should be an acceptable tradeoff. */ @@ -45,12 +45,16 @@ abstract class BaseProvider implements BaseProviderInterface { /** * Add a value to the cache. - * + * * @param {string} key - Key of the cached value * @param {string | Uint8Array | Record} value - Value to be cached * @param {number} maxAge - Maximum age in seconds for the value to be cached */ - public addToCache(key: string, value: string | Uint8Array | Record, maxAge: number): void { + public addToCache( + key: string, + value: string | Uint8Array | Record, + maxAge: number + ): void { if (maxAge <= 0) return; this.store.set(key, new ExpirableValue(value, maxAge)); @@ -65,13 +69,16 @@ abstract class BaseProvider implements BaseProviderInterface { /** * Retrieve a parameter value or return the cached value. - * + * * @param {string} name - Parameter name * @param {GetOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch */ - public async get(name: string, options?: GetOptionsInterface): Promise> { + public async get( + name: string, + options?: GetOptionsInterface + ): Promise> { const configs = new GetOptions(options, this.envVarsService); - const key = [ name, configs.transform ].toString(); + const key = [name, configs.transform].toString(); if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) { // If the code enters in this block, then the key must exist & not have been expired @@ -99,14 +106,17 @@ abstract class BaseProvider implements BaseProviderInterface { /** * Retrieve multiple parameter values or return the cached values. - * + * * @param {string} path - Parameters path * @param {GetMultipleOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch - * @returns + * @returns */ - public async getMultiple(path: string, options?: GetMultipleOptionsInterface): Promise> { + public async getMultiple( + path: string, + options?: GetMultipleOptionsInterface + ): Promise> { const configs = new GetMultipleOptions(options, this.envVarsService); - const key = [ path, configs.transform ].toString(); + const key = [path, configs.transform].toString(); if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) { // If the code enters in this block, then the key must exist & not have been expired @@ -122,7 +132,11 @@ abstract class BaseProvider implements BaseProviderInterface { } if (Object.keys(values) && configs.transform) { - values = transformValues(values, configs.transform, configs.throwOnTransformError); + values = transformValues( + values, + configs.transform, + configs.throwOnTransformError + ); } if (Array.from(Object.keys(values)).length !== 0) { @@ -134,47 +148,57 @@ abstract class BaseProvider implements BaseProviderInterface { /** * Check whether a key has expired in the cache or not. - * + * * It returns true if the key is expired or not present in the cache. - * + * * @param {string} key - Stringified representation of the key to retrieve */ public hasKeyExpiredInCache(key: string): boolean { const value = this.store.get(key); if (value) return value.isExpired(); - + return true; } /** * Retrieve parameter value from the underlying parameter store. - * + * * @param {string} name - Parameter name * @param {unknown} options - Options to pass to the underlying implemented method */ - protected abstract _get(name: string, options?: unknown): Promise; + protected abstract _get( + name: string, + options?: unknown + ): Promise; /** * Retrieve multiple parameter values from the underlying parameter store. - * + * * @param {string} path - Parameter name * @param {unknown} options - Options to pass to the underlying implementated method */ - protected abstract _getMultiple(path: string, options?: unknown): Promise>; - + protected abstract _getMultiple( + path: string, + options?: unknown + ): Promise>; } /** * Utility function to transform a value. - * + * * It supports JSON and binary transformations, as well as an 'auto' mode that will try to transform the value based on the key. - * + * * @param {string | Uint8Array | undefined} value - Value to be transformed * @param {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto' * @param {boolean} throwOnTransformError - Whether to throw an error if the transformation fails, when transforming multiple values this can be set to false * @param {string} key - Key of the value to be transformed, used to determine the transformation method when using 'auto' */ -const transformValue = (value: string | Uint8Array | undefined, transform: TransformOptions, throwOnTransformError: boolean, key: string): string | Record | undefined => { +const transformValue = ( + value: string | Uint8Array | undefined, + transform: TransformOptions, + throwOnTransformError: boolean, + key: string +): string | Record | undefined => { try { const normalizedTransform = transform.toLowerCase(); @@ -184,13 +208,15 @@ const transformValue = (value: string | Uint8Array | undefined, transform: Trans if ( (normalizedTransform === TRANSFORM_METHOD_JSON || - (normalizedTransform === 'auto' && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_JSON}`))) && + (normalizedTransform === 'auto' && + key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_JSON}`))) && typeof value === 'string' ) { return JSON.parse(value) as Record; } else if ( (normalizedTransform === TRANSFORM_METHOD_BINARY || - (normalizedTransform === 'auto' && key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_BINARY}`))) && + (normalizedTransform === 'auto' && + key.toLowerCase().endsWith(`.${TRANSFORM_METHOD_BINARY}`))) && typeof value === 'string' ) { return new TextDecoder('utf-8').decode(fromBase64(value)); @@ -207,18 +233,30 @@ const transformValue = (value: string | Uint8Array | undefined, transform: Trans /** * Utility function to transform multiple values. - * + * * It iterates over the values and applies the transformation to each one by calling the `transformValue` function. - * + * * @param {Record} value - Values to be transformed * @param {TransformOptions} transform - Transform to be applied, can be 'json', 'binary', or 'auto' * @param {boolean} throwOnTransformError - Whether to throw an error if the transformation fails, when transforming multiple values this can be set to false */ -const transformValues = (value: Record, transform: TransformOptions, throwOnTransformError: boolean): Record | undefined> => { - const transformedValues: Record | undefined> = {}; - for (const [ entryKey, entryValue ] of Object.entries(value)) { +const transformValues = ( + value: Record, + transform: TransformOptions, + throwOnTransformError: boolean +): Record | undefined> => { + const transformedValues: Record< + string, + string | Record | undefined + > = {}; + for (const [entryKey, entryValue] of Object.entries(value)) { try { - transformedValues[entryKey] = transformValue(entryValue, transform, throwOnTransformError, entryKey); + transformedValues[entryKey] = transformValue( + entryValue, + transform, + throwOnTransformError, + entryKey + ); } catch (error) { if (throwOnTransformError) throw new TransformParameterError(transform, (error as Error).message); @@ -230,7 +268,7 @@ const transformValues = (value: Record, transform: T /** * Utility function to clear all the caches of the default providers. - * + * * This is useful when you want to clear the cache of all the providers at once, for example during testing. */ const clearCaches = (): void => { @@ -245,4 +283,4 @@ export { transformValue, DEFAULT_PROVIDERS, clearCaches, -}; \ No newline at end of file +}; diff --git a/packages/parameters/src/Exceptions.ts b/packages/parameters/src/Exceptions.ts index 09712bb60c..21b557e13d 100644 --- a/packages/parameters/src/Exceptions.ts +++ b/packages/parameters/src/Exceptions.ts @@ -11,7 +11,4 @@ class TransformParameterError extends Error { } } -export { - GetParameterError, - TransformParameterError, -}; \ No newline at end of file +export { GetParameterError, TransformParameterError }; diff --git a/packages/parameters/src/ExpirableValue.ts b/packages/parameters/src/ExpirableValue.ts index 35f6c13be8..08ed63de5e 100644 --- a/packages/parameters/src/ExpirableValue.ts +++ b/packages/parameters/src/ExpirableValue.ts @@ -2,7 +2,7 @@ import type { ExpirableValueInterface } from './types'; /** * Class to represent a value that can expire. - * + * * Upon creation, the value is assigned a TTL (time to live) that is calculated * by adding the current time with the maximum age. */ @@ -11,11 +11,14 @@ class ExpirableValue implements ExpirableValueInterface { public value: string | Uint8Array | Record; /** - * + * * @param value - Value to be cached * @param maxAge - Maximum age in seconds for the value to be cached */ - public constructor(value: string | Uint8Array | Record, maxAge: number) { + public constructor( + value: string | Uint8Array | Record, + maxAge: number + ) { this.value = value; const timeNow = new Date(); this.ttl = timeNow.setSeconds(timeNow.getSeconds() + maxAge); @@ -31,6 +34,4 @@ class ExpirableValue implements ExpirableValueInterface { } } -export { - ExpirableValue -}; \ No newline at end of file +export { ExpirableValue }; diff --git a/packages/parameters/src/GetMultipleOptions.ts b/packages/parameters/src/GetMultipleOptions.ts index 53b57c9262..9b8c5ded5e 100644 --- a/packages/parameters/src/GetMultipleOptions.ts +++ b/packages/parameters/src/GetMultipleOptions.ts @@ -4,13 +4,19 @@ import type { GetMultipleOptionsInterface } from './types'; /** * Options for the `getMultiple` method. - * + * * Extends the `GetOptions` class and adds the `throwOnTransformError` option. */ -class GetMultipleOptions extends GetOptions implements GetMultipleOptionsInterface { - public throwOnTransformError: boolean = false; +class GetMultipleOptions + extends GetOptions + implements GetMultipleOptionsInterface +{ + public throwOnTransformError = false; - public constructor(options: GetMultipleOptionsInterface = {}, envVarsService: EnvironmentVariablesService) { + public constructor( + options: GetMultipleOptionsInterface = {}, + envVarsService: EnvironmentVariablesService + ) { super(options, envVarsService); if (options.throwOnTransformError !== undefined) { @@ -19,6 +25,4 @@ class GetMultipleOptions extends GetOptions implements GetMultipleOptionsInterfa } } -export { - GetMultipleOptions -}; \ No newline at end of file +export { GetMultipleOptions }; diff --git a/packages/parameters/src/GetOptions.ts b/packages/parameters/src/GetOptions.ts index b3cb97c2c3..23f77e5760 100644 --- a/packages/parameters/src/GetOptions.ts +++ b/packages/parameters/src/GetOptions.ts @@ -4,24 +4,26 @@ import type { GetOptionsInterface, TransformOptions } from './types'; /** * Options for the `get` method. - * + * * It merges the default options with the provided options. */ class GetOptions implements GetOptionsInterface { - public forceFetch: boolean = false; + public forceFetch = false; public maxAge!: number; public sdkOptions?: unknown; public transform?: TransformOptions; - public constructor(options: GetOptionsInterface = {}, envVarsService: EnvironmentVariablesService) { + public constructor( + options: GetOptionsInterface = {}, + envVarsService: EnvironmentVariablesService + ) { Object.assign(this, options); if (options.maxAge === undefined) { - this.maxAge = envVarsService.getParametersMaxAge() ?? DEFAULT_MAX_AGE_SECS; + this.maxAge = + envVarsService.getParametersMaxAge() ?? DEFAULT_MAX_AGE_SECS; } } } -export { - GetOptions -}; \ No newline at end of file +export { GetOptions }; diff --git a/packages/parameters/src/appconfig/AppConfigProvider.ts b/packages/parameters/src/appconfig/AppConfigProvider.ts index 1d82b156c5..ea28d28f5e 100644 --- a/packages/parameters/src/appconfig/AppConfigProvider.ts +++ b/packages/parameters/src/appconfig/AppConfigProvider.ts @@ -13,14 +13,14 @@ import type { /** * ## Intro * The Parameters utility provides an AppConfigProvider that allows to retrieve configuration profiles from AWS AppConfig. - * + * * ## Getting started - * + * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. - * + * * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: - * + * * ```sh * npm install @aws-lambda-powertools/parameters @aws-sdk/client-appconfigdata * ``` @@ -30,9 +30,9 @@ import type { * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * const configProvider = new AppConfigProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a configuration profile * const encodedConfig = await configProvider.get('my-config'); @@ -40,82 +40,82 @@ import type { * }; * ``` * If you want to retrieve configs without customizing the provider, you can use the {@link getAppConfig} function instead. - * + * * ## Advanced usage - * + * * ### Caching - * + * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. - * + * * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * const configProvider = new AppConfigProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a configuration profile and cache it for 10 seconds * const encodedConfig = await configProvider.get('my-config', { maxAge: 10 }); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` - * + * * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. - * + * * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * const configProvider = new AppConfigProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a config and always fetch the latest value * const config = await configProvider.get('my-config', { forceFetch: true }); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` - * + * * ### Transformations - * + * * For configurations stored as freeform JSON, Freature Flag, you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. - * + * * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * const configProvider = new AppConfigProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a JSON config or Feature Flag and parse it as JSON * const config = await configProvider.get('my-config', { transform: 'json' }); * }; * ``` - * + * * For configurations that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string. - * + * * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * const configProvider = new AppConfigProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a base64-encoded string and decode it * const config = await configProvider.get('my-config', { transform: 'binary' }); * }; * ``` - * + * * ### Extra SDK options - * + * * When retrieving a configuration profile, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. - * + * * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * const configProvider = new AppConfigProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a config and pass extra options to the AWS SDK v3 for JavaScript client * const config = await configProvider.get('my-config', { @@ -126,39 +126,39 @@ import type { * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` - * + * * This object accepts the same options as the [AWS SDK v3 for JavaScript AppConfigData client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/interfaces/startconfigurationsessioncommandinput.html). - * + * * ### Customize AWS SDK v3 for JavaScript client - * + * * By default, the provider will create a new AppConfigData client using the default configuration. - * + * * You can customize the client by passing a custom configuration object to the provider. - * + * * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * const configProvider = new AppConfigProvider({ * clientConfig: { region: 'eu-west-1' }, * }); * ``` - * + * * This object accepts the same options as the [AWS SDK v3 for JavaScript AppConfig Data client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/interfaces/appconfigdataclientconfig.html). - * + * * Otherwise, if you want to use a custom client altogether, you can pass it to the provider. - * + * * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; * import { AppConfigDataClient } from '@aws-sdk/client-appconfigdata'; - * + * * const client = new AppConfigDataClient({ region: 'eu-west-1' }); * const configProvider = new AppConfigProvider({ * awsSdkV3Client: client, * }); * ``` - * + * * This object must be an instance of the [AWS SDK v3 for JavaScript AppConfig Data client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/classes/appconfigdataclient.html). * * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). @@ -169,7 +169,7 @@ class AppConfigProvider extends BaseProvider { protected valueStore = new Map(); private application?: string; private environment: string; - + /** * It initializes the AppConfigProvider class. * * @@ -186,8 +186,9 @@ class AppConfigProvider extends BaseProvider { } else { this.client = new AppConfigDataClient(options.clientConfig || {}); } - - this.application = options?.application || this.envVarsService.getServiceName(); + + this.application = + options?.application || this.envVarsService.getServiceName(); if (!this.application || this.application.trim().length === 0) { throw new Error( 'Application name is not defined or POWERTOOLS_SERVICE_NAME is not set' @@ -198,28 +199,28 @@ class AppConfigProvider extends BaseProvider { /** * Retrieve a configuration profile from AWS AppConfig. - * + * * @example * ```typescript * import { AppConfigProvider } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * const configProvider = new AppConfigProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a configuration profile * const encodedConfig = await configProvider.get('my-config'); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` - * + * * You can customize the retrieval of the configuration profile by passing options to the function: * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * + * * For usage examples check {@link AppConfigProvider}. - * + * * @param {string} name - The name of the configuration profile or its ID * @param {GetAppConfigCombinedInterface} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ @@ -251,7 +252,6 @@ class AppConfigProvider extends BaseProvider { name: string, options?: AppConfigGetOptionsInterface ): Promise { - /** * The new AppConfig APIs require two API calls to return the configuration * First we start the session and after that we retrieve the configuration @@ -262,7 +262,6 @@ class AppConfigProvider extends BaseProvider { * {@link https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-retrieving-the-configuration.html} **/ if (!this.configurationTokenStore.has(name)) { - const sessionOptions: StartConfigurationSessionCommandInput = { ...(options?.sdkOptions || {}), ApplicationIdentifier: this.application, @@ -276,7 +275,8 @@ class AppConfigProvider extends BaseProvider { const session = await this.client.send(sessionCommand); - if (!session.InitialConfigurationToken) throw new Error('Unable to retrieve the configuration token'); + if (!session.InitialConfigurationToken) + throw new Error('Unable to retrieve the configuration token'); this.configurationTokenStore.set(name, session.InitialConfigurationToken); } @@ -288,16 +288,22 @@ class AppConfigProvider extends BaseProvider { const response = await this.client.send(getConfigurationCommand); if (response.NextPollConfigurationToken) { - this.configurationTokenStore.set(name, response.NextPollConfigurationToken); + this.configurationTokenStore.set( + name, + response.NextPollConfigurationToken + ); } else { this.configurationTokenStore.delete(name); } /** When the response is not empty, stash the result locally before returning - * See AppConfig docs: + * See AppConfig docs: * {@link https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-retrieving-the-configuration.html} **/ - if (response.Configuration !== undefined && response.Configuration?.length > 0 ) { + if ( + response.Configuration !== undefined && + response.Configuration?.length > 0 + ) { this.valueStore.set(name, response.Configuration); return response.Configuration; diff --git a/packages/parameters/src/appconfig/getAppConfig.ts b/packages/parameters/src/appconfig/getAppConfig.ts index 7613e0d942..f5a8506845 100644 --- a/packages/parameters/src/appconfig/getAppConfig.ts +++ b/packages/parameters/src/appconfig/getAppConfig.ts @@ -4,14 +4,14 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; /** * ## Intro * The Parameters utility provides an AppConfigProvider that allows to retrieve configuration profiles from AWS AppConfig. - * + * * ## Getting started - * + * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. - * + * * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: - * + * * ```sh * npm install @aws-lambda-powertools/parameters @aws-sdk/client-appconfigdata * ``` @@ -21,79 +21,79 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * @example * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * export const handler = async (): Promise => { * // Retrieve a configuration profile * const encodedConfig = await getAppConfig('my-config'); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` - * + * * ## Advanced usage - * + * * ### Caching - * + * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. - * + * * @example * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * export const handler = async (): Promise => { * // Retrieve a configuration profile and cache it for 10 seconds * const encodedConfig = await getAppConfig('my-config'); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` - * + * * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. - * + * * @example * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * export const handler = async (): Promise => { * // Retrieve a config and always fetch the latest value * const config = await getAppConfig('my-config', { forceFetch: true }); * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` - * + * * ### Transformations - * + * * For configurations stored as freeform JSON, Freature Flag, you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. - * + * * @example * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * export const handler = async (): Promise => { * // Retrieve a JSON config or Feature Flag and parse it as JSON * const config = await getAppConfig('my-config', { transform: 'json' }); * }; * ``` - * + * * For configurations that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string. - * + * * @example * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * export const handler = async (): Promise => { * // Retrieve a base64-encoded string and decode it * const config = await getAppConfig('my-config', { transform: 'binary' }); * }; * ``` - * + * * ### Extra SDK options - * + * * When retrieving a configuration profile, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. - * + * * @example * ```typescript * import { getAppConfig } from '@aws-lambda-powertools/parameters/appconfig'; - * + * * export const handler = async (): Promise => { * // Retrieve a config and pass extra options to the AWS SDK v3 for JavaScript client * const config = await getAppConfig('my-config', { @@ -104,9 +104,9 @@ import type { GetAppConfigCombinedInterface } from '../types/AppConfigProvider'; * const config = new TextDecoder('utf-8').decode(encodedConfig); * }; * ``` - * + * * This object accepts the same options as the [AWS SDK v3 for JavaScript AppConfigData client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-appconfigdata/interfaces/startconfigurationsessioncommandinput.html). - * + * * ### Built-in provider class * * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link AppConfigProvider} class. diff --git a/packages/parameters/src/appconfig/index.ts b/packages/parameters/src/appconfig/index.ts index c0215de062..9b8a24a5a4 100644 --- a/packages/parameters/src/appconfig/index.ts +++ b/packages/parameters/src/appconfig/index.ts @@ -1,2 +1,2 @@ export * from './AppConfigProvider'; -export * from './getAppConfig'; \ No newline at end of file +export * from './getAppConfig'; diff --git a/packages/parameters/src/config/ConfigServiceInterface.ts b/packages/parameters/src/config/ConfigServiceInterface.ts index 9ea8ff66fa..c422265c48 100644 --- a/packages/parameters/src/config/ConfigServiceInterface.ts +++ b/packages/parameters/src/config/ConfigServiceInterface.ts @@ -1,15 +1,11 @@ interface ConfigServiceInterface { + get?(name: string): string; - get?(name: string): string - - getServiceName(): string + getServiceName(): string; - getParametersMaxAge(): number | undefined - - getSSMDecrypt(): string + getParametersMaxAge(): number | undefined; + getSSMDecrypt(): string; } -export { - ConfigServiceInterface, -}; \ No newline at end of file +export { ConfigServiceInterface }; diff --git a/packages/parameters/src/config/EnvironmentVariablesService.ts b/packages/parameters/src/config/EnvironmentVariablesService.ts index e528a76d05..3ae96c5d56 100644 --- a/packages/parameters/src/config/EnvironmentVariablesService.ts +++ b/packages/parameters/src/config/EnvironmentVariablesService.ts @@ -2,8 +2,10 @@ import { ConfigServiceInterface } from './ConfigServiceInterface'; import { DEFAULT_MAX_AGE_SECS } from '../constants'; import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons'; -class EnvironmentVariablesService extends CommonEnvironmentVariablesService implements ConfigServiceInterface { - +class EnvironmentVariablesService + extends CommonEnvironmentVariablesService + implements ConfigServiceInterface +{ // Environment variables private parametersMaxAgeVariable = 'POWERTOOLS_PARAMETERS_MAX_AGE'; private ssmDecryptVariable = 'POWERTOOLS_PARAMETERS_SSM_DECRYPT'; @@ -12,7 +14,7 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl const maxAge = this.get(this.parametersMaxAgeVariable); if (maxAge.length === 0) return undefined; - + const maxAgeAsNumber = parseInt(maxAge, 10); if (isNaN(maxAgeAsNumber)) { console.warn( @@ -26,9 +28,6 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl public getSSMDecrypt(): string { return this.get(this.ssmDecryptVariable); } - } -export { - EnvironmentVariablesService, -}; \ No newline at end of file +export { EnvironmentVariablesService }; diff --git a/packages/parameters/src/constants.ts b/packages/parameters/src/constants.ts index b9cd5beeb3..7edd7ad9d4 100644 --- a/packages/parameters/src/constants.ts +++ b/packages/parameters/src/constants.ts @@ -1,3 +1,3 @@ export const DEFAULT_MAX_AGE_SECS = 5; export const TRANSFORM_METHOD_JSON = 'json'; -export const TRANSFORM_METHOD_BINARY = 'binary'; \ No newline at end of file +export const TRANSFORM_METHOD_BINARY = 'binary'; diff --git a/packages/parameters/src/docs.ts b/packages/parameters/src/docs.ts index 028b2bbc6b..f43ccf14aa 100644 --- a/packages/parameters/src/docs.ts +++ b/packages/parameters/src/docs.ts @@ -1,4 +1,4 @@ export * from './appconfig'; export * from './ssm'; export * from './secrets'; -export * from './dynamodb'; \ No newline at end of file +export * from './dynamodb'; diff --git a/packages/parameters/src/dynamodb/DynamoDBProvider.ts b/packages/parameters/src/dynamodb/DynamoDBProvider.ts index d178542894..1800454bf9 100644 --- a/packages/parameters/src/dynamodb/DynamoDBProvider.ts +++ b/packages/parameters/src/dynamodb/DynamoDBProvider.ts @@ -1,25 +1,32 @@ import { BaseProvider } from '../BaseProvider'; -import { DynamoDBClient, GetItemCommand, paginateQuery } from '@aws-sdk/client-dynamodb'; +import { + DynamoDBClient, + GetItemCommand, + paginateQuery, +} from '@aws-sdk/client-dynamodb'; import type { DynamoDBProviderOptions, DynamoDBGetOptionsInterface, - DynamoDBGetMultipleOptionsInterface + DynamoDBGetMultipleOptionsInterface, } from '../types/DynamoDBProvider'; -import type { GetItemCommandInput, QueryCommandInput } from '@aws-sdk/client-dynamodb'; +import type { + GetItemCommandInput, + QueryCommandInput, +} from '@aws-sdk/client-dynamodb'; import { marshall, unmarshall } from '@aws-sdk/util-dynamodb'; import type { PaginationConfiguration } from '@aws-sdk/types'; /** * ## Intro * The Parameters utility provides a DynamoDBProvider that allows to retrieve values from Amazon DynamoDB. - * + * * ## Getting started - * + * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. - * + * * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: - * + * * ```sh * npm install @aws-lambda-powertools/parameters @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb * ``` @@ -27,51 +34,51 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * ## Basic usage * * Retrieve a value from DynamoDB: - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve a value from DynamoDB * const value = await tableProvider.get('my-value-key'); * }; * ``` - * + * * You can also retrieve multiple values at once: - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve multiple values from DynamoDB * const values = await tableProvider.getMultiple('my-values-path'); * }; - * + * * ## Advanced usage - * + * * ### Caching - * + * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve a value and cache it for 10 seconds * const value = await tableProvider.get('my-value-key', { maxAge: 10 }); @@ -79,17 +86,17 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * const values = await tableProvider.getMultiple('my-values-path', { maxAge: 20 }); * }; * ``` - * + * * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve a value and skip cache * const value = await tableProvider.get('my-value-key', { forceFetch: true }); @@ -97,19 +104,19 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * const values = await tableProvider.getMultiple('my-values-path', { forceFetch: true }); * }; * ``` - * + * * ### Transformations - * + * * For values stored as JSON you can use the transform argument for deserialization. This will return a JavaScript object instead of a string. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve a value and parse it as JSON * const value = await tableProvider.get('my-value-key', { transform: 'json' }); @@ -117,17 +124,17 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * const values = await tableProvider.getMultiple('my-values-path', { transform: 'json' }); * }; * ``` - * + * * For values that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return a decoded string. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve a base64-encoded string and decode it * const value = await tableProvider.get('my-value-key', { transform: 'binary' }); @@ -135,34 +142,34 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * const values = await tableProvider.getMultiple('my-values-path', { transform: 'binary' }); * }; * ``` - * + * * When retrieving multiple values, you can also use the `transform` argument set to `auto` to let the provider automatically detect the type of transformation to apply. * The provider will use the suffix of the sort key (`sk`) to determine the transformation to apply. For example, if the sort key is `my-value-key.json`, the provider will * automatically parse the value as JSON. Likewise, if the sort key is `my-value-key.binary`, the provider will automatically decode the value as base64-encoded binary data. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve multiple values and automatically detect the transformation to apply * const values = await tableProvider.getMultiple('my-values-path', { transform: 'auto' }); * }; * ``` - * + * * ### Custom key names - * + * * By default, the provider will use the following key names: `id` for the partition key, `sk` for the sort key, and `value` for the value. * You can adjust the key names by using the `keyAttr`, `sortAttr`, and `valueAttr` parameters. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * keyAttr: 'key', @@ -170,19 +177,19 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * valueAttr: 'val', * }); * ``` - * + * * ### Extra SDK options - * + * * When retrieving values, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve a value and pass extra options to the AWS SDK v3 for JavaScript client * const value = await tableProvider.get('my-value-key', { @@ -192,49 +199,49 @@ import type { PaginationConfiguration } from '@aws-sdk/types'; * }); * }; * ``` - * + * * The objects accept the same options as respectively the [AWS SDK v3 for JavaScript PutItem command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/putitemcommand.html) and the [AWS SDK v3 for JavaScript DynamoDB client Query command](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/querycommand.html). - * + * * ### Customize AWS SDK v3 for JavaScript client - * + * * By default, the provider will create a new DynamoDB client using the default configuration. - * + * * You can customize the client by passing a custom configuration object to the provider. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * clientConfig: { region: 'eu-west-1' }, * }); * ``` - * + * * This object accepts the same options as the [AWS SDK v3 for JavaScript DynamoDB client constructor](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/dynamodbclient.html). - * + * * Otherwise, if you want to use a custom client altogether, you can pass it to the provider. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; * import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; - * + * * const client = new DynamoDBClient({ region: 'eu-west-1' }); * const tableProvider = new DynamoDBProvider({ * awsSdkV3Client: client, * }); * ``` - * + * * This object must be an instance of the [AWS SDK v3 for JavaScript DynamoDB client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/dynamodbclient.html). * * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). */ class DynamoDBProvider extends BaseProvider { public client: DynamoDBClient; - protected keyAttr: string = 'id'; - protected sortAttr: string = 'sk'; + protected keyAttr = 'id'; + protected sortAttr = 'sk'; protected tableName: string; - protected valueAttr: string = 'value'; + protected valueAttr = 'value'; /** * It initializes the DynamoDBProvider class. @@ -263,29 +270,29 @@ class DynamoDBProvider extends BaseProvider { /** * Retrieve a value from Amazon DynamoDB. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve a single value * const value = await tableProvider.get('my-value-key'); * }; * ``` - * + * * You can customize the retrieval of the value by passing options to the function: * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * + * * For usage examples check {@link DynamoDBProvider}. - * + * * @param {string} name - The name of the value to retrieve (i.e. the partition key) * @param {DynamoDBGetOptionsInterface} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ @@ -294,35 +301,37 @@ class DynamoDBProvider extends BaseProvider { name: string, options?: DynamoDBGetOptionsInterface ): Promise> { - return super.get(name, options) as Promise>; + return super.get(name, options) as Promise< + undefined | string | Record + >; } /** * Retrieve multiple values from Amazon DynamoDB. - * + * * @example * ```typescript * import { DynamoDBProvider } from '@aws-lambda-powertools/parameters/dynamodb'; - * + * * const tableProvider = new DynamoDBProvider({ * tableName: 'my-table', * }); - * + * * export const handler = async (): Promise => { * // Retrieve multiple values * const values = await tableProvider.getMultiple('my-values-path'); * }; * ``` - * + * * You can customize the retrieval of the values by passing options to the function: * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client * * `throwOnTransformError` - Whether to throw an error if the transform fails (default: `true`) - * + * * For usage examples check {@link DynamoDBProvider}. - * + * * @param {string} path - The path of the values to retrieve (i.e. the partition key) * @param {DynamoDBGetMultipleOptionsInterface} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ @@ -351,7 +360,7 @@ class DynamoDBProvider extends BaseProvider { ProjectionExpression: '#value', ExpressionAttributeNames: { '#value': this.valueAttr, - } + }, }; const result = await this.client.send(new GetItemCommand(sdkOptions)); @@ -360,7 +369,7 @@ class DynamoDBProvider extends BaseProvider { /** * Retrieve multiple items from Amazon DynamoDB. - * + * * @param {string} path - The path of the values to retrieve (i.e. the partition key) * @param {DynamoDBGetMultipleOptionsInterface} options - Options to customize the retrieval */ @@ -389,9 +398,8 @@ class DynamoDBProvider extends BaseProvider { for await (const page of paginateQuery(paginationOptions, sdkOptions)) { for (const item of page.Items || []) { const unmarshalledItem = unmarshall(item); - parameters[ - unmarshalledItem[this.sortAttr] - ] = unmarshalledItem[this.valueAttr]; + parameters[unmarshalledItem[this.sortAttr]] = + unmarshalledItem[this.valueAttr]; } } @@ -399,6 +407,4 @@ class DynamoDBProvider extends BaseProvider { } } -export { - DynamoDBProvider, -}; \ No newline at end of file +export { DynamoDBProvider }; diff --git a/packages/parameters/src/dynamodb/index.ts b/packages/parameters/src/dynamodb/index.ts index f5b2c448a3..65bb792929 100644 --- a/packages/parameters/src/dynamodb/index.ts +++ b/packages/parameters/src/dynamodb/index.ts @@ -1 +1 @@ -export * from './DynamoDBProvider'; \ No newline at end of file +export * from './DynamoDBProvider'; diff --git a/packages/parameters/src/secrets/SecretsProvider.ts b/packages/parameters/src/secrets/SecretsProvider.ts index 2144fe1268..c6555518d2 100644 --- a/packages/parameters/src/secrets/SecretsProvider.ts +++ b/packages/parameters/src/secrets/SecretsProvider.ts @@ -1,7 +1,7 @@ import { BaseProvider } from '../BaseProvider'; import { SecretsManagerClient, - GetSecretValueCommand + GetSecretValueCommand, } from '@aws-sdk/client-secrets-manager'; import type { GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; import type { @@ -14,14 +14,14 @@ import type { /** * ## Intro * The Parameters utility provides a SecretsProvider that allows to retrieve secrets from AWS Secrets Manager. - * + * * ## Getting started - * + * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. - * + * * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for Secrets Manager: - * + * * ```sh * npm install @aws-lambda-powertools/parameters @aws-sdk/client-secrets-manager * ``` @@ -31,76 +31,76 @@ import type { * @example * ```typescript * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; - * + * * const secretsProvider = new SecretsProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a secret * const secret = await secretsProvider.get('my-secret'); * }; * ``` - * + * * If you want to retrieve secrets without customizing the provider, you can use the {@link getSecret} function instead. - * + * * ## Advanced usage - * + * * ### Caching - * + * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. - * + * * @example * ```typescript * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; - * + * * const secretsProvider = new SecretsProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a secret and cache it for 10 seconds * const secret = await secretsProvider.get('my-secret', { maxAge: 10 }); * }; * ``` - * + * * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. - * + * * @example * ```typescript * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; - * + * * const secretsProvider = new SecretsProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a secret and always fetch the latest value * const secret = await secretsProvider.get('my-secret', { forceFetch: true }); * }; * ``` - * + * * ### Transformations - * + * * For parameters stored in JSON or Base64 format, you can use the transform argument for deserialization. - * + * * @example * ```typescript * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; - * + * * const secretsProvider = new SecretsProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a secret and parse it as JSON * const secret = await secretsProvider.get('my-secret', { transform: 'json' }); * }; * ``` - * + * * ### Extra SDK options - * + * * When retrieving a secret, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. - * + * * @example * ```typescript * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; - * + * * const secretsProvider = new SecretsProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a secret and pass extra options to the AWS SDK v3 for JavaScript client * const secret = await secretsProvider.get('my-secret', { @@ -110,41 +110,41 @@ import type { * }); * }; * ``` - * + * * This object accepts the same options as the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/interfaces/getsecretvaluecommandinput.html). - * + * * ### Customize AWS SDK v3 for JavaScript client - * + * * By default, the provider will create a new Secrets Manager client using the default configuration. - * + * * You can customize the client by passing a custom configuration object to the provider. - * + * * @example * ```typescript * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; - * + * * const secretsProvider = new SecretsProvider({ * clientConfig: { region: 'eu-west-1' }, * }); * ``` - * + * * This object accepts the same options as the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/interfaces/secretsmanagerclientconfig.html). - * + * * Otherwise, if you want to use a custom client altogether, you can pass it to the provider. - * + * * @example * ```typescript * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; * import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; - * + * * const client = new SecretsManagerClient({ region: 'eu-west-1' }); * const secretsProvider = new SecretsProvider({ * awsSdkV3Client: client, * }); * ``` - * + * * This object must be an instance of the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/classes/secretsmanagerclient.html). - * + * * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). * * @class @@ -156,7 +156,7 @@ class SecretsProvider extends BaseProvider { /** * It initializes the SecretsProvider class. - * + * * @param {SecretsProviderOptions} config - The configuration object. */ public constructor(config?: SecretsProviderOptions) { @@ -176,42 +176,47 @@ class SecretsProvider extends BaseProvider { /** * Retrieve a secret from AWS Secrets Manager. - * + * * @example * ```typescript * import { SecretsProvider } from '@aws-lambda-powertools/parameters/secrets'; - * + * * const secretsProvider = new SecretsProvider(); - * + * * export const handler = async (): Promise => { * // Retrieve a secret * const secret = await secretsProvider.get('my-secret'); * }; * ``` - * + * * You can customize the retrieval of the secret by passing options to the function: * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client - * + * * For usage examples check {@link SecretsProvider}. - * + * * @param {string} name - The name of the secret * @param {SecretsGetOptions} options - Options to customize the retrieval of the secret * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ public async get< ExplicitUserProvidedType = undefined, - InferredFromOptionsType extends SecretsGetOptionsUnion | undefined = SecretsGetOptionsUnion + InferredFromOptionsType extends + | SecretsGetOptionsUnion + | undefined = SecretsGetOptionsUnion >( name: string, options?: InferredFromOptionsType & SecretsGetOptions - ): Promise | undefined> { - return super.get( - name, - options - ) as Promise | undefined>; + ): Promise< + | SecretsGetOutput + | undefined + > { + return super.get(name, options) as Promise< + | SecretsGetOutput + | undefined + >; } /** @@ -239,7 +244,9 @@ class SecretsProvider extends BaseProvider { SecretId: name, }; - const result = await this.client.send(new GetSecretValueCommand(sdkOptions)); + const result = await this.client.send( + new GetSecretValueCommand(sdkOptions) + ); if (result.SecretString) return result.SecretString; @@ -248,7 +255,7 @@ class SecretsProvider extends BaseProvider { /** * Retrieving multiple parameter values is not supported with AWS Secrets Manager. - * + * * @throws Not Implemented Error. */ protected async _getMultiple( @@ -259,6 +266,4 @@ class SecretsProvider extends BaseProvider { } } -export { - SecretsProvider, -}; \ No newline at end of file +export { SecretsProvider }; diff --git a/packages/parameters/src/secrets/getSecret.ts b/packages/parameters/src/secrets/getSecret.ts index 812e165d5b..4b7994e1b7 100644 --- a/packages/parameters/src/secrets/getSecret.ts +++ b/packages/parameters/src/secrets/getSecret.ts @@ -9,14 +9,14 @@ import type { /** * ## Intro * The Parameters utility provides a SecretsProvider that allows to retrieve secrets from AWS Secrets Manager. - * + * * ## Getting started - * + * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. - * + * * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for Secrets Manager: - * + * * ```sh * npm install @aws-lambda-powertools/parameters @aws-sdk/client-secrets-manager * ``` @@ -26,64 +26,64 @@ import type { * @example * ```typescript * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; - * + * * export const handler = async (): Promise => { * // Retrieve a secret * const secret = await getSecret('my-secret'); * }; * ``` - * + * * ## Advanced usage - * + * * ### Caching - * + * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. - * + * * @example * ```typescript * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; - * + * * export const handler = async (): Promise => { * // Retrieve a secret and cache it for 10 seconds * const secret = await getSecret('my-secret', { maxAge: 10 }); * }; * ``` - * + * * If instead you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter. - * + * * @example * ```typescript * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; - * + * * export const handler = async (): Promise => { * // Retrieve a secret and always fetch the latest value * const secret = await getSecret('my-secret', { forceFetch: true }); * }; * ``` - * + * * ### Transformations - * + * * For parameters stored as JSON or base64-encoded strings, you can use the transform argument set to `json` or `binary` for deserialization. - * + * * @example * ```typescript * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; - * + * * export const handler = async (): Promise => { * // Retrieve a secret and parse it as JSON * const secret = await getSecret('my-secret', { transform: 'json' }); * }; * ``` - * + * * ### Extra SDK options - * + * * When retrieving a secret, you can pass extra options to the AWS SDK v3 for JavaScript client by using the `sdkOptions` parameter. - * + * * @example * ```typescript * import { getSecret } from '@aws-lambda-powertools/parameters/secrets'; - * + * * export const handler = async (): Promise => { * // Retrieve a secret and pass extra options to the AWS SDK v3 for JavaScript client * const secret = await getSecret('my-secret', { @@ -93,9 +93,9 @@ import type { * }); * }; * ``` - * + * * This object accepts the same options as the [AWS SDK v3 for JavaScript Secrets Manager client](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-secrets-manager/interfaces/getsecretvaluecommandinput.html). - * + * * ### Built-in provider class * * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SecretsProvider} class. @@ -109,20 +109,27 @@ import type { */ const getSecret = async < ExplicitUserProvidedType = undefined, - InferredFromOptionsType extends SecretsGetOptionsUnion | undefined = SecretsGetOptionsUnion + InferredFromOptionsType extends + | SecretsGetOptionsUnion + | undefined = SecretsGetOptionsUnion >( name: string, options?: InferredFromOptionsType & SecretsGetOptions -): Promise | undefined> => { +): Promise< + | SecretsGetOutput + | undefined +> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('secrets')) { DEFAULT_PROVIDERS.secrets = new SecretsProvider(); } - return ( - DEFAULT_PROVIDERS.secrets as SecretsProvider - ).get(name, options) as Promise | undefined>; + return (DEFAULT_PROVIDERS.secrets as SecretsProvider).get( + name, + options + ) as Promise< + | SecretsGetOutput + | undefined + >; }; -export { - getSecret -}; \ No newline at end of file +export { getSecret }; diff --git a/packages/parameters/src/secrets/index.ts b/packages/parameters/src/secrets/index.ts index 0496e024f1..5e54bcec5b 100644 --- a/packages/parameters/src/secrets/index.ts +++ b/packages/parameters/src/secrets/index.ts @@ -1,2 +1,2 @@ export * from './SecretsProvider'; -export * from './getSecret'; \ No newline at end of file +export * from './getSecret'; diff --git a/packages/parameters/src/ssm/SSMProvider.ts b/packages/parameters/src/ssm/SSMProvider.ts index 6fe9a3c4a5..72043f0270 100644 --- a/packages/parameters/src/ssm/SSMProvider.ts +++ b/packages/parameters/src/ssm/SSMProvider.ts @@ -1,11 +1,15 @@ -import { BaseProvider, DEFAULT_PROVIDERS, transformValue } from '../BaseProvider'; +import { + BaseProvider, + DEFAULT_PROVIDERS, + transformValue, +} from '../BaseProvider'; import { GetParameterError } from '../Exceptions'; import { DEFAULT_MAX_AGE_SECS } from '../constants'; import { SSMClient, GetParameterCommand, paginateGetParametersByPath, - GetParametersCommand + GetParametersCommand, } from '@aws-sdk/client-ssm'; import type { GetParameterCommandInput, @@ -319,11 +323,19 @@ class SSMProvider extends BaseProvider { * @param {SSMGetOptions} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ - public async get( + public async get< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType extends SSMGetOptions | undefined = SSMGetOptions + >( name: string, options?: InferredFromOptionsType & SSMGetOptions - ): Promise | undefined> { - return super.get(name, options) as Promise | undefined>; + ): Promise< + SSMGetOutput | undefined + > { + return super.get(name, options) as Promise< + | SSMGetOutput + | undefined + >; } /** @@ -356,11 +368,22 @@ class SSMProvider extends BaseProvider { * @param {SSMGetMultipleOptions} options - Options to configure the retrieval * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ */ - public async getMultiple( + public async getMultiple< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType extends + | SSMGetMultipleOptionsUnion + | undefined = undefined + >( path: string, options?: InferredFromOptionsType & SSMGetMultipleOptions - ): Promise | undefined> { - return super.getMultiple(path, options) as Promise | undefined>; + ): Promise< + | SSMGetMultipleOutput + | undefined + > { + return super.getMultiple(path, options) as Promise< + | SSMGetMultipleOutput + | undefined + >; } /** @@ -422,42 +445,52 @@ class SSMProvider extends BaseProvider { decrypt: this.resolveDecryptionConfigValue({}) || false, maxAge: DEFAULT_MAX_AGE_SECS, throwOnError: true, - }, ...options + }, + ...options, }; let response: Record = {}; // NOTE: We fail early to avoid unintended graceful errors being replaced with their '_errors' param values - SSMProvider.throwIfErrorsKeyIsPresent(parameters, this.errorsKey, configs.throwOnError); + SSMProvider.throwIfErrorsKeyIsPresent( + parameters, + this.errorsKey, + configs.throwOnError + ); - const { - parametersToFetchInBatch, - parametersToDecrypt - } = SSMProvider.splitBatchAndDecryptParameters(parameters, configs); + const { parametersToFetchInBatch, parametersToDecrypt } = + SSMProvider.splitBatchAndDecryptParameters(parameters, configs); // NOTE: We need to find out whether all parameters must be decrypted or not to know which API to use // Logic: // GetParameters API -> When decrypt is used for all parameters in the the batch // GetParameter API -> When decrypt is used for one or more in the batch - if (Object.keys(parametersToDecrypt).length !== Object.keys(parameters).length) { - const { - response: decryptResponse, - errors: decryptErrors - } = await this.getParametersByNameWithDecryptOption(parametersToDecrypt, configs.throwOnError); - const { - response: batchResponse, - errors: batchErrors - } = await this.getParametersBatchByName(parametersToFetchInBatch, configs.throwOnError, false); + if ( + Object.keys(parametersToDecrypt).length !== Object.keys(parameters).length + ) { + const { response: decryptResponse, errors: decryptErrors } = + await this.getParametersByNameWithDecryptOption( + parametersToDecrypt, + configs.throwOnError + ); + const { response: batchResponse, errors: batchErrors } = + await this.getParametersBatchByName( + parametersToFetchInBatch, + configs.throwOnError, + false + ); response = { ...decryptResponse, ...batchResponse }; // Fail-fast disabled, let's aggregate errors under "_errors" key so they can handle gracefully if (!configs.throwOnError) { - response[this.errorsKey] = [ ...decryptErrors, ...batchErrors ]; + response[this.errorsKey] = [...decryptErrors, ...batchErrors]; } } else { - const { - response: batchResponse, - errors: batchErrors - } = await this.getParametersBatchByName(parametersToDecrypt, configs.throwOnError, true); + const { response: batchResponse, errors: batchErrors } = + await this.getParametersBatchByName( + parametersToDecrypt, + configs.throwOnError, + true + ); response = batchResponse; // Fail-fast disabled, let's aggregate errors under "_errors" key so they can handle gracefully @@ -466,7 +499,9 @@ class SSMProvider extends BaseProvider { } } - return response as unknown as Promise>; + return response as unknown as Promise< + SSMGetParametersByNameOutput + >; } /** @@ -483,7 +518,10 @@ class SSMProvider extends BaseProvider { ...(options?.sdkOptions || {}), Name: name, }; - sdkOptions.WithDecryption = this.resolveDecryptionConfigValue(options, sdkOptions); + sdkOptions.WithDecryption = this.resolveDecryptionConfigValue( + options, + sdkOptions + ); const result = await this.client.send(new GetParameterCommand(sdkOptions)); return result.Parameter?.Value; @@ -504,16 +542,24 @@ class SSMProvider extends BaseProvider { Path: path, }; const paginationOptions: PaginationConfiguration = { - client: this.client + client: this.client, }; - sdkOptions.WithDecryption = this.resolveDecryptionConfigValue(options, sdkOptions); - sdkOptions.Recursive = options?.recursive !== undefined ? - options.recursive : sdkOptions.Recursive; - paginationOptions.pageSize = sdkOptions.MaxResults !== undefined ? - sdkOptions.MaxResults : undefined; + sdkOptions.WithDecryption = this.resolveDecryptionConfigValue( + options, + sdkOptions + ); + sdkOptions.Recursive = + options?.recursive !== undefined + ? options.recursive + : sdkOptions.Recursive; + paginationOptions.pageSize = + sdkOptions.MaxResults !== undefined ? sdkOptions.MaxResults : undefined; const parameters: Record = {}; - for await (const page of paginateGetParametersByPath(paginationOptions, sdkOptions)) { + for await (const page of paginateGetParametersByPath( + paginationOptions, + sdkOptions + )) { for (const parameter of page.Parameters || []) { /** * Standardize the parameter name @@ -556,7 +602,10 @@ class SSMProvider extends BaseProvider { } const result = await this.client.send(new GetParametersCommand(sdkOptions)); - const errors = SSMProvider.handleAnyInvalidGetParameterErrors(result, throwOnError); + const errors = SSMProvider.handleAnyInvalidGetParameterErrors( + result, + throwOnError + ); const response = this.transformAndCacheGetParametersResponse( result, parameters, @@ -585,7 +634,9 @@ class SSMProvider extends BaseProvider { let errors: string[] = []; // Fetch each possible batch param from cache and return if entire batch is cached - const { cached, toFetch } = await this.getParametersByNameFromCache(parameters); + const { cached, toFetch } = await this.getParametersByNameFromCache( + parameters + ); if (Object.keys(cached).length >= Object.keys(parameters).length) { response = cached; @@ -596,10 +647,8 @@ class SSMProvider extends BaseProvider { } // Slice batch by max permitted GetParameters call and retrieve the ones that are not cached - const { - response: batchResponse, - errors: batchErrors - } = await this.getParametersByNameInChunks(toFetch, throwOnError, decrypt); + const { response: batchResponse, errors: batchErrors } = + await this.getParametersByNameInChunks(toFetch, throwOnError, decrypt); response = { ...cached, ...batchResponse }; errors = batchErrors; @@ -620,12 +669,17 @@ class SSMProvider extends BaseProvider { const cached: Record> = {}; const toFetch: Record = {}; - for (const [ parameterName, parameterOptions ] of Object.entries(parameters)) { - const cacheKey = [ parameterName, parameterOptions.transform ].toString(); + for (const [parameterName, parameterOptions] of Object.entries( + parameters + )) { + const cacheKey = [parameterName, parameterOptions.transform].toString(); if (!this.hasKeyExpiredInCache(cacheKey)) { // Since we know the key exists in the cache, we can safely use the non-null assertion operator // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - cached[parameterName] = this.store.get(cacheKey)!.value as Record>; + cached[parameterName] = this.store.get(cacheKey)!.value as Record< + string, + string | Record + >; } else { toFetch[parameterName] = parameterOptions; } @@ -653,29 +707,26 @@ class SSMProvider extends BaseProvider { let errors: string[] = []; // Slice object into chunks of max permissible batch size - const chunks = Object.entries(parameters).reduce(( - acc, - [ parameterName, parameterOptions ], - index - ) => { - const chunkIndex = Math.floor(index / this.maxGetParametersItems); - if (!acc[chunkIndex]) { - acc[chunkIndex] = {}; - } - acc[chunkIndex][parameterName] = parameterOptions; + const chunks = Object.entries(parameters).reduce( + (acc, [parameterName, parameterOptions], index) => { + const chunkIndex = Math.floor(index / this.maxGetParametersItems); + if (!acc[chunkIndex]) { + acc[chunkIndex] = {}; + } + acc[chunkIndex][parameterName] = parameterOptions; - return acc; - }, [] as Record[]); + return acc; + }, + [] as Record[] + ); // Fetch each chunk and merge results for (const chunk of chunks) { - const { - response: chunkResponse, - errors: chunkErrors - } = await this._getParametersByName(chunk, throwOnError, decrypt); + const { response: chunkResponse, errors: chunkErrors } = + await this._getParametersByName(chunk, throwOnError, decrypt); response = { ...response, ...chunkResponse }; - errors = [ ...errors, ...chunkErrors ]; + errors = [...errors, ...chunkErrors]; } return { @@ -697,9 +748,14 @@ class SSMProvider extends BaseProvider { const response: Record = {}; const errors: string[] = []; - for (const [ parameterName, parameterOptions ] of Object.entries(parameters)) { + for (const [parameterName, parameterOptions] of Object.entries( + parameters + )) { try { - response[parameterName] = await this._get(parameterName, parameterOptions); + response[parameterName] = await this._get( + parameterName, + parameterOptions + ); } catch (error) { if (throwOnError) { throw error; @@ -743,9 +799,12 @@ class SSMProvider extends BaseProvider { sdkOptions?: GetParameterCommandInput | GetParametersByPathCommandInput ): boolean | undefined { if (options?.decrypt !== undefined) return options.decrypt; - if (sdkOptions?.WithDecryption !== undefined) return sdkOptions.WithDecryption; + if (sdkOptions?.WithDecryption !== undefined) + return sdkOptions.WithDecryption; if (this.envVarsService.getSSMDecrypt() !== '') { - return this.envVarsService.isValueTrue(this.envVarsService.getSSMDecrypt()); + return this.envVarsService.isValueTrue( + this.envVarsService.getSSMDecrypt() + ); } return undefined; @@ -761,17 +820,23 @@ class SSMProvider extends BaseProvider { parameters: Record, configs: SSMGetParametersByNameOptions ): SSMSplitBatchAndDecryptParametersOutputType { - const parametersToFetchInBatch: Record = {}; - const parametersToDecrypt: Record = {}; - - for (const [ parameterName, parameterOptions ] of Object.entries(parameters)) { + const parametersToFetchInBatch: Record< + string, + SSMGetParametersByNameOptions + > = {}; + const parametersToDecrypt: Record = + {}; + + for (const [parameterName, parameterOptions] of Object.entries( + parameters + )) { const overrides = parameterOptions; overrides.transform = overrides.transform || configs.transform; - overrides.decrypt = overrides.decrypt !== undefined ? - overrides.decrypt : configs.decrypt; - overrides.maxAge = overrides.maxAge !== undefined ? - overrides.maxAge : configs.maxAge; + overrides.decrypt = + overrides.decrypt !== undefined ? overrides.decrypt : configs.decrypt; + overrides.maxAge = + overrides.maxAge !== undefined ? overrides.maxAge : configs.maxAge; if (overrides.decrypt) { parametersToDecrypt[parameterName] = overrides; @@ -840,7 +905,7 @@ class SSMProvider extends BaseProvider { } if (value) { - const cacheKey = [ parameterName, parameterOptions.transform ].toString(); + const cacheKey = [parameterName, parameterOptions.transform].toString(); this.addToCache( cacheKey, value, @@ -855,7 +920,4 @@ class SSMProvider extends BaseProvider { } } -export { - SSMProvider, - DEFAULT_PROVIDERS, -}; \ No newline at end of file +export { SSMProvider, DEFAULT_PROVIDERS }; diff --git a/packages/parameters/src/ssm/getParameter.ts b/packages/parameters/src/ssm/getParameter.ts index ee46100113..8a5a468b69 100644 --- a/packages/parameters/src/ssm/getParameter.ts +++ b/packages/parameters/src/ssm/getParameter.ts @@ -142,20 +142,22 @@ import type { */ const getParameter = async < ExplicitUserProvidedType = undefined, - InferredFromOptionsType extends SSMGetOptionsUnion | undefined = SSMGetOptionsUnion + InferredFromOptionsType extends + | SSMGetOptionsUnion + | undefined = SSMGetOptionsUnion >( name: string, options?: InferredFromOptionsType & SSMGetOptions -): Promise | undefined> => { +): Promise< + SSMGetOutput | undefined +> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('ssm')) { DEFAULT_PROVIDERS.ssm = new SSMProvider(); } - return ( - DEFAULT_PROVIDERS.ssm as SSMProvider - ).get(name, options) as Promise | undefined>; + return (DEFAULT_PROVIDERS.ssm as SSMProvider).get(name, options) as Promise< + SSMGetOutput | undefined + >; }; -export { - getParameter, -}; \ No newline at end of file +export { getParameter }; diff --git a/packages/parameters/src/ssm/getParameters.ts b/packages/parameters/src/ssm/getParameters.ts index 94e786e7ab..bbf7a1c331 100644 --- a/packages/parameters/src/ssm/getParameters.ts +++ b/packages/parameters/src/ssm/getParameters.ts @@ -143,20 +143,27 @@ import type { */ const getParameters = async < ExplicitUserProvidedType = undefined, - InferredFromOptionsType extends SSMGetMultipleOptionsUnion | undefined = SSMGetMultipleOptionsUnion + InferredFromOptionsType extends + | SSMGetMultipleOptionsUnion + | undefined = SSMGetMultipleOptionsUnion >( path: string, options?: InferredFromOptionsType & SSMGetMultipleOptions -): Promise | undefined> => { +): Promise< + | SSMGetMultipleOutput + | undefined +> => { if (!DEFAULT_PROVIDERS.hasOwnProperty('ssm')) { DEFAULT_PROVIDERS.ssm = new SSMProvider(); } - return ( - DEFAULT_PROVIDERS.ssm as SSMProvider - ).getMultiple(path, options) as Promise | undefined>; + return (DEFAULT_PROVIDERS.ssm as SSMProvider).getMultiple( + path, + options + ) as Promise< + | SSMGetMultipleOutput + | undefined + >; }; -export { - getParameters, -}; \ No newline at end of file +export { getParameters }; diff --git a/packages/parameters/src/ssm/getParametersByName.ts b/packages/parameters/src/ssm/getParametersByName.ts index 587e7b2f99..7ba3483271 100644 --- a/packages/parameters/src/ssm/getParametersByName.ts +++ b/packages/parameters/src/ssm/getParametersByName.ts @@ -7,14 +7,14 @@ import type { /** * ## Intro * The Parameters utility provides an SSMProvider that allows to retrieve parameters from AWS Systems Manager. - * + * * ## Getting started - * + * * This utility supports AWS SDK v3 for JavaScript only. This allows the utility to be modular, and you to install only * the SDK packages you need and keep your bundle size small. - * + * * To use the provider, you must install the Parameters utility and the AWS SDK v3 for JavaScript for AppConfig: - * + * * ```sh * npm install @aws-lambda-powertools/parameters @aws-sdk/client-ssm * ``` @@ -24,7 +24,7 @@ import type { * @example * ```typescript * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; - * + * * export const handler = async (): Promise => { * // Retrieve parameters and cache them for 10 seconds * const parameters = await getParametersByName({ @@ -33,17 +33,17 @@ import type { * }); * }; * ``` - * + * * ## Advanced usage - * + * * ### Decryption - * + * * If you have encrypted parameters, you can use the `decrypt` option to automatically decrypt them. - * + * * @example * ```typescript * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; - * + * * export const handler = async (): Promise => { * // Retrieve parameters and decrypt them * const parameters = await getParametersByName({ @@ -52,16 +52,16 @@ import type { * }, { decrypt: true }); * }; * ``` - * + * * ### Caching - * + * * By default, the provider will cache parameters retrieved in-memory for 5 seconds. * You can adjust how long values should be kept in cache by using the `maxAge` parameter. - * + * * @example * ```typescript * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; - * + * * export const handler = async (): Promise => { * // Retrieve parameters and cache them for 10 seconds * const parameters = await getParametersByName({ @@ -70,13 +70,13 @@ import type { * }, { maxAge: 10 }); * }; * ``` - * + * * Alternatively, if you need more granular control over caching each parameter, you can pass it in the options object. - * + * * @example * ```typescript * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; - * + * * export const handler = async (): Promise => { * // Retrieve parameters and cache them individually * const parameters = await getParametersByName({ @@ -85,13 +85,13 @@ import type { * }); * }; * ``` - * + * * If instead you'd like to always ensure you fetch the latest values from the store regardless if already available in cache, use the `forceFetch` parameter. - * + * * @example * ```typescript * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; - * + * * export const handler = async (): Promise => { * // Retrieve parameters and pass extra options to skip cache * const parameters = await getParametersByName({ @@ -100,46 +100,46 @@ import type { * }, { forceFetch: true }); * }; * ``` - * + * * ### Transformations - * + * * For parameters stored as JSON you can use the transform argument for deserialization. This will return a JavaScript objects instead of a strings. * For parameters that are instead stored as base64-encoded binary data, you can use the transform argument set to `binary` for decoding. This will return decoded strings for each parameter. - * + * * @example * ```typescript * import { getParametersByName } from '@aws-lambda-powertools/parameters/ssm'; - * + * * export const handler = async (): Promise => { * // Retrieve parameters and pass extra options to transform them * const parameters = await getParametersByName({ * '/my-parameter-1': {}, // Use default options (no transformation) - * '/my-parameter-2': { transform: 'json' }, // Parse the value as JSON - * '/my-parameter-3': { transform: 'binary' }, // Parse the value as base64-encoded binary data + * '/my-parameter-2': { transform: 'json' }, // Parse the value as JSON + * '/my-parameter-3': { transform: 'binary' }, // Parse the value as base64-encoded binary data * }); * }; * ``` - * - * + * + * * ### Built-in provider class * * For greater flexibility such as configuring the underlying SDK client used by built-in providers, you can use the {@link SSMProvider} class. - * + * * ### Options - * + * * * You can customize the retrieval of the value by passing options to **both the function and the parameter**: * * `maxAge` - The maximum age of the value in cache before fetching a new one (in seconds) (default: 5) * * `forceFetch` - Whether to always fetch a new value from the store regardless if already available in cache * * `transform` - Whether to transform the value before returning it. Supported values: `json`, `binary` * * `sdkOptions` - Extra options to pass to the AWS SDK v3 for JavaScript client * * `decrypt` - Whether to decrypt the value before returning it - * + * * `throwOnError` decides whether to throw an error if a parameter is not found: * - A) Default fail-fast behavior: Throws a `GetParameterError` error upon any failure. * - B) Gracefully aggregate all parameters that failed under "_errors" key. - * + * * It transparently uses GetParameter and/or getParametersByName depending on decryption requirements. - * + * * ```sh * ┌────────────────────────┐ * ┌───▶ Decrypt entire batch │─────┐ @@ -154,9 +154,9 @@ import type { * └────────────────────────┘ │ getParametersByName API │ * └────────────────────┘ * ``` - * + * * For more usage examples, see [our documentation](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/). - * + * * @param {Record} parameters - The path of the parameters to retrieve * @param {SSMGetParametersByNameOptions} options - Options to configure the provider * @see https://awslabs.github.io/aws-lambda-powertools-typescript/latest/utilities/parameters/ @@ -169,11 +169,10 @@ const getParametersByName = async ( DEFAULT_PROVIDERS.ssm = new SSMProvider(); } - return ( - DEFAULT_PROVIDERS.ssm as SSMProvider - ).getParametersByName(parameters, options) as Promise>; + return (DEFAULT_PROVIDERS.ssm as SSMProvider).getParametersByName( + parameters, + options + ) as Promise>; }; -export { - getParametersByName, -}; \ No newline at end of file +export { getParametersByName }; diff --git a/packages/parameters/src/ssm/index.ts b/packages/parameters/src/ssm/index.ts index e275be6d78..2efdcd6952 100644 --- a/packages/parameters/src/ssm/index.ts +++ b/packages/parameters/src/ssm/index.ts @@ -1,4 +1,4 @@ export * from './SSMProvider'; export * from './getParameter'; export * from './getParameters'; -export * from './getParametersByName'; \ No newline at end of file +export * from './getParametersByName'; diff --git a/packages/parameters/src/types/AppConfigProvider.ts b/packages/parameters/src/types/AppConfigProvider.ts index c9005b95c3..61e3ee03cf 100644 --- a/packages/parameters/src/types/AppConfigProvider.ts +++ b/packages/parameters/src/types/AppConfigProvider.ts @@ -13,8 +13,8 @@ import type { GetOptionsInterface } from './BaseProvider'; * @property {string} [application] - The application ID or the application name. */ interface AppConfigProviderOptionsBaseInterface { - environment: string - application?: string + environment: string; + application?: string; } /** @@ -25,9 +25,10 @@ interface AppConfigProviderOptionsBaseInterface { * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. * @property {never} [awsSdkV3Client] - This property should never be passed. */ -interface AppConfigProviderOptionsWithClientConfig extends AppConfigProviderOptionsBaseInterface { - clientConfig?: AppConfigDataClientConfig - awsSdkV3Client?: never +interface AppConfigProviderOptionsWithClientConfig + extends AppConfigProviderOptionsBaseInterface { + clientConfig?: AppConfigDataClientConfig; + awsSdkV3Client?: never; } /** @@ -38,9 +39,10 @@ interface AppConfigProviderOptionsWithClientConfig extends AppConfigProviderOpti * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation * @property {never} [clientConfig] - This property should never be passed. */ -interface AppConfigProviderOptionsWithClientInstance extends AppConfigProviderOptionsBaseInterface { - awsSdkV3Client?: AppConfigDataClient - clientConfig?: never +interface AppConfigProviderOptionsWithClientInstance + extends AppConfigProviderOptionsBaseInterface { + awsSdkV3Client?: AppConfigDataClient; + clientConfig?: never; } /** @@ -52,7 +54,9 @@ interface AppConfigProviderOptionsWithClientInstance extends AppConfigProviderOp * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation. Mutually exclusive with clientConfig. */ -type AppConfigProviderOptions = AppConfigProviderOptionsWithClientConfig | AppConfigProviderOptionsWithClientInstance; +type AppConfigProviderOptions = + | AppConfigProviderOptionsWithClientConfig + | AppConfigProviderOptionsWithClientInstance; /** * Options for the AppConfigProvider get method. @@ -64,12 +68,13 @@ type AppConfigProviderOptions = AppConfigProviderOptionsWithClientConfig | AppCo * @property {StartConfigurationSessionCommandInput} [sdkOptions] - Additional options to pass to the AWS SDK v3 client. * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. */ -interface AppConfigGetOptionsInterface extends Omit { +interface AppConfigGetOptionsInterface + extends Omit { sdkOptions?: Omit< - Partial, - | 'ApplicationIdentifier' - | 'EnvironmentIdentifier | ConfigurationProfileIdentifier' - > + Partial, + | 'ApplicationIdentifier' + | 'EnvironmentIdentifier | ConfigurationProfileIdentifier' + >; } /** @@ -80,7 +85,7 @@ interface AppConfigGetOptionsInterface extends Omit, - AppConfigGetOptionsInterface {} + AppConfigGetOptionsInterface {} export type { AppConfigProviderOptions, diff --git a/packages/parameters/src/types/BaseProvider.ts b/packages/parameters/src/types/BaseProvider.ts index f2a6a79a9b..a743b4e375 100644 --- a/packages/parameters/src/types/BaseProvider.ts +++ b/packages/parameters/src/types/BaseProvider.ts @@ -15,19 +15,19 @@ interface GetOptionsInterface { /** * Maximum age of the value in the cache, in seconds. */ - maxAge?: number + maxAge?: number; /** * Force fetch the value from the parameter store, ignoring the cache. */ - forceFetch?: boolean + forceFetch?: boolean; /** * Options to pass to the underlying SDK. */ - sdkOptions?: unknown + sdkOptions?: unknown; /** * Transform to be applied, can be `json` or `binary`. */ - transform?: Omit + transform?: Omit; } /** @@ -43,11 +43,11 @@ interface GetMultipleOptionsInterface extends GetOptionsInterface { /** * Transform to be applied, can be `json`, `binary`, or `auto`. */ - transform?: TransformOptions + transform?: TransformOptions; /** * Whether to throw an error if a value cannot be transformed. */ - throwOnTransformError?: boolean + throwOnTransformError?: boolean; } /** @@ -57,19 +57,25 @@ interface ExpirableValueInterface { /** * Value of the parameter. */ - value: string | Uint8Array | Record + value: string | Uint8Array | Record; /** * Expiration timestamp of the value. */ - ttl: number + ttl: number; } /** * Interface for a parameter store provider. */ interface BaseProviderInterface { - get(name: string, options?: GetOptionsInterface): Promise> - getMultiple(path: string, options?: GetMultipleOptionsInterface): Promise> + get( + name: string, + options?: GetOptionsInterface + ): Promise>; + getMultiple( + path: string, + options?: GetMultipleOptionsInterface + ): Promise>; } export type { @@ -78,4 +84,4 @@ export type { BaseProviderInterface, ExpirableValueInterface, TransformOptions, -}; \ No newline at end of file +}; diff --git a/packages/parameters/src/types/DynamoDBProvider.ts b/packages/parameters/src/types/DynamoDBProvider.ts index 075f123838..24914ccf58 100644 --- a/packages/parameters/src/types/DynamoDBProvider.ts +++ b/packages/parameters/src/types/DynamoDBProvider.ts @@ -1,5 +1,13 @@ -import type { GetOptionsInterface, GetMultipleOptionsInterface } from './BaseProvider'; -import type { DynamoDBClient, GetItemCommandInput, QueryCommandInput, DynamoDBClientConfig } from '@aws-sdk/client-dynamodb'; +import type { + GetOptionsInterface, + GetMultipleOptionsInterface, +} from './BaseProvider'; +import type { + DynamoDBClient, + GetItemCommandInput, + QueryCommandInput, + DynamoDBClientConfig, +} from '@aws-sdk/client-dynamodb'; /** * Base interface for DynamoDBProviderOptions. @@ -11,10 +19,10 @@ import type { DynamoDBClient, GetItemCommandInput, QueryCommandInput, DynamoDBCl * @property {string} [valueAttr] - The DynamoDB table value attribute name. Defaults to 'value'. */ interface DynamoDBProviderOptionsBaseInterface { - tableName: string - keyAttr?: string - sortAttr?: string - valueAttr?: string + tableName: string; + keyAttr?: string; + sortAttr?: string; + valueAttr?: string; } /** @@ -25,9 +33,10 @@ interface DynamoDBProviderOptionsBaseInterface { * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. * @property {never} [awsSdkV3Client] - This property should never be passed. */ -interface DynamoDBProviderOptionsWithClientConfig extends DynamoDBProviderOptionsBaseInterface { - clientConfig?: DynamoDBClientConfig - awsSdkV3Client?: never +interface DynamoDBProviderOptionsWithClientConfig + extends DynamoDBProviderOptionsBaseInterface { + clientConfig?: DynamoDBClientConfig; + awsSdkV3Client?: never; } /** @@ -38,9 +47,10 @@ interface DynamoDBProviderOptionsWithClientConfig extends DynamoDBProviderOption * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during AppConfigProvider class instantiation * @property {never} [clientConfig] - This property should never be passed. */ -interface DynamoDBProviderOptionsWithClientInstance extends DynamoDBProviderOptionsBaseInterface { - awsSdkV3Client?: DynamoDBClient - clientConfig?: never +interface DynamoDBProviderOptionsWithClientInstance + extends DynamoDBProviderOptionsBaseInterface { + awsSdkV3Client?: DynamoDBClient; + clientConfig?: never; } /** @@ -54,7 +64,9 @@ interface DynamoDBProviderOptionsWithClientInstance extends DynamoDBProviderOpti * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. */ -type DynamoDBProviderOptions = DynamoDBProviderOptionsWithClientConfig | DynamoDBProviderOptionsWithClientInstance; +type DynamoDBProviderOptions = + | DynamoDBProviderOptionsWithClientConfig + | DynamoDBProviderOptionsWithClientInstance; /** * Options for the DynamoDBProvider get method. @@ -67,7 +79,10 @@ type DynamoDBProviderOptions = DynamoDBProviderOptionsWithClientConfig | DynamoD * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. */ interface DynamoDBGetOptionsInterface extends GetOptionsInterface { - sdkOptions?: Omit, 'Key' | 'TableName' | 'ProjectionExpression'> + sdkOptions?: Omit< + Partial, + 'Key' | 'TableName' | 'ProjectionExpression' + >; } /** @@ -81,12 +96,13 @@ interface DynamoDBGetOptionsInterface extends GetOptionsInterface { * @property {TransformOptions} transform - Transform to be applied, can be 'json' or 'binary'. * @property {boolean} throwOnTransformError - Whether to throw an error if the transform fails (default: `true`) */ -interface DynamoDBGetMultipleOptionsInterface extends GetMultipleOptionsInterface { - sdkOptions?: Partial +interface DynamoDBGetMultipleOptionsInterface + extends GetMultipleOptionsInterface { + sdkOptions?: Partial; } export type { DynamoDBProviderOptions, DynamoDBGetOptionsInterface, DynamoDBGetMultipleOptionsInterface, -}; \ No newline at end of file +}; diff --git a/packages/parameters/src/types/SSMProvider.ts b/packages/parameters/src/types/SSMProvider.ts index dddf071e60..dd505a6019 100644 --- a/packages/parameters/src/types/SSMProvider.ts +++ b/packages/parameters/src/types/SSMProvider.ts @@ -2,12 +2,12 @@ import type { SSMClient, SSMClientConfig, GetParameterCommandInput, - GetParametersByPathCommandInput + GetParametersByPathCommandInput, } from '@aws-sdk/client-ssm'; import type { GetOptionsInterface, GetMultipleOptionsInterface, - TransformOptions + TransformOptions, } from './BaseProvider'; /** @@ -18,8 +18,8 @@ import type { * @property {never} [awsSdkV3Client] - This property should never be passed. */ interface SSMProviderOptionsWithClientConfig { - clientConfig?: SSMClientConfig - awsSdkV3Client?: never + clientConfig?: SSMClientConfig; + awsSdkV3Client?: never; } /** @@ -30,8 +30,8 @@ interface SSMProviderOptionsWithClientConfig { * @property {never} [clientConfig] - This property should never be passed. */ interface SSMProviderOptionsWithClientInstance { - awsSdkV3Client?: SSMClient - clientConfig?: never + awsSdkV3Client?: SSMClient; + clientConfig?: never; } /** @@ -41,7 +41,9 @@ interface SSMProviderOptionsWithClientInstance { * @property {SSMClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. * @property {SSMClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during DynamoDBProvider class instantiation. Mutually exclusive with clientConfig. */ -type SSMProviderOptions = SSMProviderOptionsWithClientConfig | SSMProviderOptionsWithClientInstance; +type SSMProviderOptions = + | SSMProviderOptionsWithClientConfig + | SSMProviderOptionsWithClientInstance; /** * Options for the SSMProvider getMultiple method. @@ -58,43 +60,50 @@ interface SSMGetOptions extends GetOptionsInterface { /** * If true, the parameter will be decrypted. Defaults to `false`. */ - decrypt?: boolean + decrypt?: boolean; /** * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetParameterCommandInput`. */ - sdkOptions?: Partial + sdkOptions?: Partial; - transform?: Exclude + transform?: Exclude; } interface SSMGetOptionsTransformJson extends SSMGetOptions { - transform: 'json' + transform: 'json'; } interface SSMGetOptionsTransformBinary extends SSMGetOptions { - transform: 'binary' + transform: 'binary'; } interface SSMGetOptionsTransformNone extends SSMGetOptions { - transform?: never + transform?: never; } type SSMGetOptionsUnion = - SSMGetOptionsTransformJson | - SSMGetOptionsTransformBinary | - SSMGetOptionsTransformNone | - undefined; + | SSMGetOptionsTransformJson + | SSMGetOptionsTransformBinary + | SSMGetOptionsTransformNone + | undefined; /** * Generic output type for the SSMProvider get method. */ -type SSMGetOutput = - undefined extends ExplicitUserProvidedType ? - undefined extends InferredFromOptionsType ? string : - InferredFromOptionsType extends SSMGetOptionsTransformNone | SSMGetOptionsTransformBinary ? string : - InferredFromOptionsType extends SSMGetOptionsTransformJson ? Record : - never - : ExplicitUserProvidedType; +type SSMGetOutput< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType = undefined +> = undefined extends ExplicitUserProvidedType + ? undefined extends InferredFromOptionsType + ? string + : InferredFromOptionsType extends + | SSMGetOptionsTransformNone + | SSMGetOptionsTransformBinary + ? string + : InferredFromOptionsType extends SSMGetOptionsTransformJson + ? Record + : never + : ExplicitUserProvidedType; /** * Options for the SSMProvider getMultiple method. @@ -113,55 +122,63 @@ interface SSMGetMultipleOptions extends GetMultipleOptionsInterface { /** * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetParametersByPathCommandInput`. */ - sdkOptions?: Partial + sdkOptions?: Partial; /** * If true, the parameters will be decrypted. Defaults to `false`. */ - decrypt?: boolean + decrypt?: boolean; /** * If true, the parameters will be fetched recursively. Defaults to `false`. */ - recursive?: boolean + recursive?: boolean; /** * If true, the method will throw an error if the transform fails. */ - throwOnTransformError?: boolean + throwOnTransformError?: boolean; } interface SSMGetMultipleOptionsTransformJson extends SSMGetMultipleOptions { - transform: 'json' + transform: 'json'; } interface SSMGetMultipleOptionsTransformBinary extends SSMGetMultipleOptions { - transform: 'binary' + transform: 'binary'; } interface SSMGetMultipleOptionsTransformAuto extends SSMGetMultipleOptions { - transform: 'auto' + transform: 'auto'; } interface SSMGetMultipleOptionsTransformNone extends SSMGetMultipleOptions { - transform?: never + transform?: never; } -type SSMGetMultipleOptionsUnion = - SSMGetMultipleOptionsTransformJson | - SSMGetMultipleOptionsTransformBinary | - SSMGetMultipleOptionsTransformAuto | - SSMGetMultipleOptionsTransformNone | - undefined; +type SSMGetMultipleOptionsUnion = + | SSMGetMultipleOptionsTransformJson + | SSMGetMultipleOptionsTransformBinary + | SSMGetMultipleOptionsTransformAuto + | SSMGetMultipleOptionsTransformNone + | undefined; /** * Generic output type for the SSMProvider getMultiple method. */ -type SSMGetMultipleOutput = - undefined extends ExplicitUserProvidedType ? - undefined extends InferredFromOptionsType ? Record : - InferredFromOptionsType extends SSMGetMultipleOptionsTransformNone | SSMGetMultipleOptionsTransformBinary ? Record : - InferredFromOptionsType extends SSMGetMultipleOptionsTransformAuto ? Record : - InferredFromOptionsType extends SSMGetMultipleOptionsTransformJson ? Record> : - never - : Record; +type SSMGetMultipleOutput< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType = undefined +> = undefined extends ExplicitUserProvidedType + ? undefined extends InferredFromOptionsType + ? Record + : InferredFromOptionsType extends + | SSMGetMultipleOptionsTransformNone + | SSMGetMultipleOptionsTransformBinary + ? Record + : InferredFromOptionsType extends SSMGetMultipleOptionsTransformAuto + ? Record + : InferredFromOptionsType extends SSMGetMultipleOptionsTransformJson + ? Record> + : never + : Record; /** * Options for the SSMProvider getParametersByName method. @@ -173,43 +190,43 @@ type SSMGetMultipleOutput + maxAge?: number; + throwOnError?: boolean; + decrypt?: boolean; + transform?: Exclude; } /** * Output type for the SSMProvider splitBatchAndDecryptParameters method. */ type SSMSplitBatchAndDecryptParametersOutputType = { - parametersToFetchInBatch: Record - parametersToDecrypt: Record + parametersToFetchInBatch: Record; + parametersToDecrypt: Record; }; /** * Output type for the SSMProvider getParametersByName method. */ interface SSMGetParametersByNameOutputInterface { - response: Record - errors: string[] + response: Record; + errors: string[]; } /** * Output type for the SSMProvider getParametersByNameFromCache method. */ type SSMGetParametersByNameFromCacheOutputType = { - cached: Record> - toFetch: Record + cached: Record>; + toFetch: Record; }; /** * Generic output type for the SSMProvider getParametersByName method. */ -type SSMGetParametersByNameOutput = - undefined extends InferredFromOptionsType ? - Record & { _errors?: string[] } : - Record & { _errors?: string[] }; +type SSMGetParametersByNameOutput = + undefined extends InferredFromOptionsType + ? Record & { _errors?: string[] } + : Record & { _errors?: string[] }; export type { SSMProviderOptions, @@ -224,4 +241,4 @@ export type { SSMGetParametersByNameOutputInterface, SSMGetParametersByNameFromCacheOutputType, SSMGetParametersByNameOutput, -}; \ No newline at end of file +}; diff --git a/packages/parameters/src/types/SecretsProvider.ts b/packages/parameters/src/types/SecretsProvider.ts index 07c23c82bc..1105d016c1 100644 --- a/packages/parameters/src/types/SecretsProvider.ts +++ b/packages/parameters/src/types/SecretsProvider.ts @@ -1,11 +1,8 @@ -import type { - GetOptionsInterface, - TransformOptions -} from './BaseProvider'; +import type { GetOptionsInterface, TransformOptions } from './BaseProvider'; import type { SecretsManagerClient, SecretsManagerClientConfig, - GetSecretValueCommandInput + GetSecretValueCommandInput, } from '@aws-sdk/client-secrets-manager'; /** @@ -16,8 +13,8 @@ import type { * @property {never} [awsSdkV3Client] - This property should never be passed. */ interface SecretsProviderOptionsWithClientConfig { - clientConfig?: SecretsManagerClientConfig - awsSdkV3Client?: never + clientConfig?: SecretsManagerClientConfig; + awsSdkV3Client?: never; } /** @@ -29,8 +26,8 @@ interface SecretsProviderOptionsWithClientConfig { * @property {never} [clientConfig] - This property should never be passed. */ interface SecretsProviderOptionsWithClientInstance { - awsSdkV3Client?: SecretsManagerClient - clientConfig?: never + awsSdkV3Client?: SecretsManagerClient; + clientConfig?: never; } /** @@ -40,11 +37,13 @@ interface SecretsProviderOptionsWithClientInstance { * @property {AppConfigDataClientConfig} [clientConfig] - Optional configuration to pass during client initialization, e.g. AWS region. Mutually exclusive with awsSdkV3Client. * @property {AppConfigDataClient} [awsSdkV3Client] - Optional AWS SDK v3 client to pass during SecretsProvider class instantiation. Mutually exclusive with clientConfig. */ -type SecretsProviderOptions = SecretsProviderOptionsWithClientConfig | SecretsProviderOptionsWithClientInstance; +type SecretsProviderOptions = + | SecretsProviderOptionsWithClientConfig + | SecretsProviderOptionsWithClientInstance; /** * Options to configure the retrieval of a secret. - * + * * @interface SecretsGetOptionsInterface * @extends {GetOptionsInterface} * @property {number} maxAge - Maximum age of the value in the cache, in seconds. @@ -56,43 +55,49 @@ interface SecretsGetOptions extends GetOptionsInterface { /** * Additional options to pass to the AWS SDK v3 client. Supports all options from `GetSecretValueCommandInput`. */ - sdkOptions?: Omit, 'SecretId'> - transform?: Exclude + sdkOptions?: Omit, 'SecretId'>; + transform?: Exclude; } interface SecretsGetOptionsTransformJson extends SecretsGetOptions { - transform: 'json' + transform: 'json'; } interface SecretsGetOptionsTransformBinary extends SecretsGetOptions { - transform: 'binary' + transform: 'binary'; } interface SecretsGetOptionsTransformNone extends SecretsGetOptions { - transform?: never + transform?: never; } type SecretsGetOptionsUnion = - SecretsGetOptionsTransformNone | - SecretsGetOptionsTransformJson | - SecretsGetOptionsTransformBinary | - undefined; + | SecretsGetOptionsTransformNone + | SecretsGetOptionsTransformJson + | SecretsGetOptionsTransformBinary + | undefined; /** * Generic output type for the SecretsProvider get method. */ -type SecretsGetOutput = - undefined extends ExplicitUserProvidedType ? - undefined extends InferredFromOptionsType ? string | Uint8Array : - InferredFromOptionsType extends SecretsGetOptionsTransformNone ? string | Uint8Array : - InferredFromOptionsType extends SecretsGetOptionsTransformBinary ? string : - InferredFromOptionsType extends SecretsGetOptionsTransformJson ? Record : - never - : ExplicitUserProvidedType; +type SecretsGetOutput< + ExplicitUserProvidedType = undefined, + InferredFromOptionsType = undefined +> = undefined extends ExplicitUserProvidedType + ? undefined extends InferredFromOptionsType + ? string | Uint8Array + : InferredFromOptionsType extends SecretsGetOptionsTransformNone + ? string | Uint8Array + : InferredFromOptionsType extends SecretsGetOptionsTransformBinary + ? string + : InferredFromOptionsType extends SecretsGetOptionsTransformJson + ? Record + : never + : ExplicitUserProvidedType; export type { SecretsProviderOptions, SecretsGetOptions, SecretsGetOutput, SecretsGetOptionsUnion, -}; \ No newline at end of file +}; diff --git a/packages/parameters/src/types/index.ts b/packages/parameters/src/types/index.ts index 7bab8dc817..18f961f63c 100644 --- a/packages/parameters/src/types/index.ts +++ b/packages/parameters/src/types/index.ts @@ -2,4 +2,4 @@ export * from './BaseProvider'; export * from './AppConfigProvider'; export * from './SSMProvider'; export * from './SecretsProvider'; -export * from './DynamoDBProvider'; \ No newline at end of file +export * from './DynamoDBProvider'; diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts index 3633b7d5d6..8fe93a77ee 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.functionCode.ts @@ -1,10 +1,6 @@ import { Context } from 'aws-lambda'; -import { - AppConfigProvider, -} from '../../src/appconfig'; -import { - AppConfigGetOptionsInterface, -} from '../../src/types'; +import { AppConfigProvider } from '../../src/appconfig'; +import { AppConfigGetOptionsInterface } from '../../src/types'; import { TinyLogger } from '../helpers/tinyLogger'; import { middleware } from '../helpers/sdkMiddlewareRequestCounter'; import { AppConfigDataClient } from '@aws-sdk/client-appconfigdata'; @@ -16,14 +12,15 @@ const application = process.env.APPLICATION_NAME || 'my-app'; const environment = process.env.ENVIRONMENT_NAME || 'my-env'; const freeFormJsonName = process.env.FREEFORM_JSON_NAME || 'freeform-json'; const freeFormYamlName = process.env.FREEFORM_YAML_NAME || 'freeform-yaml'; -const freeFormBase64encodedPlainText = process.env.FREEFORM_BASE64_ENCODED_PLAIN_TEXT_NAME || 'freeform-plain-text'; +const freeFormBase64encodedPlainText = + process.env.FREEFORM_BASE64_ENCODED_PLAIN_TEXT_NAME || 'freeform-plain-text'; const featureFlagName = process.env.FEATURE_FLAG_NAME || 'feature-flag'; const defaultProvider = new AppConfigProvider({ application, environment, }); -// Provider test +// Provider test const customClient = new AppConfigDataClient({}); customClient.middlewareStack.use(middleware); const providerWithMiddleware = new AppConfigProvider({ @@ -45,7 +42,7 @@ const _call_get = async ( paramName: string, testName: string, options?: AppConfigGetOptionsInterface, - provider?: AppConfigProvider, + provider?: AppConfigProvider ): Promise => { try { const currentProvider = resolveProvider(provider); @@ -53,28 +50,39 @@ const _call_get = async ( const parameterValue = await currentProvider.get(paramName, options); logger.log({ test: testName, - value: parameterValue + value: parameterValue, }); } catch (err) { logger.log({ test: testName, - error: err.message + error: err.message, }); } }; -export const handler = async (_event: unknown, _context: Context): Promise => { +export const handler = async ( + _event: unknown, + _context: Context +): Promise => { // Test 1 - get a single parameter as-is (no transformation - should return an Uint8Array) await _call_get(freeFormYamlName, 'get'); // Test 2 - get a free-form JSON and apply json transformation (should return an object) - await _call_get(freeFormJsonName, 'get-freeform-json-binary', { transform: 'json' }); + await _call_get(freeFormJsonName, 'get-freeform-json-binary', { + transform: 'json', + }); // Test 3 - get a free-form base64-encoded plain text and apply binary transformation (should return a decoded string) - await _call_get(freeFormBase64encodedPlainText, 'get-freeform-base64-plaintext-binary', { transform: 'binary' }); + await _call_get( + freeFormBase64encodedPlainText, + 'get-freeform-base64-plaintext-binary', + { transform: 'binary' } + ); // Test 4 - get a feature flag and apply json transformation (should return an object) - await _call_get(featureFlagName, 'get-feature-flag-binary', { transform: 'json' }); + await _call_get(featureFlagName, 'get-feature-flag-binary', { + transform: 'json', + }); // Test 5 // get parameter twice with middleware, which counts the number of requests, we check later if we only called AppConfig API once @@ -85,12 +93,12 @@ export const handler = async (_event: unknown, _context: Context): Promise await providerWithMiddleware.get(freeFormBase64encodedPlainText); logger.log({ test: 'get-cached', - value: middleware.counter // should be 1 + value: middleware.counter, // should be 1 }); } catch (err) { logger.log({ test: 'get-cached', - error: err.message + error: err.message, }); } @@ -100,15 +108,17 @@ export const handler = async (_event: unknown, _context: Context): Promise providerWithMiddleware.clearCache(); middleware.counter = 0; await providerWithMiddleware.get(freeFormBase64encodedPlainText); - await providerWithMiddleware.get(freeFormBase64encodedPlainText, { forceFetch: true }); + await providerWithMiddleware.get(freeFormBase64encodedPlainText, { + forceFetch: true, + }); logger.log({ test: 'get-forced', - value: middleware.counter // should be 2 + value: middleware.counter, // should be 2 }); } catch (err) { logger.log({ test: 'get-forced', - error: err.message + error: err.message, }); } // Test 7 @@ -117,15 +127,17 @@ export const handler = async (_event: unknown, _context: Context): Promise providerWithMiddleware.clearCache(); middleware.counter = 0; const expiredResult1 = await providerWithMiddleware.get( - freeFormBase64encodedPlainText, { - maxAge: 0, - transform: 'base64' + freeFormBase64encodedPlainText, + { + maxAge: 0, + transform: 'base64', } ); const expiredResult2 = await providerWithMiddleware.get( - freeFormBase64encodedPlainText, { - maxAge: 0, - transform: 'base64' + freeFormBase64encodedPlainText, + { + maxAge: 0, + transform: 'base64', } ); logger.log({ @@ -133,13 +145,13 @@ export const handler = async (_event: unknown, _context: Context): Promise value: { counter: middleware.counter, // should be 2 result1: expiredResult1, - result2: expiredResult2 + result2: expiredResult2, }, }); } catch (err) { logger.log({ test: 'get-expired', - error: err.message + error: err.message, }); } -}; \ No newline at end of file +}; diff --git a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts index 26b6210ea2..de99890c58 100644 --- a/packages/parameters/tests/e2e/appConfigProvider.class.test.ts +++ b/packages/parameters/tests/e2e/appConfigProvider.class.test.ts @@ -1,26 +1,29 @@ /** - * Test AppConfigProvider class - * - * @group e2e/parameters/appconfig/class - */ + * Test AppConfigProvider class + * + * @group e2e/parameters/appconfig/class + */ import path from 'path'; import { App, Stack, Aspects } from 'aws-cdk-lib'; import { toBase64 } from '@aws-sdk/util-base64-node'; import { v4 } from 'uuid'; -import { - generateUniqueName, - isValidRuntimeKey, - createStackWithLambdaFunction, - invokeFunction, +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, } from '../../../commons/tests/utils/e2eUtils'; import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { + deployStack, + destroyStack, +} from '../../../commons/tests/utils/cdk-cli'; import { ResourceAccessGranter } from '../helpers/cdkAspectGrantAccess'; -import { - RESOURCE_NAME_PREFIX, - SETUP_TIMEOUT, - TEARDOWN_TIMEOUT, - TEST_CASE_TIMEOUT +import { + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT, } from './constants'; import { createBaseAppConfigResources, @@ -34,19 +37,64 @@ if (!isValidRuntimeKey(runtime)) { } const uuid = v4(); -const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'appConfigProvider'); -const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'appConfigProvider'); +const stackName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'appConfigProvider' +); +const functionName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'appConfigProvider' +); const lambdaFunctionCodeFile = 'appConfigProvider.class.test.functionCode.ts'; const invocationCount = 1; -const applicationName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'app'); -const environmentName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'env'); -const deploymentStrategyName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'immediate'); -const freeFormJsonName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'freeFormJson'); -const freeFormYamlName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'freeFormYaml'); -const freeFormBase64PlainTextName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'freeFormBase64PlainText'); -const featureFlagName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'featureFlag'); +const applicationName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'app' +); +const environmentName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'env' +); +const deploymentStrategyName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'immediate' +); +const freeFormJsonName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'freeFormJson' +); +const freeFormYamlName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'freeFormYaml' +); +const freeFormBase64PlainTextName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'freeFormBase64PlainText' +); +const featureFlagName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'featureFlag' +); const freeFormJsonValue = { foo: 'bar', @@ -54,19 +102,21 @@ const freeFormJsonValue = { const freeFormYamlValue = `foo: bar `; const freeFormPlainTextValue = 'foo'; -const freeFormBase64PlainTextValue = toBase64(new TextEncoder().encode(freeFormPlainTextValue)); +const freeFormBase64PlainTextValue = toBase64( + new TextEncoder().encode(freeFormPlainTextValue) +); const featureFlagValue = { version: '1', flags: { myFeatureFlag: { - 'name': 'myFeatureFlag', - } + name: 'myFeatureFlag', + }, }, values: { myFeatureFlag: { enabled: true, - } - } + }, + }, }; const integTestApp = new App(); @@ -76,56 +126,55 @@ let stack: Stack; * This test suite deploys a CDK stack with a Lambda function and a number of AppConfig parameters. * The function code uses the Parameters utility to retrieve the parameters. * It then logs the values to CloudWatch Logs as JSON objects. - * + * * Once the stack is deployed, the Lambda function is invoked and the CloudWatch Logs are retrieved. * The logs are then parsed and the values are checked against the expected values for each test case. - * + * * The stack creates an AppConfig application and environment, and then creates a number configuration * profiles, each with a different type of parameter. - * + * * The parameters created are: * - Free-form JSON * - Free-form YAML * - Free-form plain text base64-encoded string * - Feature flag - * + * * These parameters allow to retrieve the values and test some transformations. - * + * * The tests are: - * + * * Test 1 * get a single parameter as-is (no transformation - should return an Uint8Array) - * + * * Test 2 * get a free-form JSON and apply json transformation (should return an object) - * + * * Test 3 * get a free-form base64-encoded plain text and apply binary transformation (should return a decoded string) - * + * * Test 4 * get a feature flag and apply json transformation (should return an object) - * + * * Test 5 - * get parameter twice with middleware, which counts the number of requests, + * get parameter twice with middleware, which counts the number of requests, * we check later if we only called AppConfig API once - * + * * Test 6 * get parameter twice, but force fetch 2nd time, we count number of SDK requests and * check that we made two API calls * check that we got matching results - * + * * Test 7 * get parameter twice, using maxAge to avoid primary cache * we count number of SDK requests and check that we made two API calls * and check that the values match - * + * * Note: To avoid race conditions, we add a dependency between each pair of configuration profiles. * This allows us to influence the order of creation and ensure that each configuration profile * is created after the previous one. This is necessary because we share the same AppConfig * application and environment for all tests. */ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () => { - let invocationLogs: InvocationLogs[]; const encoder = new TextEncoder(); @@ -151,16 +200,13 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = }); // Create the base resources for an AppConfig application. - const { - application, - environment, - deploymentStrategy - } = createBaseAppConfigResources({ - stack, - applicationName, - environmentName, - deploymentStrategyName, - }); + const { application, environment, deploymentStrategy } = + createBaseAppConfigResources({ + stack, + applicationName, + environmentName, + deploymentStrategyName, + }); // Create configuration profiles for tests. const freeFormJson = createAppConfigConfigurationProfile({ @@ -173,7 +219,7 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = content: { content: JSON.stringify(freeFormJsonValue), contentType: 'application/json', - } + }, }); const freeFormYaml = createAppConfigConfigurationProfile({ @@ -186,7 +232,7 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = content: { content: freeFormYamlValue, contentType: 'application/x-yaml', - } + }, }); freeFormYaml.node.addDependency(freeFormJson); @@ -200,7 +246,7 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = content: { content: freeFormBase64PlainTextValue, contentType: 'text/plain', - } + }, }); freeFormBase64PlainText.node.addDependency(freeFormYaml); @@ -214,48 +260,45 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = content: { content: JSON.stringify(featureFlagValue), contentType: 'application/json', - } + }, }); featureFlag.node.addDependency(freeFormBase64PlainText); // Grant access to the Lambda function to the AppConfig resources. - Aspects.of(stack).add(new ResourceAccessGranter([ - freeFormJson, - freeFormYaml, - freeFormBase64PlainText, - featureFlag, - ])); + Aspects.of(stack).add( + new ResourceAccessGranter([ + freeFormJson, + freeFormYaml, + freeFormBase64PlainText, + featureFlag, + ]) + ); // Deploy the stack await deployStack(integTestApp, stack); // and invoke the Lambda function - invocationLogs = await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); - + invocationLogs = await invokeFunction( + functionName, + invocationCount, + 'SEQUENTIAL' + ); }, SETUP_TIMEOUT); describe('AppConfigProvider usage', () => { - // Test 1 - get a single parameter as-is (no transformation - should return an Uint8Array) it('should retrieve single parameter as-is', () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[0]); expect(testLog).toStrictEqual({ test: 'get', - value: JSON.parse( - JSON.stringify( - encoder.encode(freeFormYamlValue) - ) - ), + value: JSON.parse(JSON.stringify(encoder.encode(freeFormYamlValue))), }); - }); // Test 2 - get a free-form JSON and apply json transformation (should return an object) it('should retrieve a free-form JSON parameter with JSON transformation', () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[1]); @@ -263,13 +306,11 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = test: 'get-freeform-json-binary', value: freeFormJsonValue, }); - }); - - // Test 3 - get a free-form base64-encoded plain text and apply binary transformation + + // Test 3 - get a free-form base64-encoded plain text and apply binary transformation // (should return a decoded string) it('should retrieve a base64-encoded plain text parameter with binary transformation', () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[2]); @@ -277,12 +318,10 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = test: 'get-freeform-base64-plaintext-binary', value: freeFormPlainTextValue, }); - }); - + // Test 4 - get a feature flag and apply json transformation (should return an object) it('should retrieve a feature flag parameter with JSON transformation', () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[3]); @@ -290,57 +329,61 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = test: 'get-feature-flag-binary', value: featureFlagValue.values, }); - }); - + // Test 5 - get parameter twice with middleware, which counts the number // of requests, we check later if we only called AppConfig API once - it('should retrieve single parameter cached', () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[4]); - - expect(testLog).toStrictEqual({ - test: 'get-cached', - value: 1 - }); + it( + 'should retrieve single parameter cached', + () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[4]); + + expect(testLog).toStrictEqual({ + test: 'get-cached', + value: 1, + }); + }, + TEST_CASE_TIMEOUT + ); - }, TEST_CASE_TIMEOUT); - // Test 6 - get parameter twice, but force fetch 2nd time, // we count number of SDK requests and check that we made two API calls - it('should retrieve single parameter twice without caching', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[5]); - - expect(testLog).toStrictEqual({ - test: 'get-forced', - value: 2 - }); - - }, TEST_CASE_TIMEOUT); + it( + 'should retrieve single parameter twice without caching', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[5]); + + expect(testLog).toStrictEqual({ + test: 'get-forced', + value: 2, + }); + }, + TEST_CASE_TIMEOUT + ); // Test 7 - get parameter twice, using maxAge to avoid primary cache // we count number of SDK requests and check that we made two API calls // and check that the values match - it('should retrieve single parameter twice, with expiration between and matching values', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[6]); - const result = freeFormBase64PlainTextValue; - - expect(testLog).toStrictEqual({ - test: 'get-expired', - value: { - counter: 2, - result1: result, - result2: result - } - }); - - }, TEST_CASE_TIMEOUT); - + it( + 'should retrieve single parameter twice, with expiration between and matching values', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[6]); + const result = freeFormBase64PlainTextValue; + + expect(testLog).toStrictEqual({ + test: 'get-expired', + value: { + counter: 2, + result1: result, + result2: result, + }, + }); + }, + TEST_CASE_TIMEOUT + ); }); afterAll(async () => { @@ -348,5 +391,4 @@ describe(`parameters E2E tests (appConfigProvider) for runtime ${runtime}`, () = await destroyStack(integTestApp, stack); } }, TEARDOWN_TIMEOUT); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/e2e/constants.ts b/packages/parameters/tests/e2e/constants.ts index 03fb4eed52..e750986dea 100644 --- a/packages/parameters/tests/e2e/constants.ts +++ b/packages/parameters/tests/e2e/constants.ts @@ -2,4 +2,4 @@ export const RESOURCE_NAME_PREFIX = 'Parameters-E2E'; export const ONE_MINUTE = 60 * 1000; export const TEST_CASE_TIMEOUT = 3 * ONE_MINUTE; export const SETUP_TIMEOUT = 5 * ONE_MINUTE; -export const TEARDOWN_TIMEOUT = 5 * ONE_MINUTE; \ No newline at end of file +export const TEARDOWN_TIMEOUT = 5 * ONE_MINUTE; diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts index 30ac2bbbdc..7755d4b819 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.functionCode.ts @@ -14,7 +14,8 @@ const logger = new TinyLogger(); const tableGet = process.env.TABLE_GET ?? 'my-table'; const tableGetMultiple = process.env.TABLE_GET_MULTIPLE ?? 'my-table'; const tableGetCustomkeys = process.env.TABLE_GET_CUSTOM_KEYS ?? 'my-table'; -const tableGetMultipleCustomkeys = process.env.TABLE_GET_MULTIPLE_CUSTOM_KEYS ?? 'my-table'; +const tableGetMultipleCustomkeys = + process.env.TABLE_GET_MULTIPLE_CUSTOM_KEYS ?? 'my-table'; const keyAttr = process.env.KEY_ATTR ?? 'id'; const sortAttr = process.env.SORT_ATTR ?? 'sk'; const valueAttr = process.env.VALUE_ATTR ?? 'value'; @@ -54,18 +55,18 @@ const _call_get = async ( paramName: string, testName: string, provider: DynamoDBProvider, - options?: DynamoDBGetOptionsInterface, + options?: DynamoDBGetOptionsInterface ): Promise => { try { const parameterValue = await provider.get(paramName, options); logger.log({ test: testName, - value: parameterValue + value: parameterValue, }); } catch (err) { logger.log({ test: testName, - error: err.message + error: err.message, }); } }; @@ -75,52 +76,61 @@ const _call_get_multiple = async ( paramPath: string, testName: string, provider: DynamoDBProvider, - options?: DynamoDBGetMultipleOptionsInterface, + options?: DynamoDBGetMultipleOptionsInterface ): Promise => { try { - const parameterValues = await provider.getMultiple( - paramPath, - options - ); + const parameterValues = await provider.getMultiple(paramPath, options); logger.log({ test: testName, - value: parameterValues + value: parameterValues, }); } catch (err) { logger.log({ test: testName, - error: err.message + error: err.message, }); } }; -export const handler = async (_event: unknown, _context: Context): Promise => { +export const handler = async ( + _event: unknown, + _context: Context +): Promise => { // Test 1 - get a single parameter with default options (keyAttr: 'id', valueAttr: 'value') await _call_get('my-param', 'get', providerGet); - + // Test 2 - get multiple parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') await _call_get_multiple('my-params', 'get-multiple', providerGetMultiple); - + // Test 3 - get a single parameter with custom options (keyAttr: 'key', valueAttr: 'val') await _call_get('my-param', 'get-custom', providerGetCustomKeys); // Test 4 - get multiple parameters with custom options (keyAttr: 'key', sortAttr: 'sort', valueAttr: 'val') - await _call_get_multiple('my-params', 'get-multiple-custom', providerGetMultipleCustomKeys); + await _call_get_multiple( + 'my-params', + 'get-multiple-custom', + providerGetMultipleCustomKeys + ); // Test 5 - get a single parameter with json transform await _call_get('my-param-json', 'get-json-transform', providerGet, { - transform: 'json' + transform: 'json', }); - + // Test 6 - get a single parameter with binary transform await _call_get('my-param-binary', 'get-binary-transform', providerGet, { - transform: 'binary' + transform: 'binary', }); // Test 7 - get multiple parameters with auto transform - await _call_get_multiple('my-encoded-params', 'get-multiple-auto-transform', providerGetMultiple, { - transform: 'auto' - }); + await _call_get_multiple( + 'my-encoded-params', + 'get-multiple-auto-transform', + providerGetMultiple, + { + transform: 'auto', + } + ); // Test 8 // get parameter twice with middleware, which counts the number of requests, we check later if we only called DynamoDB once @@ -131,12 +141,12 @@ export const handler = async (_event: unknown, _context: Context): Promise await providerWithMiddleware.get('my-param'); logger.log({ test: 'get-cached', - value: middleware.counter // should be 1 + value: middleware.counter, // should be 1 }); } catch (err) { logger.log({ test: 'get-cached', - error: err.message + error: err.message, }); } @@ -149,13 +159,12 @@ export const handler = async (_event: unknown, _context: Context): Promise await providerWithMiddleware.get('my-param', { forceFetch: true }); logger.log({ test: 'get-forced', - value: middleware.counter // should be 2 + value: middleware.counter, // should be 2 }); } catch (err) { logger.log({ test: 'get-forced', - error: err.message + error: err.message, }); } - -}; \ No newline at end of file +}; diff --git a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts index 62e8bd6c39..a63f225db1 100644 --- a/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts +++ b/packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts @@ -7,22 +7,28 @@ import path from 'path'; import { AttributeType } from 'aws-cdk-lib/aws-dynamodb'; import { App, Stack, Aspects } from 'aws-cdk-lib'; import { v4 } from 'uuid'; -import { - generateUniqueName, - isValidRuntimeKey, - createStackWithLambdaFunction, - invokeFunction, +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, } from '../../../commons/tests/utils/e2eUtils'; import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { + deployStack, + destroyStack, +} from '../../../commons/tests/utils/cdk-cli'; import { ResourceAccessGranter } from '../helpers/cdkAspectGrantAccess'; -import { - RESOURCE_NAME_PREFIX, - SETUP_TIMEOUT, - TEARDOWN_TIMEOUT, - TEST_CASE_TIMEOUT +import { + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT, } from './constants'; -import { createDynamoDBTable, putDynamoDBItem } from '../helpers/parametersUtils'; +import { + createDynamoDBTable, + putDynamoDBItem, +} from '../helpers/parametersUtils'; const runtime: string = process.env.RUNTIME || 'nodejs18x'; @@ -31,17 +37,47 @@ if (!isValidRuntimeKey(runtime)) { } const uuid = v4(); -const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider'); -const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'dynamoDBProvider'); +const stackName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'dynamoDBProvider' +); +const functionName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'dynamoDBProvider' +); const lambdaFunctionCodeFile = 'dynamoDBProvider.class.test.functionCode.ts'; const invocationCount = 1; // Parameters to be used by Parameters in the Lambda function -const tableGet = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Table-Get'); -const tableGetMultiple = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Table-GetMultiple'); -const tableGetCustomkeys = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Table-GetCustomKeys'); -const tableGetMultipleCustomkeys = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'Table-GetMultipleCustomKeys'); +const tableGet = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'Table-Get' +); +const tableGetMultiple = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'Table-GetMultiple' +); +const tableGetCustomkeys = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'Table-GetCustomKeys' +); +const tableGetMultipleCustomkeys = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'Table-GetMultipleCustomKeys' +); const keyAttr = 'key'; const sortAttr = 'sort'; const valueAttr = 'val'; @@ -53,15 +89,15 @@ let stack: Stack; * This test suite deploys a CDK stack with a Lambda function and a number of DynamoDB tables. * The function code uses the Parameters utility to retrieve values from the DynamoDB tables. * It then logs the values to CloudWatch Logs as JSON. - * + * * Once the stack is deployed, the Lambda function is invoked and the CloudWatch Logs are retrieved. * The logs are then parsed and the values are compared to the expected values in each test case. - * + * * The tables are populated with data before the Lambda function is invoked. These tables and values * allow to test the different use cases of the DynamoDBProvider class. - * + * * The tables are: - * + * * - Table-Get: a table with a single partition key (id) and attribute (value) * +-----------------+----------------------+ * | id | value | @@ -70,7 +106,7 @@ let stack: Stack; * | my-param-json | "{\"foo\": \"bar\"}" | * | my-param-binary | "YmF6" | * +-----------------+----------------------+ - * + * * - Table-GetMultiple: a table with a partition key (id) and a sort key (sk) and attribute (value) * +-------------------+---------------+----------------------+ * | id | sk | value | @@ -80,14 +116,14 @@ let stack: Stack; * | my-encoded-params | config.json | "{\"foo\": \"bar\"}" | * | my-encoded-params | config.binary | "YmF6" | * +-------------------+---------------+----------------------+ - * + * * - Table-GetCustomKeys: a table with a single partition key (key) and attribute (val) * +-----------------+----------------------+ * | key | val | * +-----------------+----------------------+ * | my-param | foo | * +-----------------+----------------------+ - * + * * - Table-GetMultipleCustomKeys: a table with a partition key (key) and a sort key (sort) and attribute (val) * +-------------------+---------------+----------------------+ * | key | sort | val | @@ -95,38 +131,37 @@ let stack: Stack; * | my-params | config | bar | * | my-params | key | baz | * +-------------------+---------------+----------------------+ - * + * * The tests are: - * + * * Test 1 * Get a single parameter with default options (keyAttr: 'id', valueAttr: 'value') from table Table-Get - * + * * Test 2 * Get multiple parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') from table Table-GetMultiple - * + * * Test 3 * Get a single parameter with custom options (keyAttr: 'key', valueAttr: 'val') from table Table-GetCustomKeys - * + * * Test 4 * Get multiple parameters with custom options (keyAttr: 'key', sortAttr: 'sort', valueAttr: 'val') from table Table-GetMultipleCustomKeys - * + * * Test 5 * Get a single JSON parameter with default options (keyAttr: 'id', valueAttr: 'value') and transform from table Table-Get - * + * * Test 6 * Get a single binrary parameter with default options (keyAttr: 'id', valueAttr: 'value') and transform it from table Table-Get - * + * * Test 7 * Get multiple JSON and binary parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') and transform them automatically from table Table-GetMultiple - * + * * Test 8 * Get a parameter twice and check that the value is cached. This uses a custom SDK client that counts the number of calls to DynamoDB. - * + * * Test 9 * Get a cached parameter and force retrieval. This also uses the same custom SDK client that counts the number of calls to DynamoDB. */ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () => { - let invocationLogs: InvocationLogs[]; beforeAll(async () => { @@ -158,7 +193,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = tableName: tableGet, partitionKey: { name: 'id', - type: AttributeType.STRING + type: AttributeType.STRING, }, }); const ddbTableGetMultiple = createDynamoDBTable({ @@ -167,12 +202,12 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = tableName: tableGetMultiple, partitionKey: { name: 'id', - type: AttributeType.STRING + type: AttributeType.STRING, }, sortKey: { name: 'sk', - type: AttributeType.STRING - } + type: AttributeType.STRING, + }, }); const ddbTableGetCustomKeys = createDynamoDBTable({ stack, @@ -180,7 +215,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = tableName: tableGetCustomkeys, partitionKey: { name: keyAttr, - type: AttributeType.STRING + type: AttributeType.STRING, }, }); const ddbTabelGetMultipleCustomKeys = createDynamoDBTable({ @@ -189,21 +224,23 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = tableName: tableGetMultipleCustomkeys, partitionKey: { name: keyAttr, - type: AttributeType.STRING + type: AttributeType.STRING, }, sortKey: { name: sortAttr, - type: AttributeType.STRING + type: AttributeType.STRING, }, }); // Give the Lambda access to the DynamoDB tables - Aspects.of(stack).add(new ResourceAccessGranter([ - ddbTableGet, - ddbTableGetMultiple, - ddbTableGetCustomKeys, - ddbTabelGetMultipleCustomKeys, - ])); + Aspects.of(stack).add( + new ResourceAccessGranter([ + ddbTableGet, + ddbTableGetMultiple, + ddbTableGetCustomKeys, + ddbTabelGetMultipleCustomKeys, + ]) + ); // Seed tables with test data // Test 1 @@ -271,7 +308,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = [valueAttr]: 'baz', }, }); - + // Test 5 putDynamoDBItem({ stack, @@ -293,7 +330,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = value: 'YmF6', // base64 encoded 'baz' }, }); - + // Test 7 putDynamoDBItem({ stack, @@ -322,67 +359,76 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = await deployStack(integTestApp, stack); // and invoke the Lambda function - invocationLogs = await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); - + invocationLogs = await invokeFunction( + functionName, + invocationCount, + 'SEQUENTIAL' + ); }, SETUP_TIMEOUT); describe('DynamoDBProvider usage', () => { - // Test 1 - get a single parameter with default options (keyAttr: 'id', valueAttr: 'value') - it('should retrieve a single parameter', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[0]); - - expect(testLog).toStrictEqual({ - test: 'get', - value: 'foo', - }); - - }, TEST_CASE_TIMEOUT); + it( + 'should retrieve a single parameter', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[0]); + + expect(testLog).toStrictEqual({ + test: 'get', + value: 'foo', + }); + }, + TEST_CASE_TIMEOUT + ); // Test 2 - get multiple parameters with default options (keyAttr: 'id', sortAttr: 'sk', valueAttr: 'value') - it('should retrieve multiple parameters', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[1]); - - expect(testLog).toStrictEqual({ - test: 'get-multiple', - value: { config: 'bar', key: 'baz' }, - }); - - }, TEST_CASE_TIMEOUT); + it( + 'should retrieve multiple parameters', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[1]); + + expect(testLog).toStrictEqual({ + test: 'get-multiple', + value: { config: 'bar', key: 'baz' }, + }); + }, + TEST_CASE_TIMEOUT + ); // Test 3 - get a single parameter with custom options (keyAttr: 'key', valueAttr: 'val') - it('should retrieve a single parameter', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[2]); - - expect(testLog).toStrictEqual({ - test: 'get-custom', - value: 'foo', - }); - - }, TEST_CASE_TIMEOUT); + it( + 'should retrieve a single parameter', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[2]); + + expect(testLog).toStrictEqual({ + test: 'get-custom', + value: 'foo', + }); + }, + TEST_CASE_TIMEOUT + ); // Test 4 - get multiple parameters with custom options (keyAttr: 'key', sortAttr: 'sort', valueAttr: 'val') - it('should retrieve multiple parameters', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[3]); - - expect(testLog).toStrictEqual({ - test: 'get-multiple-custom', - value: { config: 'bar', key: 'baz' }, - }); - - }, TEST_CASE_TIMEOUT); + it( + 'should retrieve multiple parameters', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[3]); + + expect(testLog).toStrictEqual({ + test: 'get-multiple-custom', + value: { config: 'bar', key: 'baz' }, + }); + }, + TEST_CASE_TIMEOUT + ); // Test 5 - get a single parameter with json transform it('should retrieve a single parameter with json transform', async () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[4]); @@ -390,12 +436,10 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = test: 'get-json-transform', value: { foo: 'bar' }, }); - }); // Test 6 - get a single parameter with binary transform it('should retrieve a single parameter with binary transform', async () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[5]); @@ -403,12 +447,10 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = test: 'get-binary-transform', value: 'baz', }); - }); // Test 7 - get multiple parameters with auto transforms (json and binary) it('should retrieve multiple parameters with auto transforms', async () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[6]); @@ -419,12 +461,10 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = 'key.binary': 'baz', }, }); - }); - + // Test 8 - Get a parameter twice and check that the value is cached. it('should retrieve multiple parameters with auto transforms', async () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[7]); @@ -432,12 +472,10 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = test: 'get-cached', value: 1, }); - }); // Test 9 - Get a cached parameter and force retrieval. it('should retrieve multiple parameters with auto transforms', async () => { - const logs = invocationLogs[0].getFunctionLogs(); const testLog = InvocationLogs.parseFunctionLog(logs[8]); @@ -445,9 +483,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () = test: 'get-forced', value: 2, }); - }); - }); afterAll(async () => { diff --git a/packages/parameters/tests/e2e/secretsProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/secretsProvider.class.test.functionCode.ts index a1bd955919..a446bd0c1f 100644 --- a/packages/parameters/tests/e2e/secretsProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/secretsProvider.class.test.functionCode.ts @@ -11,19 +11,27 @@ const defaultProvider = new SecretsProvider(); const secretNamePlain = process.env.SECRET_NAME_PLAIN || ''; const secretNameObject = process.env.SECRET_NAME_OBJECT || ''; const secretNameBinary = process.env.SECRET_NAME_BINARY || ''; -const secretNameObjectWithSuffix = process.env.SECRET_NAME_OBJECT_WITH_SUFFIX || ''; -const secretNameBinaryWithSuffix = process.env.SECRET_NAME_BINARY_WITH_SUFFIX || ''; +const secretNameObjectWithSuffix = + process.env.SECRET_NAME_OBJECT_WITH_SUFFIX || ''; +const secretNameBinaryWithSuffix = + process.env.SECRET_NAME_BINARY_WITH_SUFFIX || ''; const secretNamePlainChached = process.env.SECRET_NAME_PLAIN_CACHED || ''; -const secretNamePlainForceFetch = process.env.SECRET_NAME_PLAIN_FORCE_FETCH || ''; +const secretNamePlainForceFetch = + process.env.SECRET_NAME_PLAIN_FORCE_FETCH || ''; // Provider test 8, 9 const customClient = new SecretsManagerClient({}); customClient.middlewareStack.use(middleware); const providerWithMiddleware = new SecretsProvider({ - awsSdkV3Client: customClient + awsSdkV3Client: customClient, }); -const _call_get = async (paramName: string, testName: string, options?: SecretsGetOptionsInterface, provider?: SecretsProvider,): Promise => { +const _call_get = async ( + paramName: string, + testName: string, + options?: SecretsGetOptionsInterface, + provider?: SecretsProvider +): Promise => { try { // we might get a provider with specific sdk options, otherwise fallback to default const currentProvider = provider ? provider : defaultProvider; @@ -31,32 +39,42 @@ const _call_get = async (paramName: string, testName: string, options?: SecretsG const parameterValue = await currentProvider.get(paramName, options); logger.log({ test: testName, - value: parameterValue + value: parameterValue, }); } catch (err) { logger.log({ test: testName, - error: err.message + error: err.message, }); } }; -export const handler = async (_event: unknown, _context: Context): Promise => { - +export const handler = async ( + _event: unknown, + _context: Context +): Promise => { // Test 1 get single secret as plaintext await _call_get(secretNamePlain, 'get-plain'); // Test 2 get single secret with transform json - await _call_get(secretNameObject, 'get-transform-json', { transform: 'json' }); + await _call_get(secretNameObject, 'get-transform-json', { + transform: 'json', + }); // Test 3 get single secret with transform binary - await _call_get(secretNameBinary, 'get-transform-binary', { transform: 'binary' }); + await _call_get(secretNameBinary, 'get-transform-binary', { + transform: 'binary', + }); // Test 4 get single secret with transform auto json - await _call_get(secretNameObjectWithSuffix, 'get-transform-auto-json', { transform: 'auto' }); + await _call_get(secretNameObjectWithSuffix, 'get-transform-auto-json', { + transform: 'auto', + }); // Test 5 get single secret with transform auto binary - await _call_get(secretNameBinaryWithSuffix, 'get-transform-auto-binary', { transform: 'auto' }); + await _call_get(secretNameBinaryWithSuffix, 'get-transform-auto-binary', { + transform: 'auto', + }); // Test 6 // get secret twice with middleware, which counts number of SDK requests, we check later if we only called SecretManager API once @@ -66,12 +84,12 @@ export const handler = async (_event: unknown, _context: Context): Promise await providerWithMiddleware.get(secretNamePlainChached); logger.log({ test: 'get-plain-cached', - value: middleware.counter // should be 1 + value: middleware.counter, // should be 1 }); } catch (err) { logger.log({ test: secretNamePlainChached, - error: err.message + error: err.message, }); } // Test 7 @@ -80,16 +98,17 @@ export const handler = async (_event: unknown, _context: Context): Promise middleware.counter = 0; providerWithMiddleware.clearCache(); await providerWithMiddleware.get(secretNamePlainForceFetch); - await providerWithMiddleware.get(secretNamePlainForceFetch, { forceFetch: true }); + await providerWithMiddleware.get(secretNamePlainForceFetch, { + forceFetch: true, + }); logger.log({ test: 'get-plain-force', - value: middleware.counter // should be 2 + value: middleware.counter, // should be 2 }); } catch (err) { logger.log({ test: secretNamePlainChached, - error: err.message + error: err.message, }); } - }; diff --git a/packages/parameters/tests/e2e/secretsProvider.class.test.ts b/packages/parameters/tests/e2e/secretsProvider.class.test.ts index 48e2734974..ea1fdd7494 100644 --- a/packages/parameters/tests/e2e/secretsProvider.class.test.ts +++ b/packages/parameters/tests/e2e/secretsProvider.class.test.ts @@ -7,12 +7,20 @@ import { createStackWithLambdaFunction, generateUniqueName, invokeFunction, - isValidRuntimeKey + isValidRuntimeKey, } from '../../../commons/tests/utils/e2eUtils'; -import { RESOURCE_NAME_PREFIX, SETUP_TIMEOUT, TEARDOWN_TIMEOUT, TEST_CASE_TIMEOUT } from './constants'; +import { + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT, +} from './constants'; import { v4 } from 'uuid'; import { Tracing } from 'aws-cdk-lib/aws-lambda'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { + deployStack, + destroyStack, +} from '../../../commons/tests/utils/cdk-cli'; import { App, Aspects, SecretValue, Stack } from 'aws-cdk-lib'; import path from 'path'; import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; @@ -44,11 +52,20 @@ if (!isValidRuntimeKey(runtime)) { * */ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => { - const uuid = v4(); let invocationLogs: InvocationLogs[]; - const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'secretsProvider'); - const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'secretsProvider'); + const stackName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'secretsProvider' + ); + const functionName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'secretsProvider' + ); const lambdaFunctionCodeFile = 'secretsProvider.class.test.functionCode.ts'; const invocationCount = 1; @@ -57,15 +74,49 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => let stack: Stack; beforeAll(async () => { - // use unique names for each test to keep a clean state - const secretNamePlain = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'testSecretPlain'); - const secretNameObject = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'testSecretObject'); - const secretNameBinary = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'testSecretBinary'); - const secretNameObjectWithSuffix = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'testSecretObject.json'); - const secretNameBinaryWithSuffix = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'testSecretObject.binary'); - const secretNamePlainCached = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'testSecretPlainCached'); - const secretNamePlainForceFetch = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'testSecretPlainForceFetch'); + const secretNamePlain = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'testSecretPlain' + ); + const secretNameObject = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'testSecretObject' + ); + const secretNameBinary = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'testSecretBinary' + ); + const secretNameObjectWithSuffix = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'testSecretObject.json' + ); + const secretNameBinaryWithSuffix = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'testSecretObject.binary' + ); + const secretNamePlainCached = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'testSecretPlainCached' + ); + const secretNamePlainForceFetch = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'testSecretPlainForceFetch' + ); // creates the test fuction that uses powertools secret provider we want to test // pass env vars with secret names we want to fetch @@ -85,102 +136,140 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => SECRET_NAME_PLAIN_CACHED: secretNamePlainCached, SECRET_NAME_PLAIN_FORCE_FETCH: secretNamePlainForceFetch, }, - runtime: runtime + runtime: runtime, }); const secretString = new Secret(stack, 'testSecretPlain', { secretName: secretNamePlain, - secretStringValue: SecretValue.unsafePlainText('foo') + secretStringValue: SecretValue.unsafePlainText('foo'), }); const secretObject = new Secret(stack, 'testSecretObject', { secretName: secretNameObject, secretObjectValue: { foo: SecretValue.unsafePlainText('bar'), - } + }, }); const secretBinary = new Secret(stack, 'testSecretBinary', { secretName: secretNameBinary, - secretStringValue: SecretValue.unsafePlainText('Zm9v') // 'foo' encoded in base64 + secretStringValue: SecretValue.unsafePlainText('Zm9v'), // 'foo' encoded in base64 }); - const secretObjectWithSuffix = new Secret(stack, 'testSecretObjectWithSuffix', { - secretName: secretNameObjectWithSuffix, - secretObjectValue: { - foo: SecretValue.unsafePlainText('bar') + const secretObjectWithSuffix = new Secret( + stack, + 'testSecretObjectWithSuffix', + { + secretName: secretNameObjectWithSuffix, + secretObjectValue: { + foo: SecretValue.unsafePlainText('bar'), + }, } - }); - - const secretBinaryWithSuffix = new Secret(stack, 'testSecretBinaryWithSuffix', { - secretName: secretNameBinaryWithSuffix, - secretStringValue: SecretValue.unsafePlainText('Zm9v') // 'foo' encoded in base64 - }); + ); + + const secretBinaryWithSuffix = new Secret( + stack, + 'testSecretBinaryWithSuffix', + { + secretName: secretNameBinaryWithSuffix, + secretStringValue: SecretValue.unsafePlainText('Zm9v'), // 'foo' encoded in base64 + } + ); const secretStringCached = new Secret(stack, 'testSecretStringCached', { secretName: secretNamePlainCached, - secretStringValue: SecretValue.unsafePlainText('foo') + secretStringValue: SecretValue.unsafePlainText('foo'), }); - const secretStringForceFetch = new Secret(stack, 'testSecretStringForceFetch', { - secretName: secretNamePlainForceFetch, - secretStringValue: SecretValue.unsafePlainText('foo') - }); + const secretStringForceFetch = new Secret( + stack, + 'testSecretStringForceFetch', + { + secretName: secretNamePlainForceFetch, + secretStringValue: SecretValue.unsafePlainText('foo'), + } + ); // add secrets here to grant lambda permisisons to access secrets - Aspects.of(stack).add(new ResourceAccessGranter([ - secretString, secretObject, secretBinary, secretObjectWithSuffix, secretBinaryWithSuffix, secretStringCached, secretStringForceFetch ])); + Aspects.of(stack).add( + new ResourceAccessGranter([ + secretString, + secretObject, + secretBinary, + secretObjectWithSuffix, + secretBinaryWithSuffix, + secretStringCached, + secretStringForceFetch, + ]) + ); await deployStack(integTestApp, stack); - invocationLogs = await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); - + invocationLogs = await invokeFunction( + functionName, + invocationCount, + 'SEQUENTIAL' + ); }, SETUP_TIMEOUT); describe('SecretsProvider usage', () => { + it( + 'should retrieve a secret as plain string', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[0]); + + expect(testLog).toStrictEqual({ + test: 'get-plain', + value: 'foo', + }); + }, + TEST_CASE_TIMEOUT + ); + + it( + 'should retrieve a secret using transform json option', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[1]); + + expect(testLog).toStrictEqual({ + test: 'get-transform-json', + value: { foo: 'bar' }, + }); + }, + TEST_CASE_TIMEOUT + ); + + it( + 'should retrieve a secret using transform binary option', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[2]); + + expect(testLog).toStrictEqual({ + test: 'get-transform-binary', + value: 'foo', + }); + }, + TEST_CASE_TIMEOUT + ); + }); - it('should retrieve a secret as plain string', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[0]); - - expect(testLog).toStrictEqual({ - test: 'get-plain', - value: 'foo' - }); - }, TEST_CASE_TIMEOUT); - - it('should retrieve a secret using transform json option', async () => { - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[1]); - - expect(testLog).toStrictEqual({ - test: 'get-transform-json', - value: { foo: 'bar' } - }); - }, TEST_CASE_TIMEOUT); - - it('should retrieve a secret using transform binary option', async () => { + it( + 'should retrieve a secret using transform auto option with implicit json', + async () => { const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[2]); + const testLog = InvocationLogs.parseFunctionLog(logs[3]); + // result should be a json object expect(testLog).toStrictEqual({ - test: 'get-transform-binary', - value: 'foo' + test: 'get-transform-auto-json', + value: { foo: 'bar' }, }); - }, TEST_CASE_TIMEOUT); - }); - - it('should retrieve a secret using transform auto option with implicit json', async () => { - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[3]); - - // result should be a json object - expect(testLog).toStrictEqual({ - test: 'get-transform-auto-json', - value: { foo: 'bar' } - }); - }, TEST_CASE_TIMEOUT); + }, + TEST_CASE_TIMEOUT + ); it('should retrieve a secret using transform auto option with implicit binary', async () => { const logs = invocationLogs[0].getFunctionLogs(); @@ -188,7 +277,7 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => expect(testLog).toStrictEqual({ test: 'get-transform-auto-binary', - value: 'foo' + value: 'foo', }); }); @@ -199,7 +288,7 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => // we fetch twice, but we expect to make an API call only once expect(testLogFirst).toStrictEqual({ test: 'get-plain-cached', - value: 1 + value: 1, }); }); @@ -210,7 +299,7 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => // we fetch twice, 2nd time with forceFetch: true flag, we expect two api calls expect(testLogFirst).toStrictEqual({ test: 'get-plain-force', - value: 2 + value: 2, }); }); @@ -219,4 +308,4 @@ describe(`parameters E2E tests (SecretsProvider) for runtime: ${runtime}`, () => await destroyStack(integTestApp, stack); } }, TEARDOWN_TIMEOUT); -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts b/packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts index 4f0280f0a8..e97ebca434 100644 --- a/packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts +++ b/packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts @@ -1,11 +1,9 @@ import { Context } from 'aws-lambda'; -import { - SSMProvider, -} from '../../src/ssm'; +import { SSMProvider } from '../../src/ssm'; import { SSMGetOptionsInterface, SSMGetMultipleOptionsInterface, - SSMGetParametersByNameOptionsInterface + SSMGetParametersByNameOptionsInterface, } from '../../src/types'; import { TinyLogger } from '../helpers/tinyLogger'; import { middleware } from '../helpers/sdkMiddlewareRequestCounter'; @@ -19,7 +17,7 @@ const defaultProvider = new SSMProvider(); const customClient = new SSMClient({}); customClient.middlewareStack.use(middleware); const providerWithMiddleware = new SSMProvider({ - awsSdkV3Client: customClient + awsSdkV3Client: customClient, }); const paramA = process.env.PARAM_A ?? 'my-param'; @@ -48,12 +46,12 @@ const _call_get = async ( const parameterValue = await currentProvider.get(paramName, options); logger.log({ test: testName, - value: parameterValue + value: parameterValue, }); } catch (err) { logger.log({ test: testName, - error: err.message + error: err.message, }); } }; @@ -74,12 +72,12 @@ const _call_get_multiple = async ( ); logger.log({ test: testName, - value: parameterValues + value: parameterValues, }); } catch (err) { logger.log({ test: testName, - error: err.message + error: err.message, }); } }; @@ -94,26 +92,32 @@ const _call_get_parameters_by_name = async ( try { const currentProvider = resolveProvider(provider); - const parameterValues = await currentProvider.getParametersByName(params, options); + const parameterValues = await currentProvider.getParametersByName( + params, + options + ); logger.log({ test: testName, - value: parameterValues + value: parameterValues, }); } catch (err) { logger.log({ test: testName, - error: err.message + error: err.message, }); } }; -export const handler = async (_event: unknown, _context: Context): Promise => { +export const handler = async ( + _event: unknown, + _context: Context +): Promise => { // Test 1 - get a single parameter by name with default options await _call_get(paramA, 'get'); - + // Test 2 - get a single parameter by name with decrypt await _call_get(paramEncryptedA, 'get-decrypt', { decrypt: true }); - + // Test 3 - get multiple parameters by path with default options // Get path (/param/get) const parameterPath = paramA.substring(0, paramA.lastIndexOf('/')); @@ -125,25 +129,38 @@ export const handler = async (_event: unknown, _context: Context): Promise 0, paramA.substring(1, paramA.length).indexOf('/') + 1 ); - await _call_get_multiple(parameterRoot, 'get-multiple-recursive', { recursive: true }); - + await _call_get_multiple(parameterRoot, 'get-multiple-recursive', { + recursive: true, + }); + // Test 5 - get multiple parameters by path with decrypt // Get parameters path (i.e. from /param/get/a & /param/get/b to /param/get) - const parameterPathDecrypt = paramEncryptedA.substring(0, paramEncryptedA.lastIndexOf('/')); - await _call_get_multiple(parameterPathDecrypt, 'get-multiple-decrypt', { decrypt: true }); + const parameterPathDecrypt = paramEncryptedA.substring( + 0, + paramEncryptedA.lastIndexOf('/') + ); + await _call_get_multiple(parameterPathDecrypt, 'get-multiple-decrypt', { + decrypt: true, + }); // Test 6 - get multiple parameters by name with default options - await _call_get_parameters_by_name({ - [paramA]: {}, - [paramB]: {}, - }, 'get-multiple-by-name'); - + await _call_get_parameters_by_name( + { + [paramA]: {}, + [paramB]: {}, + }, + 'get-multiple-by-name' + ); + // Test 7 - get multiple parameters by name, some of them encrypted and some not - await _call_get_parameters_by_name({ - [paramA]: {}, - [paramEncryptedA]: { decrypt: true }, - [paramEncryptedB]: { decrypt: true }, - }, 'get-multiple-by-name-mixed-decrypt'); + await _call_get_parameters_by_name( + { + [paramA]: {}, + [paramEncryptedA]: { decrypt: true }, + [paramEncryptedB]: { decrypt: true }, + }, + 'get-multiple-by-name-mixed-decrypt' + ); // Test 8 // get parameter twice with middleware, which counts the number of requests, we check later if we only called SSM API once @@ -154,12 +171,12 @@ export const handler = async (_event: unknown, _context: Context): Promise await providerWithMiddleware.get(paramA); logger.log({ test: 'get-cached', - value: middleware.counter // should be 1 + value: middleware.counter, // should be 1 }); } catch (err) { logger.log({ test: 'get-cached', - error: err.message + error: err.message, }); } @@ -172,12 +189,12 @@ export const handler = async (_event: unknown, _context: Context): Promise await providerWithMiddleware.get(paramA, { forceFetch: true }); logger.log({ test: 'get-forced', - value: middleware.counter // should be 2 + value: middleware.counter, // should be 2 }); } catch (err) { logger.log({ test: 'get-forced', - error: err.message + error: err.message, }); } -}; \ No newline at end of file +}; diff --git a/packages/parameters/tests/e2e/ssmProvider.class.test.ts b/packages/parameters/tests/e2e/ssmProvider.class.test.ts index fb7f3e56f4..e71f123703 100644 --- a/packages/parameters/tests/e2e/ssmProvider.class.test.ts +++ b/packages/parameters/tests/e2e/ssmProvider.class.test.ts @@ -1,26 +1,29 @@ /** * Test SSMProvider class - * + * * @group e2e/parameters/ssm/class */ import path from 'path'; import { App, Stack, Aspects } from 'aws-cdk-lib'; import { StringParameter } from 'aws-cdk-lib/aws-ssm'; import { v4 } from 'uuid'; -import { - generateUniqueName, - isValidRuntimeKey, - createStackWithLambdaFunction, - invokeFunction, +import { + generateUniqueName, + isValidRuntimeKey, + createStackWithLambdaFunction, + invokeFunction, } from '../../../commons/tests/utils/e2eUtils'; import { InvocationLogs } from '../../../commons/tests/utils/InvocationLogs'; -import { deployStack, destroyStack } from '../../../commons/tests/utils/cdk-cli'; +import { + deployStack, + destroyStack, +} from '../../../commons/tests/utils/cdk-cli'; import { ResourceAccessGranter } from '../helpers/cdkAspectGrantAccess'; -import { - RESOURCE_NAME_PREFIX, - SETUP_TIMEOUT, - TEARDOWN_TIMEOUT, - TEST_CASE_TIMEOUT +import { + RESOURCE_NAME_PREFIX, + SETUP_TIMEOUT, + TEARDOWN_TIMEOUT, + TEST_CASE_TIMEOUT, } from './constants'; import { createSSMSecureString } from '../helpers/parametersUtils'; @@ -31,17 +34,47 @@ if (!isValidRuntimeKey(runtime)) { } const uuid = v4(); -const stackName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'ssmProvider'); -const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'ssmProvider'); +const stackName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'ssmProvider' +); +const functionName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'ssmProvider' +); const lambdaFunctionCodeFile = 'ssmProvider.class.test.functionCode.ts'; const invocationCount = 1; // Parameter names to be used by Parameters in the Lambda function -const paramA = generateUniqueName(`/${RESOURCE_NAME_PREFIX}`, uuid, runtime, 'param/a'); -const paramB = generateUniqueName(`/${RESOURCE_NAME_PREFIX}`, uuid, runtime, 'param/b'); -const paramEncryptedA = generateUniqueName(`/${RESOURCE_NAME_PREFIX}`, uuid, runtime, 'param-encrypted/a'); -const paramEncryptedB = generateUniqueName(`/${RESOURCE_NAME_PREFIX}`, uuid, runtime, 'param-encrypted/b'); +const paramA = generateUniqueName( + `/${RESOURCE_NAME_PREFIX}`, + uuid, + runtime, + 'param/a' +); +const paramB = generateUniqueName( + `/${RESOURCE_NAME_PREFIX}`, + uuid, + runtime, + 'param/b' +); +const paramEncryptedA = generateUniqueName( + `/${RESOURCE_NAME_PREFIX}`, + uuid, + runtime, + 'param-encrypted/a' +); +const paramEncryptedB = generateUniqueName( + `/${RESOURCE_NAME_PREFIX}`, + uuid, + runtime, + 'param-encrypted/b' +); // Parameters values const paramAValue = 'foo'; @@ -56,53 +89,52 @@ let stack: Stack; * This test suite deploys a CDK stack with a Lambda function and a number of SSM parameters. * The function code uses the Parameters utility to retrieve the SSM parameters. * It then logs the values to CloudWatch Logs as JSON objects. - * + * * Once the stack is deployed, the Lambda function is invoked and the CloudWatch Logs are retrieved. * The logs are then parsed and the values are checked against the expected values for each test case. - * + * * The parameters created are: * - Name: param/a - Value: foo * - Name: param/b - Value: bar * - Name: param-encrypted/a - Value: foo-encrypted * - Name: param-encrypted/b - Value: bar-encrypted - * + * * These parameters allow to retrieve one or more parameters both by name and by path, as well as * mixing encrypted and unencrypted parameters. - * + * * The tests are: - * + * * Test 1 * get a single parameter by name with default options - * + * * Test 2 * get a single parameter by name with decrypt - * + * * Test 3 * get multiple parameters by path with default options - * + * * Test 4 * get multiple parameters by path recursively (aka. get all parameters under a path recursively) * i.e. given /param, retrieve /param/get/a and /param/get/b (note path depth) - * + * * Test 5 * get multiple parameters by path with decrypt - * + * * Test 6 * get multiple parameters by name with default options - * + * * Test 7 * get multiple parameters by name, some of them encrypted and some not - * + * * Test 8 - * get parameter twice with middleware, which counts the number of requests, + * get parameter twice with middleware, which counts the number of requests, * we check later if we only called SSM API once - * + * * Test 9 * get parameter twice, but force fetch 2nd time, we count number of SDK requests and * check that we made two API calls */ describe(`parameters E2E tests (ssmProvider) for runtime: ${runtime}`, () => { - let invocationLogs: InvocationLogs[]; beforeAll(async () => { @@ -140,7 +172,7 @@ describe(`parameters E2E tests (ssmProvider) for runtime: ${runtime}`, () => { name: paramEncryptedA, value: paramEncryptedAValue, }); - + const parameterEncryptedB = createSSMSecureString({ stack, id: 'Param-encrypted-b', @@ -149,169 +181,198 @@ describe(`parameters E2E tests (ssmProvider) for runtime: ${runtime}`, () => { }); // Give the Lambda function access to the SSM parameters - Aspects.of(stack).add(new ResourceAccessGranter([ - parameterGetA, - parameterGetB, - parameterEncryptedA, - parameterEncryptedB, - ])); + Aspects.of(stack).add( + new ResourceAccessGranter([ + parameterGetA, + parameterGetB, + parameterEncryptedA, + parameterEncryptedB, + ]) + ); // Deploy the stack await deployStack(integTestApp, stack); // and invoke the Lambda function - invocationLogs = await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); - + invocationLogs = await invokeFunction( + functionName, + invocationCount, + 'SEQUENTIAL' + ); }, SETUP_TIMEOUT); describe('SSMProvider usage', () => { - // Test 1 - get a single parameter by name with default options - it('should retrieve a single parameter', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[0]); - - expect(testLog).toStrictEqual({ - test: 'get', - value: paramAValue - }); + it( + 'should retrieve a single parameter', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[0]); + + expect(testLog).toStrictEqual({ + test: 'get', + value: paramAValue, + }); + }, + TEST_CASE_TIMEOUT + ); - }, TEST_CASE_TIMEOUT); - // Test 2 - get a single parameter by name with decrypt - it('should retrieve a single parameter with decryption', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[1]); - - expect(testLog).toStrictEqual({ - test: 'get-decrypt', - value: paramEncryptedAValue - }); + it( + 'should retrieve a single parameter with decryption', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[1]); + + expect(testLog).toStrictEqual({ + test: 'get-decrypt', + value: paramEncryptedAValue, + }); + }, + TEST_CASE_TIMEOUT + ); - }, TEST_CASE_TIMEOUT); - // Test 3 - get multiple parameters by path with default options - it('should retrieve multiple parameters', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[2]); - const expectedParameterNameA = paramA.substring(paramA.lastIndexOf('/') + 1); - const expectedParameterNameB = paramB.substring(paramB.lastIndexOf('/') + 1); - - expect(testLog).toStrictEqual({ - test: 'get-multiple', - value: { - [expectedParameterNameA]: paramAValue, - [expectedParameterNameB]: paramBValue, - } - }); - - }, TEST_CASE_TIMEOUT); - + it( + 'should retrieve multiple parameters', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[2]); + const expectedParameterNameA = paramA.substring( + paramA.lastIndexOf('/') + 1 + ); + const expectedParameterNameB = paramB.substring( + paramB.lastIndexOf('/') + 1 + ); + + expect(testLog).toStrictEqual({ + test: 'get-multiple', + value: { + [expectedParameterNameA]: paramAValue, + [expectedParameterNameB]: paramBValue, + }, + }); + }, + TEST_CASE_TIMEOUT + ); + // Test 4 - get multiple parameters by path recursively // (aka. get all parameters under a path recursively) i.e. // given /param, retrieve /param/get/a and /param/get/b (note path depth) - it('should retrieve multiple parameters recursively', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[3]); - const expectedParameterNameA = paramA.substring(paramA.lastIndexOf('/') + 1); - const expectedParameterNameB = paramB.substring(paramB.lastIndexOf('/') + 1); - - expect(testLog).toStrictEqual({ - test: 'get-multiple-recursive', - value: { - [expectedParameterNameA]: paramAValue, - [expectedParameterNameB]: paramBValue, - } - }); - - }, TEST_CASE_TIMEOUT); - - it('should retrieve multiple parameters with decryption', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[4]); - const expectedParameterNameA = paramEncryptedA.substring( - paramEncryptedA.lastIndexOf('/') + 1 - ); - const expectedParameterNameB = paramEncryptedB.substring( - paramEncryptedB.lastIndexOf('/') + 1 - ); - - expect(testLog).toStrictEqual({ - test: 'get-multiple-decrypt', - value: { - [expectedParameterNameA]: paramEncryptedAValue, - [expectedParameterNameB]: paramEncryptedBValue, - } - }); - - }, TEST_CASE_TIMEOUT); - - // Test 6 - get multiple parameters by name with default options - it('should retrieve multiple parameters by name', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[5]); + it( + 'should retrieve multiple parameters recursively', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[3]); + const expectedParameterNameA = paramA.substring( + paramA.lastIndexOf('/') + 1 + ); + const expectedParameterNameB = paramB.substring( + paramB.lastIndexOf('/') + 1 + ); + + expect(testLog).toStrictEqual({ + test: 'get-multiple-recursive', + value: { + [expectedParameterNameA]: paramAValue, + [expectedParameterNameB]: paramBValue, + }, + }); + }, + TEST_CASE_TIMEOUT + ); + + it( + 'should retrieve multiple parameters with decryption', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[4]); + const expectedParameterNameA = paramEncryptedA.substring( + paramEncryptedA.lastIndexOf('/') + 1 + ); + const expectedParameterNameB = paramEncryptedB.substring( + paramEncryptedB.lastIndexOf('/') + 1 + ); + + expect(testLog).toStrictEqual({ + test: 'get-multiple-decrypt', + value: { + [expectedParameterNameA]: paramEncryptedAValue, + [expectedParameterNameB]: paramEncryptedBValue, + }, + }); + }, + TEST_CASE_TIMEOUT + ); - expect(testLog).toStrictEqual({ - test: 'get-multiple-by-name', - value: { - [paramA]: paramAValue, - [paramB]: paramBValue, - } - }); + // Test 6 - get multiple parameters by name with default options + it( + 'should retrieve multiple parameters by name', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[5]); + + expect(testLog).toStrictEqual({ + test: 'get-multiple-by-name', + value: { + [paramA]: paramAValue, + [paramB]: paramBValue, + }, + }); + }, + TEST_CASE_TIMEOUT + ); - }, TEST_CASE_TIMEOUT); - // Test 7 - get multiple parameters by name, some of them encrypted and some not - it('should retrieve multiple parameters by name with mixed decryption', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[6]); - - expect(testLog).toStrictEqual({ - test: 'get-multiple-by-name-mixed-decrypt', - value: { - [paramEncryptedA]: paramEncryptedAValue, - [paramEncryptedB]: paramEncryptedBValue, - [paramA]: paramAValue, - } - }); - - }, TEST_CASE_TIMEOUT); + it( + 'should retrieve multiple parameters by name with mixed decryption', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[6]); + + expect(testLog).toStrictEqual({ + test: 'get-multiple-by-name-mixed-decrypt', + value: { + [paramEncryptedA]: paramEncryptedAValue, + [paramEncryptedB]: paramEncryptedBValue, + [paramA]: paramAValue, + }, + }); + }, + TEST_CASE_TIMEOUT + ); // Test 8 - get parameter twice with middleware, which counts the number // of requests, we check later if we only called SSM API once - it('should retrieve single parameter cached', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[7]); - - expect(testLog).toStrictEqual({ - test: 'get-cached', - value: 1 - }); - - }, TEST_CASE_TIMEOUT); + it( + 'should retrieve single parameter cached', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[7]); + + expect(testLog).toStrictEqual({ + test: 'get-cached', + value: 1, + }); + }, + TEST_CASE_TIMEOUT + ); // Test 9 - get parameter twice, but force fetch 2nd time, // we count number of SDK requests and check that we made two API calls - it('should retrieve single parameter twice without caching', async () => { - - const logs = invocationLogs[0].getFunctionLogs(); - const testLog = InvocationLogs.parseFunctionLog(logs[8]); - - expect(testLog).toStrictEqual({ - test: 'get-forced', - value: 2 - }); - - }, TEST_CASE_TIMEOUT); - + it( + 'should retrieve single parameter twice without caching', + async () => { + const logs = invocationLogs[0].getFunctionLogs(); + const testLog = InvocationLogs.parseFunctionLog(logs[8]); + + expect(testLog).toStrictEqual({ + test: 'get-forced', + value: 2, + }); + }, + TEST_CASE_TIMEOUT + ); }); afterAll(async () => { @@ -319,5 +380,4 @@ describe(`parameters E2E tests (ssmProvider) for runtime: ${runtime}`, () => { await destroyStack(integTestApp, stack); } }, TEARDOWN_TIMEOUT); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/helpers/cdkAspectGrantAccess.ts b/packages/parameters/tests/helpers/cdkAspectGrantAccess.ts index 68f755a3fc..5d832509df 100644 --- a/packages/parameters/tests/helpers/cdkAspectGrantAccess.ts +++ b/packages/parameters/tests/helpers/cdkAspectGrantAccess.ts @@ -7,7 +7,9 @@ import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; import { CfnDeployment } from 'aws-cdk-lib/aws-appconfig'; import { StringParameter, IStringParameter } from 'aws-cdk-lib/aws-ssm'; -const isStringParameterGeneric = (parameter: IConstruct): parameter is StringParameter | IStringParameter => +const isStringParameterGeneric = ( + parameter: IConstruct +): parameter is StringParameter | IStringParameter => parameter.hasOwnProperty('parameterArn'); /** @@ -21,65 +23,77 @@ const isStringParameterGeneric = (parameter: IConstruct): parameter is StringPar * This aspect allows us to grant access to specific resources to all Lambda functions in a stack * after the stack tree has been generated and before the stack is deployed. This aspect is * used to grant access to different resource types (DynamoDB tables, SSM parameters, etc.). - * + * * @see {@link https://docs.aws.amazon.com/cdk/v2/guide/aspects.html CDK Docs - Aspects} */ export class ResourceAccessGranter implements IAspect { - private readonly resources: Table[] | Secret[] | StringParameter[] | IStringParameter[] | CfnDeployment[]; + private readonly resources: + | Table[] + | Secret[] + | StringParameter[] + | IStringParameter[] + | CfnDeployment[]; - public constructor(resources: Table[] | Secret[] | StringParameter[] | IStringParameter[] | CfnDeployment[]) { + public constructor( + resources: + | Table[] + | Secret[] + | StringParameter[] + | IStringParameter[] + | CfnDeployment[] + ) { this.resources = resources; } public visit(node: IConstruct): void { // See that we're dealing with a Function if (node instanceof NodejsFunction) { - // Grant access to the resources - this.resources.forEach((resource: Table | Secret | StringParameter | IStringParameter | CfnDeployment) => { + this.resources.forEach( + ( + resource: + | Table + | Secret + | StringParameter + | IStringParameter + | CfnDeployment + ) => { + if (resource instanceof Table) { + resource.grantReadData(node); + } else if (resource instanceof Secret) { + resource.grantRead(node); + } else if (isStringParameterGeneric(resource)) { + resource.grantRead(node); - if (resource instanceof Table) { - resource.grantReadData(node); - } else if ( - resource instanceof Secret - ) { - resource.grantRead(node); - } else if (isStringParameterGeneric(resource)) { - resource.grantRead(node); - - // Grant access also to the path of the parameter - node.addToRolePolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - actions: [ - 'ssm:GetParametersByPath', - ], - resources: [ - resource.parameterArn.split(':').slice(0, -1).join(':'), - ], - }), - ); - } else if (resource instanceof CfnDeployment) { - const appConfigConfigurationArn = Stack.of(node).formatArn({ - service: 'appconfig', - resource: `application/${resource.applicationId}/environment/${resource.environmentId}/configuration/${resource.configurationProfileId}`, - }); - - node.addToRolePolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - actions: [ - 'appconfig:StartConfigurationSession', - 'appconfig:GetLatestConfiguration', - ], - resources: [ - appConfigConfigurationArn, - ], - }), - ); - } + // Grant access also to the path of the parameter + node.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ['ssm:GetParametersByPath'], + resources: [ + resource.parameterArn.split(':').slice(0, -1).join(':'), + ], + }) + ); + } else if (resource instanceof CfnDeployment) { + const appConfigConfigurationArn = Stack.of(node).formatArn({ + service: 'appconfig', + resource: `application/${resource.applicationId}/environment/${resource.environmentId}/configuration/${resource.configurationProfileId}`, + }); - }); + node.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + 'appconfig:StartConfigurationSession', + 'appconfig:GetLatestConfiguration', + ], + resources: [appConfigConfigurationArn], + }) + ); + } + } + ); } } } diff --git a/packages/parameters/tests/helpers/parametersUtils.ts b/packages/parameters/tests/helpers/parametersUtils.ts index 89a8c52efc..f351773f5b 100644 --- a/packages/parameters/tests/helpers/parametersUtils.ts +++ b/packages/parameters/tests/helpers/parametersUtils.ts @@ -12,13 +12,13 @@ import { } from 'aws-cdk-lib/aws-appconfig'; import { AwsCustomResource, - AwsCustomResourcePolicy + AwsCustomResourcePolicy, } from 'aws-cdk-lib/custom-resources'; import { marshall } from '@aws-sdk/util-dynamodb'; export type CreateDynamoDBTableOptions = { - stack: Stack - id: string + stack: Stack; + id: string; } & TableProps; const createDynamoDBTable = (options: CreateDynamoDBTableOptions): Table => { @@ -33,50 +33,48 @@ const createDynamoDBTable = (options: CreateDynamoDBTableOptions): Table => { }; export type AppConfigResourcesOptions = { - stack: Stack - applicationName: string - environmentName: string - deploymentStrategyName: string + stack: Stack; + applicationName: string; + environmentName: string; + deploymentStrategyName: string; }; type AppConfigResourcesOutput = { - application: CfnApplication - environment: CfnEnvironment - deploymentStrategy: CfnDeploymentStrategy + application: CfnApplication; + environment: CfnEnvironment; + deploymentStrategy: CfnDeploymentStrategy; }; /** * Utility function to create the base resources for an AppConfig application. */ -const createBaseAppConfigResources = (options: AppConfigResourcesOptions): AppConfigResourcesOutput => { - const { - stack, - applicationName, - environmentName, - deploymentStrategyName, - } = options; +const createBaseAppConfigResources = ( + options: AppConfigResourcesOptions +): AppConfigResourcesOutput => { + const { stack, applicationName, environmentName, deploymentStrategyName } = + options; // create a new app config application. - const application = new CfnApplication( - stack, - 'application', - { - name: applicationName, - } - ); + const application = new CfnApplication(stack, 'application', { + name: applicationName, + }); const environment = new CfnEnvironment(stack, 'environment', { name: environmentName, applicationId: application.ref, }); - const deploymentStrategy = new CfnDeploymentStrategy(stack, 'deploymentStrategy', { - name: deploymentStrategyName, - deploymentDurationInMinutes: 0, - growthFactor: 100, - replicateTo: 'NONE', - finalBakeTimeInMinutes: 0, - }); + const deploymentStrategy = new CfnDeploymentStrategy( + stack, + 'deploymentStrategy', + { + name: deploymentStrategyName, + deploymentDurationInMinutes: 0, + growthFactor: 100, + replicateTo: 'NONE', + finalBakeTimeInMinutes: 0, + } + ); return { application, @@ -86,22 +84,24 @@ const createBaseAppConfigResources = (options: AppConfigResourcesOptions): AppCo }; export type CreateAppConfigConfigurationProfileOptions = { - stack: Stack - name: string - application: CfnApplication - environment: CfnEnvironment - deploymentStrategy: CfnDeploymentStrategy - type: 'AWS.Freeform' | 'AWS.AppConfig.FeatureFlags' + stack: Stack; + name: string; + application: CfnApplication; + environment: CfnEnvironment; + deploymentStrategy: CfnDeploymentStrategy; + type: 'AWS.Freeform' | 'AWS.AppConfig.FeatureFlags'; content: { - contentType: 'application/json' | 'application/x-yaml' | 'text/plain' - content: string - } + contentType: 'application/json' | 'application/x-yaml' | 'text/plain'; + content: string; + }; }; /** * Utility function to create an AppConfig configuration profile and deployment. */ -const createAppConfigConfigurationProfile = (options: CreateAppConfigConfigurationProfileOptions): CfnDeployment => { +const createAppConfigConfigurationProfile = ( + options: CreateAppConfigConfigurationProfileOptions +): CfnDeployment => { const { stack, name, @@ -111,19 +111,27 @@ const createAppConfigConfigurationProfile = (options: CreateAppConfigConfigurati type, content, } = options; - - const configProfile = new CfnConfigurationProfile(stack, `${name}-configProfile`, { - name, - applicationId: application.ref, - locationUri: 'hosted', - type, - }); - const configVersion = new CfnHostedConfigurationVersion(stack, `${name}-configVersion`, { - applicationId: application.ref, - configurationProfileId: configProfile.ref, - ...content - }); + const configProfile = new CfnConfigurationProfile( + stack, + `${name}-configProfile`, + { + name, + applicationId: application.ref, + locationUri: 'hosted', + type, + } + ); + + const configVersion = new CfnHostedConfigurationVersion( + stack, + `${name}-configVersion`, + { + applicationId: application.ref, + configurationProfileId: configProfile.ref, + ...content, + } + ); return new CfnDeployment(stack, `${name}-deployment`, { applicationId: application.ref, @@ -135,13 +143,15 @@ const createAppConfigConfigurationProfile = (options: CreateAppConfigConfigurati }; export type CreateSSMSecureStringOptions = { - stack: Stack - id: string - name: string - value: string + stack: Stack; + id: string; + name: string; + value: string; }; -const createSSMSecureString = (options: CreateSSMSecureStringOptions): IStringParameter => { +const createSSMSecureString = ( + options: CreateSSMSecureStringOptions +): IStringParameter => { const { stack, id, name, value } = options; const paramCreator = new AwsCustomResource(stack, `create-${id}`, { @@ -176,13 +186,15 @@ const createSSMSecureString = (options: CreateSSMSecureStringOptions): IStringPa }; export type PutDynamoDBItemOptions = { - stack: Stack - id: string - table: Table - item: Record + stack: Stack; + id: string; + table: Table; + item: Record; }; -const putDynamoDBItem = async (options: PutDynamoDBItemOptions): Promise => { +const putDynamoDBItem = async ( + options: PutDynamoDBItemOptions +): Promise => { const { stack, id, table, item } = options; new AwsCustomResource(stack, id, { diff --git a/packages/parameters/tests/helpers/populateEnvironmentVariables.ts b/packages/parameters/tests/helpers/populateEnvironmentVariables.ts index 7ff0774273..acb0fd0595 100644 --- a/packages/parameters/tests/helpers/populateEnvironmentVariables.ts +++ b/packages/parameters/tests/helpers/populateEnvironmentVariables.ts @@ -1,7 +1,11 @@ // Reserved variables -process.env._X_AMZN_TRACE_ID = 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; +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) { +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/helpers/sdkMiddlewareRequestCounter.ts b/packages/parameters/tests/helpers/sdkMiddlewareRequestCounter.ts index 0fbff71084..76cea93cc3 100644 --- a/packages/parameters/tests/helpers/sdkMiddlewareRequestCounter.ts +++ b/packages/parameters/tests/helpers/sdkMiddlewareRequestCounter.ts @@ -1,19 +1,19 @@ /** * Middleware to count the number of API calls made by the SDK. - * + * * The AWS SDK for JavaScript v3 uses a middleware stack to manage the execution of * operations. Middleware can be added to the stack to perform custom tasks before * or after an operation is executed. - * + * * This middleware is added to the stack to count the number of API calls (`ROUND_TRIP`) made by the SDK. * This allows us to verify that the SDK is making the expected number of API calls and thus test that * caching or forcing a retrieval are working as expected. - * + * * @see {@link https://aws.amazon.com/blogs/developer/middleware-stack-modular-aws-sdk-js/|AWS Blog - Middleware Stack} */ export const middleware = { - // - counter : 0, + // + counter: 0, applyToStack: (stack) => { // Middleware added to mark start and end of an complete API call. stack.add( @@ -34,4 +34,4 @@ export const middleware = { { tags: ['ROUND_TRIP'] } ); }, -}; \ No newline at end of file +}; diff --git a/packages/parameters/tests/helpers/tinyLogger.ts b/packages/parameters/tests/helpers/tinyLogger.ts index 0effa723ae..4fd4603d53 100644 --- a/packages/parameters/tests/helpers/tinyLogger.ts +++ b/packages/parameters/tests/helpers/tinyLogger.ts @@ -2,20 +2,23 @@ import { Console } from 'console'; /** * A tiny logger that logs to stdout and stderr. - * + * * This is used to log the results of the function code during the integration tests. * We use this instead of the global console object because we want to log pure JSON objects. * In Node.js runtimes, AWS Lambda usually patches the global console object to inject some * metadata like the request ID. This is not desirable in our case because we want to log pure * JSON objects to stdout and stderr. - * + * * This allows us to get the logs when invoking the function and parse them to verify that * the function code is working as expected. */ export class TinyLogger { - private console = new Console({ stdout: process.stdout, stderr: process.stderr }); + private console = new Console({ + stdout: process.stdout, + stderr: process.stderr, + }); public log(message: unknown): void { this.console.log(JSON.stringify(message)); } -} \ No newline at end of file +} diff --git a/packages/parameters/tests/unit/AppConfigProvider.test.ts b/packages/parameters/tests/unit/AppConfigProvider.test.ts index f5924dd191..0fa4af63d2 100644 --- a/packages/parameters/tests/unit/AppConfigProvider.test.ts +++ b/packages/parameters/tests/unit/AppConfigProvider.test.ts @@ -181,7 +181,7 @@ describe('Class: AppConfigProvider', () => { }).toThrow(); }); - test('when configuration response doesn\'t have the next token it should force a new session by removing the stored token', async () => { + test('when configuration response does not have the next token it should force a new session by removing the stored token', async () => { // Prepare class AppConfigProviderMock extends AppConfigProvider { public _addToStore(key: string, value: string): void { @@ -214,7 +214,7 @@ describe('Class: AppConfigProvider', () => { expect(provider._storeHas(name)).toBe(false); }); - test('when session response doesn\'t have an initial token, it throws an error', async () => { + test('when session response does not have an initial token, it throws an error', async () => { // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -232,7 +232,6 @@ describe('Class: AppConfigProvider', () => { }); test('when session returns an empty configuration on the second call, it returns the last value', async () => { - // Prepare const options: AppConfigProviderOptions = { application: 'MyApp', @@ -302,7 +301,7 @@ describe('Class: ExpirableValue', () => { // Prepare const seconds = 10; const nowTimestamp = Date.now(); - const futureTimestampSeconds = nowTimestamp/1000+(seconds); + const futureTimestampSeconds = nowTimestamp / 1000 + seconds; // Act const expirableValue = new ExpirableValue('foo', seconds); @@ -322,7 +321,7 @@ describe('Class: ExpirableValue', () => { // Assess expect(expirableValue.isExpired()).toBeFalsy(); - }); + }); test('when called, it returns false when maxAge is in the past', () => { // Prepare @@ -333,6 +332,6 @@ describe('Class: ExpirableValue', () => { // Assess expect(expirableValue.isExpired()).toBeTruthy(); - }); + }); }); -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/BaseProvider.test.ts b/packages/parameters/tests/unit/BaseProvider.test.ts index 71f5637e2a..6fb45c2010 100644 --- a/packages/parameters/tests/unit/BaseProvider.test.ts +++ b/packages/parameters/tests/unit/BaseProvider.test.ts @@ -16,7 +16,6 @@ import { toBase64 } from '@aws-sdk/util-base64-node'; const encoder = new TextEncoder(); describe('Class: BaseProvider', () => { - afterEach(() => { jest.clearAllMocks(); }); @@ -25,7 +24,7 @@ describe('Class: BaseProvider', () => { public _add(key: string, value: ExpirableValue): void { this.store.set(key, value); } - + public _get(_name: string): Promise { throw Error('Not implemented.'); } @@ -33,8 +32,10 @@ describe('Class: BaseProvider', () => { public _getKeyTest(key: string): ExpirableValue | undefined { return this.store.get(key); } - - public _getMultiple(_path: string): Promise> { + + public _getMultiple( + _path: string + ): Promise> { throw Error('Not implemented.'); } @@ -44,9 +45,7 @@ describe('Class: BaseProvider', () => { } describe('Method: addToCache', () => { - test('when called with a value and maxAge equal to 0, it skips the cache entirely', () => { - // Prepare const provider = new TestProvider(); @@ -55,11 +54,9 @@ describe('Class: BaseProvider', () => { // Assess expect(provider._getKeyTest('my-key')).toBeUndefined(); - }); test('when called with a value and maxAge, it places the value in the cache', () => { - // Prepare const provider = new TestProvider(); @@ -67,80 +64,90 @@ describe('Class: BaseProvider', () => { provider.addToCache('my-key', 'my-value', 5000); // Assess - expect(provider._getKeyTest('my-key')).toEqual(expect.objectContaining({ - value: 'my-value' - })); - + expect(provider._getKeyTest('my-key')).toEqual( + expect.objectContaining({ + value: 'my-value', + }) + ); }); - }); describe('Method: get', () => { - test('when the underlying _get method throws an error, it throws a GetParameterError', async () => { - // Prepare const provider = new TestProvider(); // Act & Assess - await expect(provider.get('my-parameter')).rejects.toThrowError(GetParameterError); - + await expect(provider.get('my-parameter')).rejects.toThrowError( + GetParameterError + ); }); test('when called and a cached value is available, it returns an the cached value', async () => { - // Prepare const provider = new TestProvider(); - provider._add([ 'my-parameter', undefined ].toString(), new ExpirableValue('my-value', 5000)); - + provider._add( + ['my-parameter', undefined].toString(), + new ExpirableValue('my-value', 5000) + ); + // Act const values = await provider.get('my-parameter'); - + // Assess expect(values).toEqual('my-value'); - }); test('when called with forceFetch, even whith cached value available, it returns the remote value', async () => { - // Prepare const mockData = 'my-remote-value'; const provider = new TestProvider(); - jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); - provider._add([ 'my-parameter', undefined ].toString(), new ExpirableValue('my-value', 5000)); - + jest + .spyOn(provider, '_get') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); + provider._add( + ['my-parameter', undefined].toString(), + new ExpirableValue('my-value', 5000) + ); + // Act const values = await provider.get('my-parameter', { forceFetch: true }); - + // Assess expect(values).toEqual('my-remote-value'); - }); test('when called and cached value is expired, it returns the remote value', async () => { - // Prepare const mockData = 'my-remote-value'; const provider = new TestProvider(); - jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_get') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); const expirableValue = new ExpirableValue('my-other-value', 0); jest.spyOn(expirableValue, 'isExpired').mockImplementation(() => true); - provider._add([ 'my-path', undefined ].toString(), expirableValue); - + provider._add(['my-path', undefined].toString(), expirableValue); + // Act const values = await provider.get('my-parameter'); - + // Assess 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 () => { - // Prepare const mockData = JSON.stringify({ foo: 'bar' }); const provider = new TestProvider(); - jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_get') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act const value = await provider.get('my-parameter', { transform: 'json' }); @@ -148,28 +155,35 @@ describe('Class: BaseProvider', () => { // Assess expect(typeof value).toBe('object'); expect(value).toMatchObject({ - 'foo': 'bar' + foo: 'bar', }); - }); - - test('when called with a json transform, and the value is NOT a valid string representation of a JSON, it throws', async () => { + test('when called with a json transform, and the value is NOT a valid string representation of a JSON, it throws', async () => { // Prepare const mockData = `${JSON.stringify({ foo: 'bar' })}{`; const provider = new TestProvider(); - jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_get') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act & Assess - await expect(provider.get('my-parameter', { transform: 'json' })).rejects.toThrowError(TransformParameterError); - + await expect( + provider.get('my-parameter', { transform: 'json' }) + ).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 () => { // Prepare const mockData = toBase64(encoder.encode('my-value')); const provider = new TestProvider(); - jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_get') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act const value = await provider.get('my-parameter', { transform: 'binary' }); @@ -177,27 +191,36 @@ describe('Class: BaseProvider', () => { // Assess expect(typeof value).toBe('string'); 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 () => { - // Prepare const mockData = 'qw'; const provider = new TestProvider(); - jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_get') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act & Assess - await expect(provider.get('my-parameter', { transform: 'binary' })).rejects.toThrowError(TransformParameterError); - + await expect( + provider.get('my-parameter', { transform: 'binary' }) + ).rejects.toThrowError(TransformParameterError); }); - - test('when called with no transform, and the value is a valid binary, it returns the binary as-is', async () => { + test('when called with no transform, and the value is a valid binary, it returns the binary as-is', 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))); + jest + .spyOn(provider, '_get') + .mockImplementation( + () => + new Promise((resolve, _reject) => + resolve(mockData as unknown as string) + ) + ); // Act const value = await provider.get('my-parameter'); @@ -205,76 +228,97 @@ describe('Class: BaseProvider', () => { // Assess expect(value).toBeInstanceOf(Uint8Array); 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 () => { + test('when called with a binary transform, and the value is a valid binary but NOT base64 encoded, it throws', 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))); + jest + .spyOn(provider, '_get') + .mockImplementation( + () => + new Promise((resolve, _reject) => + resolve(mockData as unknown as string) + ) + ); // Act & Assess - await expect(provider.get('my-parameter', { transform: 'binary' })).rejects.toThrowError(TransformParameterError); - + await expect( + provider.get('my-parameter', { transform: 'binary' }) + ).rejects.toThrowError(TransformParameterError); }); - - test('when called with an auto transform, and the value is a valid JSON, it returns the parsed value', async () => { + test('when called with an auto transform, and the value is a valid JSON, it returns the parsed value', async () => { // Prepare const mockData = JSON.stringify({ foo: 'bar' }); const provider = new TestProvider(); - jest.spyOn(provider, '_get').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_get') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act - const value = await provider.get('my-parameter.json', { transform: 'auto' }); + const value = await provider.get('my-parameter.json', { + transform: 'auto', + }); // Assess expect(value).toStrictEqual({ foo: 'bar' }); - }); - }); describe('Method: getMultiple', () => { test('when the underlying _getMultiple throws an error, it throws a GetParameterError', async () => { - // Prepare const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((_resolve, reject) => reject(new Error('Some error.')))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => + new Promise((_resolve, reject) => reject(new Error('Some error.'))) + ); // Act & Assess - await expect(provider.getMultiple('my-parameter')).rejects.toThrowError(GetParameterError); - + await expect(provider.getMultiple('my-parameter')).rejects.toThrowError( + GetParameterError + ); }); 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 () => { - // Prepare - const mockData = { 'A': JSON.stringify({ foo: 'bar' }) }; + const mockData = { A: JSON.stringify({ foo: 'bar' }) }; const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); - + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); + // Act - const values = await provider.getMultiple('my-path', { transform: 'json' }); - + const values = await provider.getMultiple('my-path', { + transform: 'json', + }); + // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A': { - 'foo': 'bar' - } + A: { + foo: 'bar', + }, }); - }); - - test('when called, it returns an object with the values', async () => { + test('when called, it returns an object with the values', async () => { // Prepare - const mockData = { 'A': 'foo', 'B': 'bar' }; + const mockData = { A: 'foo', B: 'bar' }; const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act const values = await provider.getMultiple('my-path'); @@ -282,153 +326,195 @@ describe('Class: BaseProvider', () => { // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A': 'foo', - 'B': 'bar' + A: 'foo', + B: 'bar', }); - }); 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 () => { - // Prepare - const mockData = { 'A': JSON.stringify({ foo: 'bar' }), 'B': `${JSON.stringify({ foo: 'bar' })}{` }; + 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))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act - const values = await provider.getMultiple('my-path', { transform: 'json' }); + const values = await provider.getMultiple('my-path', { + transform: 'json', + }); // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A': { - 'foo': 'bar' + A: { + foo: 'bar', }, - 'B': undefined + B: undefined, }); - }); 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 () => { - // Prepare - const mockData = { 'A': `${JSON.stringify({ foo: 'bar' })}{` }; + const mockData = { A: `${JSON.stringify({ foo: 'bar' })}{` }; const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act & Assess - await expect(provider.getMultiple('my-path', { transform: 'json', throwOnTransformError: true })).rejects.toThrowError(TransformParameterError); - + await expect( + provider.getMultiple('my-path', { + transform: 'json', + throwOnTransformError: true, + }) + ).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 () => { - // Prepare - const mockData = { 'A': toBase64(encoder.encode('my-value')).toString() }; + const mockData = { A: toBase64(encoder.encode('my-value')).toString() }; const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act - const values = await provider.getMultiple('my-path', { transform: 'binary' }); + const values = await provider.getMultiple('my-path', { + transform: 'binary', + }); // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A': 'my-value' + A: 'my-value', }); - }); 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 () => { - // Prepare - const mockData = { 'A': toBase64(encoder.encode('my-value')), 'B': 'qw' }; + 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))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act - const values = await provider.getMultiple('my-path', { transform: 'binary' }); + const values = await provider.getMultiple('my-path', { + transform: 'binary', + }); // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A': 'my-value', - 'B': undefined + A: 'my-value', + B: undefined, }); - }); 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 () => { - // Prepare - const mockData = { 'A': 'qw' }; + const mockData = { A: 'qw' }; const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act & Assess - await expect(provider.getMultiple('my-path', { transform: 'binary', throwOnTransformError: true })).rejects.toThrowError(TransformParameterError); - + await expect( + provider.getMultiple('my-path', { + transform: 'binary', + throwOnTransformError: true, + }) + ).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 () => { - // 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))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act - const values = await provider.getMultiple('my-path', { transform: 'auto' }); + const values = await provider.getMultiple('my-path', { + transform: 'auto', + }); // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A.binary': 'my-value' + '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 () => { - // 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))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act - const values = await provider.getMultiple('my-path', { transform: 'auto' }); + const values = await provider.getMultiple('my-path', { + transform: 'auto', + }); // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A.foo': mockBinary + '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 () => { - // Prepare - const mockData = { 'A': undefined }; + const mockData = { A: undefined }; const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); // Act - const values = await provider.getMultiple('my-path', { transform: 'auto' }); + const values = await provider.getMultiple('my-path', { + transform: 'auto', + }); expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A': undefined + A: undefined, }); - }); test('when called and values cached are available, it returns an object with the cached values', async () => { - // Prepare const provider = new TestProvider(); - provider._add([ 'my-path', undefined ].toString(), new ExpirableValue({ 'A': 'my-value' }, 60000)); + provider._add( + ['my-path', undefined].toString(), + new ExpirableValue({ A: 'my-value' }, 60000) + ); // Act const values = await provider.getMultiple('my-path'); @@ -436,20 +522,22 @@ describe('Class: BaseProvider', () => { // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A': 'my-value', + A: 'my-value', }); - }); test('when called and values cached are expired, it returns an object with the remote values', async () => { - // Prepare - const mockData = { 'A': 'my-value' }; + const mockData = { A: 'my-value' }; const provider = new TestProvider(); - jest.spyOn(provider, '_getMultiple').mockImplementation(() => new Promise((resolve, _reject) => resolve(mockData))); - const expirableValue = new ExpirableValue({ 'B': 'my-other-value' }, 0); + jest + .spyOn(provider, '_getMultiple') + .mockImplementation( + () => new Promise((resolve, _reject) => resolve(mockData)) + ); + const expirableValue = new ExpirableValue({ B: 'my-other-value' }, 0); jest.spyOn(expirableValue, 'isExpired').mockImplementation(() => true); - provider._add([ 'my-path', undefined ].toString(), expirableValue); + provider._add(['my-path', undefined].toString(), expirableValue); // Act const values = await provider.getMultiple('my-path'); @@ -457,49 +545,43 @@ describe('Class: BaseProvider', () => { // Assess expect(typeof values).toBe('object'); expect(values).toMatchObject({ - 'A': 'my-value', + A: 'my-value', }); - }); - }); describe('Method: clearCache', () => { - test('when called, it clears the store', () => { - // Prepare const provider = new TestProvider(); - provider._add([ 'my-path', undefined ].toString(), new ExpirableValue({ 'B': 'my-other-value' }, 0)); + provider._add( + ['my-path', undefined].toString(), + new ExpirableValue({ B: 'my-other-value' }, 0) + ); // Act provider.clearCache(); // Assess expect(provider._getStoreSize()).toBe(0); - }); - }); - }); describe('Function: clearCaches', () => { - class TestProvider extends BaseProvider { - public _get(_name: string): Promise { throw Error('Not implemented.'); } - - public _getMultiple(_path: string): Promise> { + + public _getMultiple( + _path: string + ): Promise> { throw Error('Not implemented.'); } - } test('when called, it clears all the caches', () => { - // Prepare const provider1 = new TestProvider(); const provider2 = new TestProvider(); @@ -514,7 +596,5 @@ describe('Function: clearCaches', () => { // Assess expect(provider1Spy).toBeCalledTimes(1); expect(provider2Spy).toBeCalledTimes(1); - }); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/DynamoDBProvider.test.ts b/packages/parameters/tests/unit/DynamoDBProvider.test.ts index 86ebc01890..43f011ed28 100644 --- a/packages/parameters/tests/unit/DynamoDBProvider.test.ts +++ b/packages/parameters/tests/unit/DynamoDBProvider.test.ts @@ -4,22 +4,27 @@ * @group unit/parameters/DynamoDBProvider/class */ import { DynamoDBProvider } from '../../src/dynamodb'; -import { DynamoDBClient, GetItemCommand, QueryCommand } from '@aws-sdk/client-dynamodb'; -import type { GetItemCommandInput, QueryCommandInput } from '@aws-sdk/client-dynamodb'; +import { + DynamoDBClient, + GetItemCommand, + QueryCommand, +} from '@aws-sdk/client-dynamodb'; +import type { + GetItemCommandInput, + QueryCommandInput, +} from '@aws-sdk/client-dynamodb'; import type { DynamoDBProviderOptions } from '../../src/types/DynamoDBProvider'; import { marshall } from '@aws-sdk/util-dynamodb'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; describe('Class: DynamoDBProvider', () => { - beforeEach(() => { jest.clearAllMocks(); }); describe('Method: constructor', () => { test('when the class instantiates without SDK client and client config it has default options', async () => { - // Prepare const options: DynamoDBProviderOptions = { tableName: 'test-table', @@ -37,7 +42,6 @@ describe('Class: DynamoDBProvider', () => { }); test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { - // Prepare const options: DynamoDBProviderOptions = { tableName: 'test-table', @@ -58,7 +62,6 @@ describe('Class: DynamoDBProvider', () => { }); test('when the user provides an SDK client in the options, the class instantiates with it', async () => { - // Prepare const awsSdkV3Client = new DynamoDBClient({ serviceId: 'with-custom-sdk-client', @@ -96,9 +99,7 @@ describe('Class: DynamoDBProvider', () => { }); describe('Method: _get', () => { - test('when called and the sdk client returns no items, it returns undefined', async () => { - // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', @@ -111,27 +112,27 @@ describe('Class: DynamoDBProvider', () => { // Assess expect(parameter).toBeUndefined(); - }); - - test('when called with only a name, it gets the parameter using the default attribute values and table name', async () => { + test('when called with only a name, it gets the parameter using the default attribute values and table 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, - }) - }); + const client = mockClient(DynamoDBClient) + .on(GetItemCommand) + .resolves({ + Item: marshall({ + id: parameterName, + value: parameterValue, + }), + }); // Act const parameter = await provider.get(parameterName); - + // Assess expect(client).toReceiveCommandWith(GetItemCommand, { TableName: 'test-table', @@ -144,11 +145,9 @@ describe('Class: DynamoDBProvider', () => { ProjectionExpression: '#value', }); 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 () => { - // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', @@ -157,12 +156,14 @@ describe('Class: DynamoDBProvider', () => { }); const parameterName = 'foo'; const parameterValue = 'bar'; - const client = mockClient(DynamoDBClient).on(GetItemCommand).resolves({ - Item: marshall({ - key: parameterName, - val: parameterValue, - }) - }); + const client = mockClient(DynamoDBClient) + .on(GetItemCommand) + .resolves({ + Item: marshall({ + key: parameterName, + val: parameterValue, + }), + }); // Act const parameter = await provider.get(parameterName); @@ -179,29 +180,29 @@ describe('Class: DynamoDBProvider', () => { ProjectionExpression: '#value', }); expect(parameter).toEqual(parameterValue); - }); test('when called with name and sdkOptions, it gets the parameter using the options provided', 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, - }) - }); + const client = mockClient(DynamoDBClient) + .on(GetItemCommand) + .resolves({ + Item: marshall({ + id: parameterName, + value: parameterValue, + }), + }); // Act const parameter = await provider.get(parameterName, { sdkOptions: { ConsistentRead: true, - } + }, }); // Assess @@ -217,23 +218,23 @@ describe('Class: DynamoDBProvider', () => { ConsistentRead: true, }); expect(parameter).toEqual(parameterValue); - }); test('when called with sdkOptions that override arguments passed to the method, it gets the parameter using the arguments', 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, - }) - }); + const client = mockClient(DynamoDBClient) + .on(GetItemCommand) + .resolves({ + Item: marshall({ + id: parameterName, + value: parameterValue, + }), + }); // Act await provider.get(parameterName, { @@ -257,39 +258,37 @@ describe('Class: DynamoDBProvider', () => { }, ProjectionExpression: '#value', }); - }); - }); describe('Method: _getMultiple', () => { - test('when called with only a path, it gets the parameters using the default attribute values and table name', 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' - }), - ] - }); + 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', + }), + ], + }); // Act const parameters = await provider.getMultiple(parameterPath); @@ -304,7 +303,7 @@ describe('Class: DynamoDBProvider', () => { ExpressionAttributeNames: { '#key': 'id', '#sk': 'sk', - '#value': 'value' + '#value': 'value', }, ProjectionExpression: '#sk, #value', }); @@ -313,11 +312,9 @@ describe('Class: DynamoDBProvider', () => { b: 'parameter-b', c: 'parameter-c', }); - }); test('when called with only a path, it gets the parameter using the attribute values and table name provided to the constructor', async () => { - // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', @@ -326,25 +323,27 @@ 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' - }), - ] - }); + 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', + }), + ], + }); // Act const parameters = await provider.getMultiple(parameterPath); @@ -359,7 +358,7 @@ describe('Class: DynamoDBProvider', () => { ExpressionAttributeNames: { '#key': 'key', '#sk': 'sort', - '#value': 'val' + '#value': 'val', }, ProjectionExpression: '#sk, #value', }); @@ -368,42 +367,42 @@ describe('Class: DynamoDBProvider', () => { b: 'parameter-b', c: 'parameter-c', }); - }); test('when called with a path and sdkOptions, it gets the parameters using the options provided', 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' - }), - ] - }); + 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', + }), + ], + }); // Act const parameters = await provider.getMultiple(parameterPath, { sdkOptions: { ConsistentRead: true, Limit: 10, - } + }, }); // Assess @@ -416,7 +415,7 @@ describe('Class: DynamoDBProvider', () => { ExpressionAttributeNames: { '#key': 'id', '#sk': 'sk', - '#value': 'value' + '#value': 'value', }, ProjectionExpression: '#sk, #value', ConsistentRead: true, @@ -426,28 +425,27 @@ describe('Class: DynamoDBProvider', () => { b: 'parameter-b', c: 'parameter-c', }); - }); test('when multiple pages are found, it returns an object with all the parameters', async () => { - // Prepare const provider = new DynamoDBProvider({ tableName: 'test-table', }); const parameterPath = 'foo'; - mockClient(DynamoDBClient).on(QueryCommand) + mockClient(DynamoDBClient) + .on(QueryCommand) .resolvesOnce({ Items: [ marshall({ id: parameterPath, sk: 'a', - value: 'parameter-a' + value: 'parameter-a', }), marshall({ id: parameterPath, sk: 'b', - value: 'parameter-b' + value: 'parameter-b', }), ], LastEvaluatedKey: marshall({ @@ -460,7 +458,7 @@ describe('Class: DynamoDBProvider', () => { marshall({ id: parameterPath, sk: 'c', - value: 'parameter-c' + value: 'parameter-c', }), ], LastEvaluatedKey: marshall({ @@ -479,35 +477,35 @@ describe('Class: DynamoDBProvider', () => { b: 'parameter-b', c: 'parameter-c', }); - }); test('when called with sdkOptions that override arguments or internals, it discards the ones passed in sdkOptions and leaves others untouched', 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' - }), - ], - }); + 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', + }), + ], + }); // Act await provider.getMultiple(parameterPath, { @@ -521,7 +519,7 @@ describe('Class: DynamoDBProvider', () => { Limit: 10, } as unknown as QueryCommandInput, }); - + // Assess expect(client).toReceiveCommandWith(QueryCommand, { TableName: 'test-table', @@ -532,15 +530,12 @@ describe('Class: DynamoDBProvider', () => { ExpressionAttributeNames: { '#key': 'id', '#sk': 'sk', - '#value': 'value' + '#value': 'value', }, ProjectionExpression: '#sk, #value', ConsistentRead: true, Limit: 10, }); - }); - }); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/SSMProvider.test.ts b/packages/parameters/tests/unit/SSMProvider.test.ts index 4e43670538..1d58232fbe 100644 --- a/packages/parameters/tests/unit/SSMProvider.test.ts +++ b/packages/parameters/tests/unit/SSMProvider.test.ts @@ -8,7 +8,7 @@ import { SSMClient, GetParameterCommand, GetParametersCommand, - GetParametersByPathCommand + GetParametersByPathCommand, } from '@aws-sdk/client-ssm'; import type { GetParametersCommandOutput } from '@aws-sdk/client-ssm'; import { mockClient } from 'aws-sdk-client-mock'; @@ -18,7 +18,7 @@ import type { SSMGetParametersByNameFromCacheOutputType, SSMGetParametersByNameOptions, SSMSplitBatchAndDecryptParametersOutputType, - SSMGetParametersByNameOutputInterface + SSMGetParametersByNameOutputInterface, } from '../../src/types/SSMProvider'; import { ExpirableValue } from '../../src/BaseProvider'; import { toBase64 } from '@aws-sdk/util-base64'; @@ -35,7 +35,6 @@ describe('Class: SSMProvider', () => { describe('Method: constructor', () => { test('when the class instantiates without SDK client and client config it has default options', async () => { - // Prepare const options: SSMProviderOptions = {}; @@ -51,7 +50,6 @@ describe('Class: SSMProvider', () => { }); test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { - // Prepare const options: SSMProviderOptions = { clientConfig: { @@ -71,7 +69,6 @@ describe('Class: SSMProvider', () => { }); test('when the user provides an SDK client in the options, the class instantiates with it', async () => { - // Prepare const awsSdkV3Client = new SSMClient({ serviceId: 'with-custom-sdk-client', @@ -107,7 +104,6 @@ describe('Class: SSMProvider', () => { }); describe('Method: getParametersByName', () => { - class SSMProviderMock extends SSMProvider { public getParametersBatchByName = jest.fn(); public getParametersByNameWithDecryptOption = jest.fn(); @@ -118,7 +114,6 @@ describe('Class: SSMProvider', () => { } test('when called with no parameters to decrypt, it calls both getParametersByNameWithDecryptOption and getParametersBatchByName, then returns', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -143,17 +138,21 @@ describe('Class: SSMProvider', () => { // Act await provider.getParametersByName(parameters, { decrypt: false }); - + // Assess // throwOnTransformError is true by default - expect(provider.getParametersByNameWithDecryptOption).toHaveBeenCalledWith({}, true); - expect(provider.getParametersBatchByName).toHaveBeenCalledWith(parameters, true, false); + expect( + provider.getParametersByNameWithDecryptOption + ).toHaveBeenCalledWith({}, true); + expect(provider.getParametersBatchByName).toHaveBeenCalledWith( + parameters, + true, + false + ); expect(provider.getParametersBatchByName).toHaveBeenCalledTimes(1); - }); test('when called with all parameters to decrypt, it calls only getParametersBatchByName', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -176,14 +175,18 @@ describe('Class: SSMProvider', () => { await provider.getParametersByName(parameters, { decrypt: true }); // Assess - expect(provider.getParametersBatchByName).toHaveBeenCalledWith(parameters, true, true); + expect(provider.getParametersBatchByName).toHaveBeenCalledWith( + parameters, + true, + true + ); expect(provider.getParametersBatchByName).toHaveBeenCalledTimes(1); - expect(provider.getParametersByNameWithDecryptOption).not.toHaveBeenCalled(); - + expect( + provider.getParametersByNameWithDecryptOption + ).not.toHaveBeenCalled(); }); test('when called with some parameters to decrypt, it calls both getParametersByNameWithDecryptOption and getParametersBatchByName, then returns', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -210,30 +213,39 @@ describe('Class: SSMProvider', () => { // Act await provider.getParametersByName(parameters, {}); - + // Assess // throwOnError is true by default - expect(provider.getParametersByNameWithDecryptOption).toHaveBeenCalledWith({ - '/foo/baz': { - transform: 'json', - decrypt: true, - maxAge: 5, + expect( + provider.getParametersByNameWithDecryptOption + ).toHaveBeenCalledWith( + { + '/foo/baz': { + transform: 'json', + decrypt: true, + maxAge: 5, + }, }, - }, true); - expect(provider.getParametersBatchByName).toHaveBeenCalledWith({ - '/foo/bar': { - maxAge: 1000, - decrypt: false, - transform: undefined, + true + ); + expect(provider.getParametersBatchByName).toHaveBeenCalledWith( + { + '/foo/bar': { + maxAge: 1000, + decrypt: false, + transform: undefined, + }, }, - }, true, false); - expect(provider.getParametersByNameWithDecryptOption).toHaveBeenCalledTimes(1); + true, + false + ); + expect( + provider.getParametersByNameWithDecryptOption + ).toHaveBeenCalledTimes(1); expect(provider.getParametersBatchByName).toHaveBeenCalledTimes(1); - }); test('when called and getParametersBatchByName returns an error and throwOnError is false, it returns the errors', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -252,26 +264,23 @@ describe('Class: SSMProvider', () => { response: { '/foo/baz': 'baz', }, - errors: [ - '/foo/bar', - ], + errors: ['/foo/bar'], }); // Act - const result = await provider.getParametersByName(parameters, { decrypt: false, throwOnError: false }); + const result = await provider.getParametersByName(parameters, { + decrypt: false, + throwOnError: false, + }); // Assess expect(result).toEqual({ '/foo/baz': 'baz', - _errors: [ - '/foo/bar', - ], + _errors: ['/foo/bar'], }); - }); test('when called and getParametersBatchByName returns an error and throwOnError is false, it returns the errors', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters: Record = { @@ -284,41 +293,36 @@ describe('Class: SSMProvider', () => { }; provider.getParametersBatchByName.mockResolvedValue({ response: {}, - errors: [ - '/foo/bar', - '/foo/baz', - ], + errors: ['/foo/bar', '/foo/baz'], }); // Act - const result = await provider.getParametersByName(parameters, { decrypt: true, throwOnError: false }); + const result = await provider.getParametersByName(parameters, { + decrypt: true, + throwOnError: false, + }); // Assess expect(result).toEqual({ - _errors: [ - '/foo/bar', - '/foo/baz', - ], + _errors: ['/foo/bar', '/foo/baz'], }); - }); - }); 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 () => { - // Prepare process.env.POWERTOOLS_PARAMETERS_SSM_DECRYPT = 'true'; const provider = new SSMProvider(); const parameterName = 'foo'; const parameterValue = 'foo'; - const client = mockClient(SSMClient).on(GetParameterCommand).resolves({ - Parameter: { - Value: parameterValue, - }, - }); + const client = mockClient(SSMClient) + .on(GetParameterCommand) + .resolves({ + Parameter: { + Value: parameterValue, + }, + }); // Act const value = await provider.get(parameterName); @@ -329,20 +333,20 @@ describe('Class: SSMProvider', () => { WithDecryption: true, }); expect(value).toBe(parameterValue); - }); test('when called without sdkOptions, it gets the parameter using the name and with no decryption', async () => { - // Prepare const provider = new SSMProvider(); const parameterName = 'foo'; const parameterValue = 'foo'; - const client = mockClient(SSMClient).on(GetParameterCommand).resolves({ - Parameter: { - Value: parameterValue, - }, - }); + const client = mockClient(SSMClient) + .on(GetParameterCommand) + .resolves({ + Parameter: { + Value: parameterValue, + }, + }); // Act const value = await provider.get(parameterName); @@ -352,29 +356,27 @@ describe('Class: SSMProvider', () => { Name: parameterName, }); expect(value).toBe(parameterValue); - }); - - test('when called with sdkOptions, it gets the parameter using the parameters', async () => { + test('when called with sdkOptions, it gets the parameter using the parameters', async () => { // Prepare const provider = new SSMProvider(); const client = mockClient(SSMClient).on(GetParameterCommand).resolves({}); const parameterName = 'foo'; // Act - await provider.get(parameterName, { sdkOptions: { WithDecryption: true } }); + await provider.get(parameterName, { + sdkOptions: { WithDecryption: true }, + }); // Assess expect(client).toReceiveCommandWith(GetParameterCommand, { Name: parameterName, WithDecryption: true, }); - }); test('when called with the decrypt option, the WithDecryption parameter is passed to the sdk client', async () => { - // Prepare const provider = new SSMProvider(); const client = mockClient(SSMClient).on(GetParameterCommand).resolves({}); @@ -388,156 +390,157 @@ describe('Class: SSMProvider', () => { Name: parameterName, WithDecryption: true, }); - }); - }); describe('Method: _getMultiple', () => { - test('when called with only a path, it passes it to the sdk', async () => { - // Prepare const provider = new SSMProvider(); - const client = mockClient(SSMClient).on(GetParametersByPathCommand) + const client = mockClient(SSMClient) + .on(GetParametersByPathCommand) .resolves({}); const parameterPath = '/foo'; // Act await provider.getMultiple(parameterPath); - + // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { Path: parameterPath, }); - }); test('when called with a path and sdkOptions, it passes them to the sdk', async () => { - // Prepare const provider = new SSMProvider(); - const client = mockClient(SSMClient).on(GetParametersByPathCommand) + const client = mockClient(SSMClient) + .on(GetParametersByPathCommand) .resolves({ - Parameters: [] + Parameters: [], }); const parameterPath = '/foo'; // Act - await provider.getMultiple(parameterPath, { sdkOptions: { MaxResults: 10 } }); - + await provider.getMultiple(parameterPath, { + sdkOptions: { MaxResults: 10 }, + }); + // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { Path: parameterPath, MaxResults: 10, }); - }); test('when called with no options, it uses the default sdk options', async () => { - // Prepare const provider = new SSMProvider(); - const client = mockClient(SSMClient).on(GetParametersByPathCommand) + const client = mockClient(SSMClient) + .on(GetParametersByPathCommand) .resolves({ - Parameters: [] + Parameters: [], }); const parameterPath = '/foo'; // Act await provider.getMultiple(parameterPath); - + // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { Path: parameterPath, }); - }); - - test('when called with decrypt or recursive, it passes them to the sdk', async () => { + test('when called with decrypt or recursive, it passes them to the sdk', async () => { // Prepare const provider = new SSMProvider(); - const client = mockClient(SSMClient).on(GetParametersByPathCommand) + const client = mockClient(SSMClient) + .on(GetParametersByPathCommand) .resolves({ - Parameters: [] + Parameters: [], }); const parameterPath = '/foo'; // Act - await provider.getMultiple(parameterPath, { recursive: false, decrypt: true }); - + await provider.getMultiple(parameterPath, { + recursive: false, + decrypt: true, + }); + // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { Path: parameterPath, Recursive: false, WithDecryption: true, }); - }); - - test('when multiple parameters that share the same path as suffix are retrieved, it returns an object with the names only', async () => { + test('when multiple parameters that share the same path as suffix are retrieved, it returns an object with the names only', async () => { // Prepare const provider = new SSMProvider(); - mockClient(SSMClient).on(GetParametersByPathCommand) + mockClient(SSMClient) + .on(GetParametersByPathCommand) .resolves({ - Parameters: [ { - 'Name':'/foo/bar', - 'Value':'bar', - }, { - 'Name':'/foo/baz', - 'Value':'baz', - } ] + Parameters: [ + { + Name: '/foo/bar', + Value: 'bar', + }, + { + Name: '/foo/baz', + Value: 'baz', + }, + ], }); const parameterPath = '/foo'; // Act const parameters = await provider.getMultiple(parameterPath); - + // Assess expect(parameters).toEqual({ - 'bar': 'bar', - 'baz': 'baz', + bar: 'bar', + baz: 'baz', }); - }); test('when multiple pages are found, it returns an object with all the parameters', async () => { - // Prepare const provider = new SSMProvider(); - mockClient(SSMClient).on(GetParametersByPathCommand) + mockClient(SSMClient) + .on(GetParametersByPathCommand) .resolvesOnce({ - Parameters: [{ - Name:'/foo/bar', - Value:'bar', - }], + Parameters: [ + { + Name: '/foo/bar', + Value: 'bar', + }, + ], NextToken: 'someToken', }) .resolves({ - Parameters: [{ - Name:'/foo/baz', - Value:'baz', - }] + Parameters: [ + { + Name: '/foo/baz', + Value: 'baz', + }, + ], }); const parameterPath = '/foo'; // Act const parameters = await provider.getMultiple(parameterPath); - + // Assess expect(parameters).toEqual({ - 'bar': 'bar', - 'baz': 'baz', + bar: 'bar', + baz: 'baz', }); - }); - }); describe('Method: _getParametersByName', () => { - class SSMProviderMock extends SSMProvider { public transformAndCacheGetParametersResponse = jest.fn(); @@ -545,28 +548,33 @@ describe('Class: SSMProvider', () => { super(); } - public _getParametersByName(parameters: Record, throwOnError: boolean, decrypt: boolean): Promise { + public _getParametersByName( + parameters: Record, + throwOnError: boolean, + decrypt: boolean + ): Promise { return super._getParametersByName(parameters, throwOnError, decrypt); } } test('when called with a list of parameters, it passes them to the sdk', async () => { - // Prepare const provider = new SSMProviderMock(); - const client = mockClient(SSMClient).on(GetParametersCommand).resolves({ - Parameters: [ - { - Name: '/foo/bar', - Value: 'bar', - }, - { - Name: '/foo/baz', - Value: 'baz', - } - ], - InvalidParameters: [], - }); + const client = mockClient(SSMClient) + .on(GetParametersCommand) + .resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: 'bar', + }, + { + Name: '/foo/baz', + Value: 'baz', + }, + ], + InvalidParameters: [], + }); const parameters = { '/foo/bar': { throwOnError: true, @@ -583,7 +591,11 @@ describe('Class: SSMProvider', () => { }); // Act - const results = await provider._getParametersByName(parameters, true, true); + const results = await provider._getParametersByName( + parameters, + true, + true + ); // Assess expect(results).toEqual({ @@ -594,32 +606,32 @@ describe('Class: SSMProvider', () => { errors: [], }); expect(client).toReceiveCommandWith(GetParametersCommand, { - Names: [ - '/foo/bar', - '/foo/baz', - ], + Names: ['/foo/bar', '/foo/baz'], WithDecryption: true, }); - expect(provider.transformAndCacheGetParametersResponse).toHaveBeenCalledWith({ - Parameters: [ - { - Name: '/foo/bar', - Value: 'bar', - }, - { - Name: '/foo/baz', - Value: 'baz', - } - ], - InvalidParameters: [], - }, parameters, true); - + expect( + provider.transformAndCacheGetParametersResponse + ).toHaveBeenCalledWith( + { + Parameters: [ + { + Name: '/foo/bar', + Value: 'bar', + }, + { + Name: '/foo/baz', + Value: 'baz', + }, + ], + InvalidParameters: [], + }, + parameters, + true + ); }); - }); - - describe('Method: getParametersBatchByName', () => { + describe('Method: getParametersBatchByName', () => { class SSMProviderMock extends SSMProvider { public getParametersByNameFromCache = jest.fn(); public getParametersByNameInChunks = jest.fn(); @@ -628,13 +640,20 @@ describe('Class: SSMProvider', () => { super(); } - public getParametersBatchByName(parameters: Record, throwOnError: boolean, decrypt: boolean): Promise { - return super.getParametersBatchByName(parameters, throwOnError, decrypt); + public getParametersBatchByName( + parameters: Record, + throwOnError: boolean, + decrypt: boolean + ): Promise { + return super.getParametersBatchByName( + parameters, + throwOnError, + decrypt + ); } } test('when called with a list of parameters, if they are all cached, it returns them immediately', async () => { - // Prepare const provider = new SSMProviderMock(); provider.getParametersByNameFromCache.mockResolvedValueOnce({ @@ -646,10 +665,14 @@ describe('Class: SSMProvider', () => { }); // Act - const parameters = await provider.getParametersBatchByName({ - '/foo/bar': {}, - '/foo/baz': {}, - }, true, true); + const parameters = await provider.getParametersBatchByName( + { + '/foo/bar': {}, + '/foo/baz': {}, + }, + true, + true + ); // Assess expect(parameters).toEqual({ @@ -661,11 +684,9 @@ describe('Class: SSMProvider', () => { }); expect(provider.getParametersByNameFromCache).toHaveBeenCalledTimes(1); 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 () => { + test('when called with a list of parameters, if none of them are cached, it retrieves them and then returns', async () => { // Prepare const provider = new SSMProviderMock(); provider.getParametersByNameFromCache.mockResolvedValueOnce({ @@ -684,10 +705,14 @@ describe('Class: SSMProvider', () => { }); // Act - const parameters = await provider.getParametersBatchByName({ - '/foo/bar': {}, - '/foo/baz': {}, - }, true, true); + const parameters = await provider.getParametersBatchByName( + { + '/foo/bar': {}, + '/foo/baz': {}, + }, + true, + true + ); // Assess expect(parameters).toEqual({ @@ -699,15 +724,17 @@ describe('Class: SSMProvider', () => { }); expect(provider.getParametersByNameFromCache).toHaveBeenCalledTimes(1); expect(provider.getParametersByNameInChunks).toHaveBeenCalledTimes(1); - expect(provider.getParametersByNameInChunks).toHaveBeenCalledWith({ - '/foo/bar': {}, - '/foo/baz': {}, - }, true, true); - + expect(provider.getParametersByNameInChunks).toHaveBeenCalledWith( + { + '/foo/bar': {}, + '/foo/baz': {}, + }, + true, + true + ); }); - - 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 () => { + 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 () => { // Prepare const provider = new SSMProviderMock(); provider.getParametersByNameFromCache.mockResolvedValueOnce({ @@ -727,10 +754,14 @@ describe('Class: SSMProvider', () => { }); // Act - const parameters = await provider.getParametersBatchByName({ - '/foo/bar': {}, - '/foo/baz': {}, - }, true, true); + const parameters = await provider.getParametersBatchByName( + { + '/foo/bar': {}, + '/foo/baz': {}, + }, + true, + true + ); // Assess expect(parameters).toEqual({ @@ -742,17 +773,18 @@ describe('Class: SSMProvider', () => { }); expect(provider.getParametersByNameFromCache).toHaveBeenCalledTimes(1); expect(provider.getParametersByNameInChunks).toHaveBeenCalledTimes(1); - expect(provider.getParametersByNameInChunks).toHaveBeenCalledWith({ - '/foo/baz': {}, - }, true, true); - + expect(provider.getParametersByNameInChunks).toHaveBeenCalledWith( + { + '/foo/baz': {}, + }, + true, + true + ); }); - }); describe('Method: getParametersByNameFromCache', () => { - class SSMProviderMock extends SSMProvider { - + class SSMProviderMock extends SSMProvider { public constructor() { super(); } @@ -761,24 +793,30 @@ describe('Class: SSMProvider', () => { this.store.set(key, value); } - public async getParametersByNameFromCache(parameters: Record): Promise { + public async getParametersByNameFromCache( + parameters: Record + ): Promise { return super.getParametersByNameFromCache(parameters); } } test('when called with a batch of parameters, it returns an object with parameters split by cached and to fetch', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { '/foo/bar': {}, '/foo/baz': {}, }; - provider._add([ '/foo/bar', undefined ].toString(), new ExpirableValue('my-value', 60000)); + provider._add( + ['/foo/bar', undefined].toString(), + new ExpirableValue('my-value', 60000) + ); // Act - const { cached, toFetch } = await provider.getParametersByNameFromCache(parameters); - + const { cached, toFetch } = await provider.getParametersByNameFromCache( + parameters + ); + // Assess expect(cached).toEqual({ '/foo/bar': 'my-value', @@ -786,13 +824,10 @@ describe('Class: SSMProvider', () => { expect(toFetch).toEqual({ '/foo/baz': {}, }); - }); - }); describe('Method: getParametersByNameInChunks', () => { - class SSMProviderMock extends SSMProvider { public _getParametersByName = jest.fn(); public maxGetParametersItems = 1; @@ -801,13 +836,20 @@ describe('Class: SSMProvider', () => { super(); } - public async getParametersByNameInChunks(parameters: Record, throwOnError: boolean, decrypt: boolean): Promise { - return super.getParametersByNameInChunks(parameters, throwOnError, decrypt); + public async getParametersByNameInChunks( + parameters: Record, + throwOnError: boolean, + decrypt: boolean + ): Promise { + return super.getParametersByNameInChunks( + parameters, + throwOnError, + decrypt + ); } } test('when called with a batch of parameters to retrieve, it splits them in chunks and retrieves them', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -826,7 +868,11 @@ describe('Class: SSMProvider', () => { }); // Act - const { response, errors } = await provider.getParametersByNameInChunks(parameters, false, false); + const { response, errors } = await provider.getParametersByNameInChunks( + parameters, + false, + false + ); // Assess expect(response).toEqual({ @@ -835,11 +881,9 @@ describe('Class: SSMProvider', () => { }); expect(errors).toEqual([]); expect(provider._getParametersByName).toHaveBeenCalledTimes(2); - }); test('when retrieving parameters, if throwOnError is true, it throws an error if any parameter is not found', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -855,12 +899,12 @@ describe('Class: SSMProvider', () => { .mockRejectedValueOnce(new Error('Parameter not found')); // Act - await expect(provider.getParametersByNameInChunks(parameters, true, false)).rejects.toThrowError('Parameter not found'); - + await expect( + provider.getParametersByNameInChunks(parameters, true, false) + ).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 () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -879,20 +923,21 @@ describe('Class: SSMProvider', () => { }); // Act - const { response, errors } = await provider.getParametersByNameInChunks(parameters, false, false); + const { response, errors } = await provider.getParametersByNameInChunks( + parameters, + false, + false + ); // Assess expect(response).toEqual({ '/foo/bar': 'bar', }); expect(errors).toEqual(['/foo/baz']); - }); - }); describe('Method: getParametersByNameWithDecryptOption', () => { - class SSMProviderMock extends SSMProvider { public _get = jest.fn(); @@ -900,37 +945,39 @@ describe('Class: SSMProvider', () => { super(); } - public async getParametersByNameWithDecryptOption(parameters: Record, throwOnError: boolean): Promise { - return super.getParametersByNameWithDecryptOption(parameters, throwOnError); + public async getParametersByNameWithDecryptOption( + parameters: Record, + throwOnError: boolean + ): Promise { + return super.getParametersByNameWithDecryptOption( + parameters, + throwOnError + ); } } test('when called with a batch of parameters to retrieve, it returns an object with the parameters values', async () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { '/foo/bar': {}, '/foo/baz': {}, }; - provider._get - .mockResolvedValueOnce('bar') - .mockResolvedValueOnce('baz'); + provider._get.mockResolvedValueOnce('bar').mockResolvedValueOnce('baz'); // Act - const { response, errors } = await provider.getParametersByNameWithDecryptOption(parameters, false); - + const { response, errors } = + await provider.getParametersByNameWithDecryptOption(parameters, false); + // Assess expect(response).toEqual({ '/foo/bar': 'bar', '/foo/baz': 'baz', }); 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 () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -942,18 +989,17 @@ describe('Class: SSMProvider', () => { .mockRejectedValueOnce(new Error('baz')); // Act - const { response, errors } = await provider.getParametersByNameWithDecryptOption(parameters, false); - + const { response, errors } = + await provider.getParametersByNameWithDecryptOption(parameters, false); + // Assess expect(response).toEqual({ '/foo/bar': 'bar', }); 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 () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -965,28 +1011,30 @@ describe('Class: SSMProvider', () => { .mockRejectedValueOnce(new Error('baz')); // Act & Assess - await expect(provider.getParametersByNameWithDecryptOption(parameters, true)) - .rejects.toThrow(); - + await expect( + provider.getParametersByNameWithDecryptOption(parameters, true) + ).rejects.toThrow(); }); - }); describe('Method: handleAnyInvalidGetparameterErrors', () => { - - class SSMProviderMock extends SSMProvider { - + class SSMProviderMock extends SSMProvider { public constructor() { super(); } - public handleAnyInvalidGetParameterErrors(result: Partial, throwOnError: boolean): string[] { - return SSMProvider.handleAnyInvalidGetParameterErrors(result as GetParametersCommandOutput, throwOnError); + public handleAnyInvalidGetParameterErrors( + result: Partial, + throwOnError: boolean + ): string[] { + return SSMProvider.handleAnyInvalidGetParameterErrors( + result as GetParametersCommandOutput, + throwOnError + ); } } - test('when called without any errors, it doesn\'t throw and returns an empty errors array', () => { - + test('when called without any errors, it does not throw and returns an empty errors array', () => { // Prepare const provider = new SSMProviderMock(); const result = { @@ -998,44 +1046,38 @@ describe('Class: SSMProvider', () => { // Assess expect(errors).toEqual([]); - }); test('when called with errors, and throwOnError is set to false, it returns the errors array', () => { - // Prepare const provider = new SSMProviderMock(); const result = { - InvalidParameters: [ '/foo/bar', '/foo/baz' ], + InvalidParameters: ['/foo/bar', '/foo/baz'], }; // Act const errors = provider.handleAnyInvalidGetParameterErrors(result, false); // Assess - expect(errors).toEqual([ '/foo/bar', '/foo/baz' ]); - + expect(errors).toEqual(['/foo/bar', '/foo/baz']); }); test('when called with errors, and throwOnError is set to true, it throws an error', () => { - // Prepare const provider = new SSMProviderMock(); const result = { - InvalidParameters: [ '/foo/bar', '/foo/baz' ], + InvalidParameters: ['/foo/bar', '/foo/baz'], }; // Act & Assess - expect(() => provider.handleAnyInvalidGetParameterErrors(result, true)).toThrowError('Failed to fetch parameters: /foo/bar, /foo/baz'); - + expect(() => + provider.handleAnyInvalidGetParameterErrors(result, true) + ).toThrowError('Failed to fetch parameters: /foo/bar, /foo/baz'); }); - }); describe('Method: splitBatchAndDecryptParameters', () => { - - class SSMProviderMock extends SSMProvider { - + class SSMProviderMock extends SSMProvider { public constructor() { super(); } @@ -1049,7 +1091,6 @@ 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', () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1060,11 +1101,9 @@ describe('Class: SSMProvider', () => { }; // Act - const { - parametersToFetchInBatch, - parametersToDecrypt - } = provider.splitBatchAndDecryptParameters(parameters, {}); - + const { parametersToFetchInBatch, parametersToDecrypt } = + provider.splitBatchAndDecryptParameters(parameters, {}); + // Assess expect(parametersToDecrypt).toEqual({}); expect(parametersToFetchInBatch).toEqual({ @@ -1073,11 +1112,9 @@ describe('Class: SSMProvider', () => { maxAge: 1000, }, }); - }); test('when called with a batch of parameters, it returns an object with parameters split by decrypt and not to decrypt', () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1090,29 +1127,25 @@ describe('Class: SSMProvider', () => { }; // Act - const { - parametersToFetchInBatch, - parametersToDecrypt - } = provider.splitBatchAndDecryptParameters(parameters, {}); - + const { parametersToFetchInBatch, parametersToDecrypt } = + provider.splitBatchAndDecryptParameters(parameters, {}); + // Assess expect(parametersToDecrypt).toEqual({ '/foo/bar': { decrypt: true, - transform: undefined + transform: undefined, }, }); expect(parametersToFetchInBatch).toEqual({ '/foo/baz': { decrypt: false, - transform: undefined - } + transform: undefined, + }, }); - }); test('when called with a batch of parameters, it respects any local overrides by giving them precedence over global config', () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { @@ -1125,122 +1158,132 @@ describe('Class: SSMProvider', () => { }; // Act - const { - parametersToFetchInBatch, - parametersToDecrypt - } = provider.splitBatchAndDecryptParameters(parameters, { - decrypt: false, - maxAge: 2000, - }); - + const { parametersToFetchInBatch, parametersToDecrypt } = + provider.splitBatchAndDecryptParameters(parameters, { + decrypt: false, + maxAge: 2000, + }); + // Assess expect(parametersToDecrypt).toEqual({ '/foo/bar': { decrypt: true, maxAge: 2000, - transform: undefined + transform: undefined, }, }); expect(parametersToFetchInBatch).toEqual({ '/foo/baz': { decrypt: false, maxAge: 1000, - transform: undefined - } + transform: undefined, + }, }); - }); - }); describe('Method: throwIfErrorsKeyIsPresent', () => { - - class SSMProviderMock extends SSMProvider { - + class SSMProviderMock extends SSMProvider { public constructor() { super(); } - public throwIfErrorsKeyIsPresent(parameters: Record, reservedParameter: string, throwOnError: boolean): void { - return SSMProvider.throwIfErrorsKeyIsPresent(parameters, reservedParameter, throwOnError); + public throwIfErrorsKeyIsPresent( + parameters: Record, + reservedParameter: string, + throwOnError: boolean + ): void { + return SSMProvider.throwIfErrorsKeyIsPresent( + parameters, + reservedParameter, + throwOnError + ); } } - test('when called and no parameter is named _errors, it doesn\'t throw', () => { - + test('when called and no parameter is named _errors, it does not throw', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { - 'foo': 'bar', - 'baz': 'qux', + foo: 'bar', + baz: 'qux', }; // Act & Assess - expect(() => provider.throwIfErrorsKeyIsPresent(parameters, '_errors', false)).not.toThrow(); - + expect(() => + provider.throwIfErrorsKeyIsPresent(parameters, '_errors', false) + ).not.toThrow(); }); - test('when called and a parameter is named _errors, and throwOnError is set to false (graceful error mode), it doesn\'t throw', () => { - + test('when called and a parameter is named _errors, and throwOnError is set to false (graceful error mode), it does not throw', () => { // Prepare const provider = new SSMProviderMock(); const parameters = { - 'foo': 'bar', - 'baz': 'qux', - '_errors': 'baz', + foo: 'bar', + baz: 'qux', + _errors: 'baz', }; // Act & Assess - expect(() => provider.throwIfErrorsKeyIsPresent(parameters, '_errors', false)).toThrow('You cannot fetch a parameter named _errors in graceful error mode.'); - + expect(() => + provider.throwIfErrorsKeyIsPresent(parameters, '_errors', false) + ).toThrow( + 'You cannot fetch a parameter named _errors in graceful error mode.' + ); }); test('when called and a parameter is named _errors, and throwOnError is set to true (fail fast mode), it throws an error', () => { - // Prepare const provider = new SSMProviderMock(); const parameters = { - 'foo': 'bar', - 'baz': 'qux', - '_errors': 'baz', + foo: 'bar', + baz: 'qux', + _errors: 'baz', }; // Act & Assess - expect(() => provider.throwIfErrorsKeyIsPresent(parameters, '_errors', true)).not.toThrow(); - + expect(() => + provider.throwIfErrorsKeyIsPresent(parameters, '_errors', true) + ).not.toThrow(); }); - }); describe('Method: transformAndCacheGetParametersResponse', () => { - - class SSMProviderMock extends SSMProvider { - + class SSMProviderMock extends SSMProvider { public constructor() { super(); } - public transformAndCacheGetParametersResponse(response: Partial, parameters: Record, throwOnError: boolean): Record { - return super.transformAndCacheGetParametersResponse(response as GetParametersCommandOutput, parameters, throwOnError); + public transformAndCacheGetParametersResponse( + response: Partial, + parameters: Record, + throwOnError: boolean + ): Record { + return super.transformAndCacheGetParametersResponse( + response as GetParametersCommandOutput, + parameters, + throwOnError + ); } } test('when called with a response that has no Parameters list, it returns an empty object', () => { - // Prepare const provider = new SSMProviderMock(); const response = {}; // Act - const parameters = provider.transformAndCacheGetParametersResponse(response, {}, false); + const parameters = provider.transformAndCacheGetParametersResponse( + response, + {}, + false + ); // Assess expect(parameters).toEqual({}); - }); - - test('when called with an empty response, it returns an empty object', () => { + test('when called with an empty response, it returns an empty object', () => { // Prepare const provider = new SSMProviderMock(); const response = { @@ -1248,43 +1291,49 @@ describe('Class: SSMProvider', () => { }; // Act - const parameters = provider.transformAndCacheGetParametersResponse(response, {}, false); + const parameters = provider.transformAndCacheGetParametersResponse( + response, + {}, + false + ); // Assess expect(parameters).toEqual({}); - }); test('when called with a response, it returns an object with the parameters', () => { - // Prepare const provider = new SSMProviderMock(); const response = { - Parameters: [ { - Name: '/foo/bar', - Value: toBase64(encoder.encode('bar')).toString(), - }, { - Name: '/foo/baz', - Value: 'baz', - } ], + Parameters: [ + { + Name: '/foo/bar', + Value: toBase64(encoder.encode('bar')).toString(), + }, + { + Name: '/foo/baz', + Value: 'baz', + }, + ], }; // Act - const parameters = provider.transformAndCacheGetParametersResponse(response, { - '/foo/bar': { - transform: 'binary' + const parameters = provider.transformAndCacheGetParametersResponse( + response, + { + '/foo/bar': { + transform: 'binary', + }, + '/foo/baz': {}, }, - '/foo/baz': {}, - }, false); + false + ); // Assess expect(parameters).toEqual({ '/foo/bar': 'bar', '/foo/baz': 'baz', }); - }); - }); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/SecretsProvider.test.ts b/packages/parameters/tests/unit/SecretsProvider.test.ts index e35923cb32..7eac0c8cf5 100644 --- a/packages/parameters/tests/unit/SecretsProvider.test.ts +++ b/packages/parameters/tests/unit/SecretsProvider.test.ts @@ -4,7 +4,10 @@ * @group unit/parameters/SecretsProvider/class */ import { SecretsProvider } from '../../src/secrets'; -import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; +import { + SecretsManagerClient, + GetSecretValueCommand, +} from '@aws-sdk/client-secrets-manager'; import type { GetSecretValueCommandInput } from '@aws-sdk/client-secrets-manager'; import type { SecretsProviderOptions } from '../../src/types/SecretsProvider'; import { mockClient } from 'aws-sdk-client-mock'; @@ -13,7 +16,6 @@ import 'aws-sdk-client-mock-jest'; const encoder = new TextEncoder(); describe('Class: SecretsProvider', () => { - const client = mockClient(SecretsManagerClient); beforeEach(() => { @@ -22,7 +24,6 @@ describe('Class: SecretsProvider', () => { describe('Method: constructor', () => { test('when the class instantiates without SDK client and client config it has default options', async () => { - // Prepare const options: SecretsProviderOptions = {}; @@ -38,7 +39,6 @@ describe('Class: SecretsProvider', () => { }); test('when the user provides a client config in the options, the class instantiates a new client with client config options', async () => { - // Prepare const options: SecretsProviderOptions = { clientConfig: { @@ -58,7 +58,6 @@ describe('Class: SecretsProvider', () => { }); test('when the user provides an SDK client in the options, the class instantiates with it', async () => { - // Prepare const awsSdkV3Client = new SecretsManagerClient({ serviceId: 'with-custom-sdk-client', @@ -94,9 +93,7 @@ describe('Class: SecretsProvider', () => { }); describe('Method: _get', () => { - test('when called with only a name, it gets the secret string', async () => { - // Prepare const provider = new SecretsProvider(); const secretName = 'foo'; @@ -109,11 +106,9 @@ describe('Class: SecretsProvider', () => { // Assess expect(result).toBe('bar'); - }); test('when called with only a name, it gets the secret binary', async () => { - // Prepare const provider = new SecretsProvider(); const secretName = 'foo'; @@ -127,11 +122,9 @@ describe('Class: SecretsProvider', () => { // Assess expect(result).toBe(mockData); - }); test('when called with a name and sdkOptions, it gets the secret using the options provided', async () => { - // Prepare const provider = new SecretsProvider(); const secretName = 'foo'; @@ -143,7 +136,7 @@ describe('Class: SecretsProvider', () => { await provider.get(secretName, { sdkOptions: { VersionId: 'test-version', - } + }, }); // Assess @@ -151,11 +144,9 @@ describe('Class: SecretsProvider', () => { SecretId: secretName, VersionId: 'test-version', }); - }); test('when called with sdkOptions that override arguments passed to the method, it gets the secret using the arguments', async () => { - // Prepare const provider = new SecretsProvider(); const secretName = 'foo'; @@ -174,23 +165,18 @@ describe('Class: SecretsProvider', () => { expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName, }); - }); - }); describe('Method: _getMultiple', () => { - test('when called, it throws an error', async () => { - // Prepare const provider = new SecretsProvider(); - - // Act & Assess - await expect(provider.getMultiple('foo')).rejects.toThrow('Method not implemented.'); + // Act & Assess + await expect(provider.getMultiple('foo')).rejects.toThrow( + 'Method not implemented.' + ); }); - }); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/config/EnvironmentVariablesService.test.ts b/packages/parameters/tests/unit/config/EnvironmentVariablesService.test.ts index b6822885e1..80e60086c5 100644 --- a/packages/parameters/tests/unit/config/EnvironmentVariablesService.test.ts +++ b/packages/parameters/tests/unit/config/EnvironmentVariablesService.test.ts @@ -6,7 +6,6 @@ import { EnvironmentVariablesService } from '../../../src/config/EnvironmentVariablesService'; describe('Class: EnvironmentVariablesService', () => { - const ENVIRONMENT_VARIABLES = process.env; beforeEach(() => { @@ -19,9 +18,7 @@ describe('Class: EnvironmentVariablesService', () => { }); describe('Method: getParametersMaxAge', () => { - test('it returns undefined if the POWERTOOLS_PARAMETERS_MAX_AGE is empty', () => { - // Prepare const service = new EnvironmentVariablesService(); @@ -30,11 +27,9 @@ describe('Class: EnvironmentVariablesService', () => { // Assess expect(value).toEqual(undefined); - }); test('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(); @@ -44,11 +39,9 @@ describe('Class: EnvironmentVariablesService', () => { // Assess expect(value).toEqual(36); - }); - - test('it logs a warning if the POWERTOOLS_PARAMETERS_MAX_AGE has a non-numeric value', () => { + test('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(); @@ -59,19 +52,14 @@ describe('Class: EnvironmentVariablesService', () => { // Assess expect(value).toEqual(undefined); - expect(warnLogspy) - .toHaveBeenCalledWith( - 'Invalid value for POWERTOOLS_PARAMETERS_MAX_AGE environment variable: [invalid], using default value of 5 seconds' - ); - + expect(warnLogspy).toHaveBeenCalledWith( + 'Invalid value for POWERTOOLS_PARAMETERS_MAX_AGE environment variable: [invalid], using default value of 5 seconds' + ); }); - }); describe('Method: getSSMDecrypt', () => { - test('it returns the value of the environment variable POWERTOOLS_PARAMETERS_SSM_DECRYPT', () => { - // Prepare process.env.POWERTOOLS_PARAMETERS_SSM_DECRYPT = 'true'; const service = new EnvironmentVariablesService(); @@ -82,7 +70,5 @@ describe('Class: EnvironmentVariablesService', () => { // Assess expect(value).toEqual('true'); }); - }); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/getAppConfig.test.ts b/packages/parameters/tests/unit/getAppConfig.test.ts index e729d23dfd..20e9bfe8d9 100644 --- a/packages/parameters/tests/unit/getAppConfig.test.ts +++ b/packages/parameters/tests/unit/getAppConfig.test.ts @@ -26,7 +26,7 @@ describe('Function: getAppConfig', () => { jest.clearAllMocks(); }); - test('when called and a default provider doesn\'t exist, it instantiates one and returns the value', async () => { + test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { // Prepare const options: GetAppConfigCombinedInterface = { application: 'MyApp', @@ -90,7 +90,7 @@ describe('Function: getAppConfig', () => { expect(result).toBe(mockData); }); - test('when called with transform: \'binary\' option, it returns string value', async () => { + test('when called with transform: `binary` option, it returns string value', async () => { // Prepare const options: GetAppConfigCombinedInterface = { application: 'MyApp', diff --git a/packages/parameters/tests/unit/getParameter.test.ts b/packages/parameters/tests/unit/getParameter.test.ts index b7bf839f57..f6e5d50320 100644 --- a/packages/parameters/tests/unit/getParameter.test.ts +++ b/packages/parameters/tests/unit/getParameter.test.ts @@ -14,21 +14,21 @@ import 'aws-sdk-client-mock-jest'; * generic types defined in the utility are working as expected. If they are not, the tests will fail to compile. */ describe('Function: getParameter', () => { - beforeEach(() => { jest.clearAllMocks(); }); - test('when called and a default provider doesn\'t exist, it instantiates one and returns the value', async () => { - + test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { // Prepare const parameterName = 'foo'; const parameterValue = 'foo'; - const client = mockClient(SSMClient).on(GetParameterCommand).resolves({ - Parameter: { - Value: parameterValue, - }, - }); + const client = mockClient(SSMClient) + .on(GetParameterCommand) + .resolves({ + Parameter: { + Value: parameterValue, + }, + }); // Act const value: string | undefined = await getParameter(parameterName); @@ -38,21 +38,21 @@ describe('Function: getParameter', () => { Name: parameterName, }); expect(value).toBe(parameterValue); - }); test('when called and a default provider exists, it uses it and returns the value', 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, - }, - }); + const client = mockClient(SSMClient) + .on(GetParameterCommand) + .resolves({ + Parameter: { + Value: parameterValue, + }, + }); // Act const value: string | undefined = await getParameter(parameterName); @@ -63,55 +63,59 @@ 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, - }, - }); + const client = mockClient(SSMClient) + .on(GetParameterCommand) + .resolves({ + Parameter: { + Value: parameterValue, + }, + }); // Act - const value: Record | undefined = await getParameter(parameterName, { transform: 'json' }); + 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, - }, - }); + const client = mockClient(SSMClient) + .on(GetParameterCommand) + .resolves({ + Parameter: { + Value: parameterValue, + }, + }); // Act - const value: number | undefined = await getParameter(parameterName, { transform: 'json' }); + const value: number | undefined = await getParameter( + parameterName, + { transform: 'json' } + ); // Assess expect(client).toReceiveCommandWith(GetParameterCommand, { Name: parameterName, }); expect(value).toBe(JSON.parse(parameterValue)); - }); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/getParameters.test.ts b/packages/parameters/tests/unit/getParameters.test.ts index 2025c0ae7f..7ec9680dc7 100644 --- a/packages/parameters/tests/unit/getParameters.test.ts +++ b/packages/parameters/tests/unit/getParameters.test.ts @@ -14,25 +14,29 @@ import 'aws-sdk-client-mock-jest'; * 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(); }); - test('when called and a default provider doesn\'t exist, it instantiates one and returns the value', async () => { - + test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { // Prepare const parameterPath = '/foo'; const parameterValue = 'bar'; - const client = mockClient(SSMClient).on(GetParametersByPathCommand).resolves({ - Parameters: [{ - Name: '/foo/bar', - Value: parameterValue, - }], - }); + const client = mockClient(SSMClient) + .on(GetParametersByPathCommand) + .resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: parameterValue, + }, + ], + }); // Act - const parameters: Record | undefined = await getParameters(parameterPath); + const parameters: Record | undefined = await getParameters( + parameterPath + ); // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { @@ -41,91 +45,99 @@ describe('Function: getParameters', () => { expect(parameters).toEqual({ bar: parameterValue, }); - }); test('when called and a default provider exists, it uses it and returns the value', async () => { - // Prepare const provider = new SSMProvider(); DEFAULT_PROVIDERS.ssm = provider; const parameterPath = '/foo'; const parameterValue = 'bar'; - const client = mockClient(SSMClient).on(GetParametersByPathCommand).resolves({ - Parameters: [{ - Name: '/foo/bar', - Value: parameterValue, - }], - }); + const client = mockClient(SSMClient) + .on(GetParametersByPathCommand) + .resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: parameterValue, + }, + ], + }); // Act - const parameters: Record | undefined = await getParameters(parameterPath); + const parameters: Record | undefined = await getParameters( + parameterPath + ); // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { Path: parameterPath, }); expect(parameters).toEqual({ - 'bar': parameterValue, + 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, - }], - }); + const client = mockClient(SSMClient) + .on(GetParametersByPathCommand) + .resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: parameterValue, + }, + ], + }); // Act - const parameters: Record> | undefined = await getParameters(parameterPath); + const parameters: Record> | undefined = + await getParameters(parameterPath); // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { Path: parameterPath, }); expect(parameters).toStrictEqual({ - 'bar': parameterValue, + 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, - }], - }); + const client = mockClient(SSMClient) + .on(GetParametersByPathCommand) + .resolves({ + Parameters: [ + { + Name: '/foo/bar', + Value: parameterValue, + }, + ], + }); // Act - const parameters: Record> | undefined = await getParameters>(parameterPath); + const parameters: Record> | undefined = + await getParameters>(parameterPath); // Assess expect(client).toReceiveCommandWith(GetParametersByPathCommand, { Path: parameterPath, }); expect(parameters).toStrictEqual({ - 'bar': parameterValue, + bar: parameterValue, }); expect(DEFAULT_PROVIDERS.ssm).toBe(provider); - }); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/getParametersByName.test.ts b/packages/parameters/tests/unit/getParametersByName.test.ts index 7fb0896341..3e2dcfc5f3 100644 --- a/packages/parameters/tests/unit/getParametersByName.test.ts +++ b/packages/parameters/tests/unit/getParametersByName.test.ts @@ -5,18 +5,14 @@ */ import { DEFAULT_PROVIDERS } from '../../src/BaseProvider'; import { SSMProvider, getParametersByName } from '../../src/ssm'; -import type { - SSMGetParametersByNameOptions -} from '../../src/types/SSMProvider'; +import type { SSMGetParametersByNameOptions } from '../../src/types/SSMProvider'; describe('Function: getParametersByName', () => { - beforeEach(() => { jest.clearAllMocks(); }); - test('when called and a default provider doesn\'t exist, it instantiates one and returns the value', async () => { - + test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { // Prepare const parameters: Record = { '/foo/bar': { @@ -25,20 +21,20 @@ describe('Function: getParametersByName', () => { '/foo/baz': { maxAge: 2000, transform: 'json', - } + }, }; - const getParametersByNameSpy = jest.spyOn(SSMProvider.prototype, 'getParametersByName').mockImplementation(); + const getParametersByNameSpy = jest + .spyOn(SSMProvider.prototype, 'getParametersByName') + .mockImplementation(); // Act await getParametersByName(parameters); // Assess expect(getParametersByNameSpy).toHaveBeenCalledWith(parameters, undefined); - }); - - test('when called and a default provider exists, it uses it and returns the value', async () => { + test('when called and a default provider exists, it uses it and returns the value', async () => { // Prepare const provider = new SSMProvider(); DEFAULT_PROVIDERS.ssm = provider; @@ -48,9 +44,11 @@ describe('Function: getParametersByName', () => { }, '/foo/baz': { maxAge: 2000, - } + }, }; - const getParametersByNameSpy = jest.spyOn(provider, 'getParametersByName').mockImplementation(); + const getParametersByNameSpy = jest + .spyOn(provider, 'getParametersByName') + .mockImplementation(); // Act await getParametersByName(parameters); @@ -58,7 +56,5 @@ describe('Function: getParametersByName', () => { // Assess expect(getParametersByNameSpy).toHaveBeenCalledWith(parameters, undefined); expect(DEFAULT_PROVIDERS.ssm).toBe(provider); - }); - -}); \ No newline at end of file +}); diff --git a/packages/parameters/tests/unit/getSecret.test.ts b/packages/parameters/tests/unit/getSecret.test.ts index ad9360daae..966f6456e7 100644 --- a/packages/parameters/tests/unit/getSecret.test.ts +++ b/packages/parameters/tests/unit/getSecret.test.ts @@ -5,22 +5,23 @@ */ import { DEFAULT_PROVIDERS } from '../../src/BaseProvider'; import { SecretsProvider, getSecret } from '../../src/secrets'; -import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; +import { + SecretsManagerClient, + GetSecretValueCommand, +} from '@aws-sdk/client-secrets-manager'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; const encoder = new TextEncoder(); describe('Function: getSecret', () => { - const client = mockClient(SecretsManagerClient); beforeEach(() => { jest.clearAllMocks(); }); - test('when called and a default provider doesn\'t exist, it instantiates one and returns the value', async () => { - + test('when called and a default provider does not exist, it instantiates one and returns the value', async () => { // Prepare const secretName = 'foo'; const secretValue = 'bar'; @@ -32,13 +33,13 @@ describe('Function: getSecret', () => { const result: string | Uint8Array | undefined = await getSecret(secretName); // Assess - expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName }); + expect(client).toReceiveCommandWith(GetSecretValueCommand, { + SecretId: secretName, + }); expect(result).toBe(secretValue); - }); test('when called and a default provider exists, it uses it and returns the value', async () => { - // Prepare const provider = new SecretsProvider(); DEFAULT_PROVIDERS.secrets = provider; @@ -53,54 +54,59 @@ describe('Function: getSecret', () => { const result: string | Uint8Array | undefined = await getSecret(secretName); // Assess - expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName }); + expect(client).toReceiveCommandWith(GetSecretValueCommand, { + SecretId: secretName, + }); 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, - }); + const client = mockClient(SecretsManagerClient) + .on(GetSecretValueCommand) + .resolves({ + SecretString: secretValue, + }); // Act - const value: Record | undefined = await getSecret(secretName, { transform: 'json' }); + 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, - }); + const client = mockClient(SecretsManagerClient) + .on(GetSecretValueCommand) + .resolves({ + SecretString: secretValue, + }); // Act - const value: number | undefined = await getSecret(secretName, { transform: 'json' }); + const value: number | undefined = await getSecret(secretName, { + transform: 'json', + }); // Assess expect(client).toReceiveCommandWith(GetSecretValueCommand, { SecretId: secretName, }); expect(value).toBe(JSON.parse(secretValue)); - }); - -}); \ No newline at end of file +});