diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index 3784c37376..d8e836184d 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -249,3 +249,51 @@ The package `@types/aws-lambda` is a popular project that contains type definiti Powertools parser utility also bring AWS Lambda event types based on the built-in schema definitions. We recommend to use the types provided by the parser utility. If you encounter any issues or have any feedback, please [submit an issue](https://github.com/aws-powertools/powertools-lambda-typescript/issues/new/choose). + +## Testing your code + +When testing your handler with [**parser decorator**](#parse-events) you need to use double assetion to bypass TypeScript type checking in your tests. +This is useful when you want to test the handler for invalid payloads or when you want to test the error handling. +If you are you use middy middleware, you don't need to do this. + +=== "handlerDecorator.test.ts" + + ```typescript hl_lines="26" + --8<-- "examples/snippets/parser/unitTestDecorator.ts" + ``` + + 1. Use double assertion `as unknown as X` to bypass TypeScript type checking in your tests + +=== "handlerDecorator.ts" + + ```typescript + --8<-- "examples/snippets/parser/handlerDecorator.ts" + ``` + +=== "schema.ts" + + ```typescript + --8<-- "examples/snippets/parser/schema.ts" + ``` + +This also works when using `safeParse` option. + +=== "handlerSafeParse.test.ts" + + ```typescript hl_lines="21-29 35 45" + --8<-- "examples/snippets/parser/unitTestSafeParse.ts" + ``` + + 1. Use double assertion to pass expected types to the handler + +=== "handlerSafeParse.ts" + + ```typescript + --8<-- "examples/snippets/parser/handlerSafeParseDecorator.ts" + ``` + +=== "schema.ts" + + ```typescript + --8<-- "examples/snippets/parser/schema.ts" + ``` diff --git a/examples/snippets/parser/handlerDecorator.ts b/examples/snippets/parser/handlerDecorator.ts new file mode 100644 index 0000000000..c8b3cbd40a --- /dev/null +++ b/examples/snippets/parser/handlerDecorator.ts @@ -0,0 +1,20 @@ +import type { Context } from 'aws-lambda'; +import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; +import { parser } from '@aws-lambda-powertools/parser'; +import { Logger } from '@aws-lambda-powertools/logger'; +import { orderSchema, type Order } from './schema.js'; + +const logger = new Logger(); + +class Lambda implements LambdaInterface { + @parser({ schema: orderSchema }) + public async handler(event: Order, _context: Context): Promise { + logger.info('Processing event', { event }); + + // ... business logic + return event.id; + } +} + +const myFunction = new Lambda(); +export const handler = myFunction.handler.bind(myFunction); diff --git a/examples/snippets/parser/handlerSafeParseDecorator.ts b/examples/snippets/parser/handlerSafeParseDecorator.ts new file mode 100644 index 0000000000..55a13e4519 --- /dev/null +++ b/examples/snippets/parser/handlerSafeParseDecorator.ts @@ -0,0 +1,36 @@ +import type { Context } from 'aws-lambda'; +import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; +import { parser } from '@aws-lambda-powertools/parser'; +import { Logger } from '@aws-lambda-powertools/logger'; +import { orderSchema, type Order } from './schema.js'; +import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes'; +import type { + ParsedResult, + EventBridgeEvent, +} from '@aws-lambda-powertools/parser/types'; + +const logger = new Logger(); + +class Lambda implements LambdaInterface { + @parser({ + schema: orderSchema, + envelope: EventBridgeEnvelope, + safeParse: true, + }) + public async handler( + event: ParsedResult, + _context: Context + ): Promise { + logger.info('Processing event', { event }); + if (event.success) { + // ... business logic + return event.data.id; + } else { + logger.error('Failed to parse event', { event }); + throw new Error('Failed to parse event'); + } + } +} + +const myFunction = new Lambda(); +export const handler = myFunction.handler.bind(myFunction); diff --git a/examples/snippets/parser/safeParseDecorator.ts b/examples/snippets/parser/safeParseDecorator.ts index aaf4d19ca9..7e709c97a4 100644 --- a/examples/snippets/parser/safeParseDecorator.ts +++ b/examples/snippets/parser/safeParseDecorator.ts @@ -7,6 +7,7 @@ import type { EventBridgeEvent, } from '@aws-lambda-powertools/parser/types'; import { Logger } from '@aws-lambda-powertools/logger'; +import { EventBridgeEnvelope } from '@aws-lambda-powertools/parser/envelopes'; const logger = new Logger(); @@ -26,7 +27,11 @@ const orderSchema = z.object({ type Order = z.infer; class Lambda implements LambdaInterface { - @parser({ schema: orderSchema, safeParse: true }) // (1)! + @parser({ + schema: orderSchema, + envelope: EventBridgeEnvelope, + safeParse: true, + }) // (1)! public async handler( event: ParsedResult, _context: Context diff --git a/examples/snippets/parser/schema.ts b/examples/snippets/parser/schema.ts index f14db32fff..1340b40f26 100644 --- a/examples/snippets/parser/schema.ts +++ b/examples/snippets/parser/schema.ts @@ -13,4 +13,6 @@ const orderSchema = z.object({ optionalField: z.string().optional(), }); -export { orderSchema }; +type Order = z.infer; + +export { orderSchema, type Order }; diff --git a/examples/snippets/parser/unitTestDecorator.ts b/examples/snippets/parser/unitTestDecorator.ts new file mode 100644 index 0000000000..eabc1c0f59 --- /dev/null +++ b/examples/snippets/parser/unitTestDecorator.ts @@ -0,0 +1,31 @@ +import type { Context } from 'aws-lambda'; +import type { Order } from './schema.js'; +import { handler } from './decorator.js'; + +describe('Test handler', () => { + it('should parse event successfully', async () => { + const testEvent = { + id: 123, + description: 'test', + items: [ + { + id: 1, + quantity: 1, + description: 'item1', + }, + ], + }; + + await expect(handler(testEvent, {} as Context)).resolves.toEqual(123); + }); + + it('should throw error if event is invalid', async () => { + const testEvent = { foo: 'bar' }; + await expect( + handler( + testEvent as unknown as Order, // (1)! + {} as Context + ) + ).rejects.toThrow(); + }); +}); diff --git a/examples/snippets/parser/unitTestSafeParse.ts b/examples/snippets/parser/unitTestSafeParse.ts new file mode 100644 index 0000000000..9f83f3a0af --- /dev/null +++ b/examples/snippets/parser/unitTestSafeParse.ts @@ -0,0 +1,50 @@ +import type { Order } from './schema.js'; +import type { Context } from 'aws-lambda'; +import { handler } from './safeParseDecorator.js'; +import { + ParsedResult, + EventBridgeEvent, +} from '@aws-lambda-powertools/parser/types'; + +describe('Test handler', () => { + it('should parse event successfully', async () => { + const testEvent = { + version: '0', + id: '6a7e8feb-b491-4cf7-a9f1-bf3703467718', + 'detail-type': 'OrderPurchased', + source: 'OrderService', + account: '111122223333', + time: '2020-10-22T18:43:48Z', + region: 'us-west-1', + resources: ['some_additional'], + detail: { + id: 10876546789, + description: 'My order', + items: [ + { + id: 1015938732, + quantity: 1, + description: 'item xpto', + }, + ], + }, + }; + + await expect( + handler( + testEvent as unknown as ParsedResult, // (1)! + {} as Context + ) + ).resolves.toEqual(10876546789); + }); + + it('should throw error if event is invalid', async () => { + const testEvent = { foo: 'bar' }; + await expect( + handler( + testEvent as unknown as ParsedResult, + {} as Context + ) + ).rejects.toThrow(); + }); +});