diff --git a/packages/commons/src/userAgentMiddleware.ts b/packages/commons/src/userAgentMiddleware.ts index 2f66661ef8..24b278f7b4 100644 --- a/packages/commons/src/userAgentMiddleware.ts +++ b/packages/commons/src/userAgentMiddleware.ts @@ -33,6 +33,13 @@ const customUserAgentMiddleware = (feature: string) => { // @ts-ignore const addUserAgentMiddleware = (client, feature: string): void => { try { + if ( + client.middlewareStack + .identify() + .includes('addPowertoolsToUserAgent: POWERTOOLS,USER_AGENT') + ) { + return; + } client.middlewareStack.addRelativeTo( customUserAgentMiddleware(feature), middlewareOptions diff --git a/packages/commons/tests/unit/userAgentMiddleware.test.ts b/packages/commons/tests/unit/userAgentMiddleware.test.ts index da71ecd8a5..3a7f4538ed 100644 --- a/packages/commons/tests/unit/userAgentMiddleware.test.ts +++ b/packages/commons/tests/unit/userAgentMiddleware.test.ts @@ -3,12 +3,26 @@ import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda'; import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { ScanCommand } from '@aws-sdk/lib-dynamodb'; import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm'; -import { version as PT_VERSION } from '../../package.json'; +import { PT_VERSION } from '../../src/version'; import { AppConfigDataClient } from '@aws-sdk/client-appconfigdata'; import { GetSecretValueCommand, SecretsManagerClient, } from '@aws-sdk/client-secrets-manager'; +import { RelativeMiddlewareOptions } from '@aws-sdk/types/dist-types/middleware'; + +type SupportedSdkClients = + | LambdaClient + | DynamoDBClient + | SSMClient + | SecretsManagerClient + | AppConfigDataClient; + +type SupportedSdkCommands = + | InvokeCommand + | ScanCommand + | GetParameterCommand + | GetSecretValueCommand; const options = { region: 'us-east-1', @@ -20,6 +34,57 @@ const options = { }, }; +const assertMiddleware = (feature: string) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return (next) => (args) => { + const userAgent = args?.request?.headers['user-agent']; + expect(userAgent).toContain(`PT/${feature}/${PT_VERSION} PTEnv/NA`); + // make sure it's at the end of the user agent + expect( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + userAgent + ?.split(' ') + .slice(userAgent?.split(' ').length - 2) // take the last to entries of the user-agent header + .join(' ') + ).toEqual(`PT/${feature}/${PT_VERSION} PTEnv/NA`); + + return next(args); + }; +}; + +const assertMiddlewareOptions: RelativeMiddlewareOptions = { + relation: 'after', + toMiddleware: 'addPowertoolsToUserAgent', + name: 'testUserAgentHeader', + tags: ['TEST'], +}; + +const runCommand = async ( + client: SupportedSdkClients, + command: SupportedSdkCommands +): Promise => { + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return await client.send(command); + } catch (e) { + // throw only jest errors and swallow the SDK client errors like credentials or connection issues + if ( + e instanceof Error && + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + e.matcherResult !== undefined && + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + e.matcherResult.pass === false + ) { + throw e; + } + } +}; + describe('Given a client of instance: ', () => { it.each([ { @@ -57,44 +122,15 @@ describe('Given a client of instance: ', () => { ); client.middlewareStack.addRelativeTo( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - (next) => (args) => { - const userAgent = args?.request?.headers['user-agent']; - expect(userAgent).toContain(`PT/my-feature/${PT_VERSION} PTEnv/NA`); - // make sure it's at the end of the user agent - expect( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - userAgent - ?.split(' ') - .slice(userAgent?.split(' ').length - 2) // take the last to entries of the user-agent header - .join(' ') - ).toEqual(`PT/my-feature/${PT_VERSION} PTEnv/NA`); - - return next(args); - }, - { - relation: 'after', - toMiddleware: 'addPowertoolsToUserAgent', - name: 'testUserAgentHeader', - tags: ['TEST'], - } + assertMiddleware('my-feature'), + assertMiddlewareOptions ); - try { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - await client.send(command); - } catch (e) { - if (e instanceof Error && e.name === 'JestAssertionError') { - throw e; - } - } + await runCommand(client, command); } ); - it('should not throw erro, when client fails to add middleware', () => { + it('should not throw error, when client fails to add middleware', () => { // create mock client that throws error when adding middleware const client = { middlewareStack: { @@ -106,4 +142,21 @@ describe('Given a client of instance: ', () => { expect(() => addUserAgentMiddleware(client, 'my-feature')).not.toThrow(); }); + + it('should no-op if we add the middleware twice', async () => { + const client = new LambdaClient(options); + const command = new InvokeCommand({ FunctionName: 'test', Payload: '' }); + addUserAgentMiddleware(client, 'my-feature'); + addUserAgentMiddleware(client, 'your-feature'); + + client.middlewareStack.addRelativeTo( + assertMiddleware('my-feature'), + assertMiddlewareOptions + ); + await runCommand(client, command); + + expect(client.middlewareStack.identify()).toContain( + 'addPowertoolsToUserAgent: POWERTOOLS,USER_AGENT' + ); + }); });