diff --git a/.gitignore b/.gitignore index d5b3be11..ca8f9a1e 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,6 @@ dist # Compiled code lib/ + +# IDEs +.idea diff --git a/src/v2/adapters/aws/dynamodb.adapter.ts b/src/v2/adapters/aws/dynamodb.adapter.ts index d933ac1a..0d6236e3 100644 --- a/src/v2/adapters/aws/dynamodb.adapter.ts +++ b/src/v2/adapters/aws/dynamodb.adapter.ts @@ -33,6 +33,8 @@ export interface DynamoDBAdapterOptions { /** * The adapter to handle requests from AWS DynamoDB. * + * The option of `responseWithErrors` is ignored by this adapter and we always call `resolver.fail` with the error. + * * {@link https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html Event Reference} * * @example```typescript diff --git a/src/v2/adapters/aws/event-bridge.adapter.ts b/src/v2/adapters/aws/event-bridge.adapter.ts index 17027feb..90a32eb5 100644 --- a/src/v2/adapters/aws/event-bridge.adapter.ts +++ b/src/v2/adapters/aws/event-bridge.adapter.ts @@ -38,6 +38,8 @@ export type EventBridgeEventAll = EventBridgeEvent; /** * The adapter to handle requests from AWS EventBridge (Cloudwatch Events). * + * The option of `responseWithErrors` is ignored by this adapter and we always call `resolver.fail` with the error. + * * {@link https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html Event Reference} * * @example```typescript diff --git a/src/v2/adapters/aws/sns.adapter.ts b/src/v2/adapters/aws/sns.adapter.ts index e72ed73d..c7692fbf 100644 --- a/src/v2/adapters/aws/sns.adapter.ts +++ b/src/v2/adapters/aws/sns.adapter.ts @@ -33,6 +33,8 @@ export interface SNSAdapterOptions { /** * The adapter to handle requests from AWS SNS. * + * The option of `responseWithErrors` is ignored by this adapter and we always call `resolver.fail` with the error. + * * {@link https://docs.aws.amazon.com/pt_br/lambda/latest/dg/with-sns.html Event Reference} * * @example```typescript @@ -70,7 +72,7 @@ export class SNSAdapter public canHandle(event: unknown): event is SNSEvent { const snsEvent = event as Partial; - if (!Array.isArray(snsEvent.Records)) return false; + if (!Array.isArray(snsEvent?.Records)) return false; const eventSource = snsEvent.Records[0]?.EventSource; @@ -81,13 +83,22 @@ export class SNSAdapter * @inheritDoc */ public getRequest(event: SNSEvent): AdapterRequest { - const path = getDefaultIfUndefined(this.options?.snsForwardPath, '/sqs'); + const path = getDefaultIfUndefined(this.options?.snsForwardPath, '/sns'); const method = getDefaultIfUndefined( this.options?.snsForwardMethod, 'POST' ); - const headers = { host: 'sns.amazonaws.com' }; - const [body] = getEventBodyAsBuffer(JSON.stringify(event), false); + + const [body, contentLength] = getEventBodyAsBuffer( + JSON.stringify(event), + false + ); + + const headers = { + host: 'sns.amazonaws.com', + 'content-type': 'application/json', + 'content-length': String(contentLength), + }; return { method, diff --git a/src/v2/adapters/aws/sqs.adapter.ts b/src/v2/adapters/aws/sqs.adapter.ts index 4b07e3b0..f94ca975 100644 --- a/src/v2/adapters/aws/sqs.adapter.ts +++ b/src/v2/adapters/aws/sqs.adapter.ts @@ -33,6 +33,8 @@ export interface SQSAdapterOptions { /** * The adapter to handle requests from AWS SQS. * + * The option of `responseWithErrors` is ignored by this adapter and we always call `resolver.fail` with the error. + * * {@link https://docs.aws.amazon.com/pt_br/lambda/latest/dg/with-sqs.html Event Reference} * * @example```typescript diff --git a/test/adapters/aws/sns.adapter.spec.ts b/test/adapters/aws/sns.adapter.spec.ts new file mode 100644 index 00000000..29cee8f9 --- /dev/null +++ b/test/adapters/aws/sns.adapter.spec.ts @@ -0,0 +1,129 @@ +import { SNSEvent } from 'aws-lambda'; +import { SNSAdapter } from '../../../src/v2/adapters/aws'; +import { Resolver } from '../../../src/v2/contracts'; +import { + EmptyResponse, + getEventBodyAsBuffer, + IEmptyResponse, + ILogger, +} from '../../../src/v2/core'; +import { createCanHandleTestsForAdapter } from '../utils/can-handle'; +import { createSNSEvent } from './utils/sns'; + +describe(SNSAdapter.name, () => { + let adapter!: SNSAdapter; + + beforeEach(() => { + adapter = new SNSAdapter(); + }); + + describe('getAdapterName', () => { + it('should be the same name of the class', () => { + expect(adapter.getAdapterName()).toBe(SNSAdapter.name); + }); + }); + + createCanHandleTestsForAdapter(() => new SNSAdapter(), undefined); + + describe('getRequest', () => { + it('should return the correct mapping for the request', () => { + const event = createSNSEvent(); + + const result = adapter.getRequest(event); + + expect(result.method).toBe('POST'); + expect(result.path).toBe('/sns'); + expect(result.headers).toHaveProperty('host', 'sns.amazonaws.com'); + expect(result.headers).toHaveProperty('content-type', 'application/json'); + + const [bodyBuffer, contentLength] = getEventBodyAsBuffer( + JSON.stringify(event), + false + ); + + expect(result.body).toBeInstanceOf(Buffer); + expect(result.body).toStrictEqual(bodyBuffer); + + expect(result.headers).toHaveProperty( + 'content-length', + String(contentLength) + ); + }); + + it('should return the correct mapping for the request with custom path and method', () => { + const event = createSNSEvent(); + + const method = 'PUT'; + const path = '/custom/sns'; + + const customAdapter = new SNSAdapter({ + snsForwardMethod: method, + snsForwardPath: path, + }); + + const result = customAdapter.getRequest(event); + + expect(result.method).toBe(method); + expect(result.path).toBe(path); + expect(result.headers).toHaveProperty('host', 'sns.amazonaws.com'); + expect(result.headers).toHaveProperty('content-type', 'application/json'); + + const [bodyBuffer, contentLength] = getEventBodyAsBuffer( + JSON.stringify(event), + false + ); + + expect(result.body).toBeInstanceOf(Buffer); + expect(result.body).toStrictEqual(bodyBuffer); + + expect(result.headers).toHaveProperty( + 'content-length', + String(contentLength) + ); + }); + }); + + describe('getResponse', () => { + it('should return the correct mapping for the response', () => { + const result = adapter.getResponse(); + + expect(result).toBe(EmptyResponse); + }); + }); + + describe('onErrorWhileForwarding', () => { + it('should resolver just call fail without get response', () => { + const event = createSNSEvent(); + + const error = new Error('fail because I need to test.'); + const resolver: Resolver = { + fail: jest.fn(), + succeed: jest.fn(), + }; + + const oldGetResponse = adapter.getResponse.bind(adapter); + + let getResponseResult: IEmptyResponse; + + adapter.getResponse = jest.fn(() => { + getResponseResult = oldGetResponse(); + + return getResponseResult; + }); + + adapter.onErrorWhileForwarding({ + event, + error, + resolver, + log: {} as ILogger, + respondWithErrors: false, + }); + + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(adapter.getResponse).toHaveBeenCalledTimes(0); + + expect(resolver.fail).toHaveBeenCalledTimes(1); + expect(resolver.succeed).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/test/adapters/aws/utils/alb-event.ts b/test/adapters/aws/utils/alb-event.ts index e4575357..8a9eacc6 100644 --- a/test/adapters/aws/utils/alb-event.ts +++ b/test/adapters/aws/utils/alb-event.ts @@ -1,5 +1,8 @@ import { ALBEvent } from 'aws-lambda'; +/** + * Sample event from {@link https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html} + */ export function createAlbEvent( httpMethod: string, path: string, diff --git a/test/adapters/aws/utils/dynamodb.ts b/test/adapters/aws/utils/dynamodb.ts index 97f408f3..95061756 100644 --- a/test/adapters/aws/utils/dynamodb.ts +++ b/test/adapters/aws/utils/dynamodb.ts @@ -1,5 +1,8 @@ import { DynamoDBStreamEvent } from 'aws-lambda'; +/** + * Sample event from {@link https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html} + */ export function createDynamoDBEvent(): DynamoDBStreamEvent { return { Records: [ diff --git a/test/adapters/aws/utils/event-bridge.ts b/test/adapters/aws/utils/event-bridge.ts index 80867030..91feb27d 100644 --- a/test/adapters/aws/utils/event-bridge.ts +++ b/test/adapters/aws/utils/event-bridge.ts @@ -1,5 +1,8 @@ import { EventBridgeEvent } from 'aws-lambda'; +/** + * Sample event from {@link https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html} + */ export function createEventBridgeEvent(): EventBridgeEvent { return { version: '0', @@ -21,6 +24,9 @@ export function createEventBridgeEvent(): EventBridgeEvent { }; } +/** + * Sample event from {@link https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents.html} + */ export function createEventBridgeEventSimple(): EventBridgeEvent { return { version: '0', diff --git a/test/adapters/aws/utils/events.ts b/test/adapters/aws/utils/events.ts index 3cec264c..89fbb8f3 100644 --- a/test/adapters/aws/utils/events.ts +++ b/test/adapters/aws/utils/events.ts @@ -4,6 +4,7 @@ import { ApiGatewayV2Adapter, DynamoDBAdapter, EventBridgeAdapter, + SNSAdapter, SQSAdapter, } from '../../../../src/v2/adapters/aws'; import { @@ -18,6 +19,7 @@ import { createEventBridgeEventSimple, } from './event-bridge'; import { createSQSEvent } from './sqs'; +import { createSNSEvent } from './sns'; export const allAWSEvents: Array<[string, any]> = [ ['fake-to-test-undefined-event', undefined], @@ -62,4 +64,5 @@ export const allAWSEvents: Array<[string, any]> = [ [EventBridgeAdapter.name, createEventBridgeEvent()], [EventBridgeAdapter.name, createEventBridgeEventSimple()], [SQSAdapter.name, createSQSEvent()], + [SNSAdapter.name, createSNSEvent()], ]; diff --git a/test/adapters/aws/utils/sns.ts b/test/adapters/aws/utils/sns.ts new file mode 100644 index 00000000..b0c5908c --- /dev/null +++ b/test/adapters/aws/utils/sns.ts @@ -0,0 +1,42 @@ +import { SNSEvent } from 'aws-lambda'; + +/** + * Sample event from {@link https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html} + */ +export function createSNSEvent(): SNSEvent { + return { + Records: [ + { + EventVersion: '1.0', + EventSubscriptionArn: + 'arn:aws:sns:us-east-2:123456789012:sns-lambda:21be56ed-a058-49f5-8c98-aedd2564c486', + EventSource: 'aws:sns', + Sns: { + SignatureVersion: '1', + Timestamp: '2019-01-02T12:45:07.000Z', + Signature: + 'tcc6faL2yUC6dgZdmrwh1Y4cGa/ebXEkAi6RibDsvpi+tE/1+82j...65r==', + SigningCertUrl: + 'https://sns.us-east-2.amazonaws.com/SimpleNotificationService-ac565b8b1a6c5d002d285f9598aa1d9b.pem', + MessageId: '95df01b4-ee98-5cb9-9903-4c221d41eb5e', + Message: 'Hello from SNS!', + MessageAttributes: { + Test: { + Type: 'String', + Value: 'TestString', + }, + TestBinary: { + Type: 'Binary', + Value: 'TestBinary', + }, + }, + Type: 'Notification', + UnsubscribeUrl: + 'https://sns.us-east-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:123456789012:test-lambda:21be56ed-a058-49f5-8c98-aedd2564c486', + TopicArn: 'arn:aws:sns:us-east-2:123456789012:sns-lambda', + Subject: 'TestInvoke', + }, + }, + ], + }; +} diff --git a/test/adapters/aws/utils/sqs.ts b/test/adapters/aws/utils/sqs.ts index d6918629..d58d96a0 100644 --- a/test/adapters/aws/utils/sqs.ts +++ b/test/adapters/aws/utils/sqs.ts @@ -1,5 +1,8 @@ import { SQSEvent } from 'aws-lambda'; +/** + * Sample event from {@link https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html} + */ export function createSQSEvent(): SQSEvent { return { Records: [