From c3cd864876d5c4a3f72934f69ecad8fc2a082be0 Mon Sep 17 00:00:00 2001 From: Alexander Schueren Date: Wed, 10 Jul 2024 11:54:12 +0200 Subject: [PATCH 1/2] add testing section to parser --- docs/utilities/parser.md | 48 +++++++++++++++++ .../snippets/parser/safeParseDecorator.ts | 7 ++- examples/snippets/parser/unitTestDecorator.ts | 34 ++++++++++++ examples/snippets/parser/unitTestSafeParse.ts | 53 +++++++++++++++++++ 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 examples/snippets/parser/unitTestDecorator.ts create mode 100644 examples/snippets/parser/unitTestSafeParse.ts diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index 3784c37376..b85344c036 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** 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="29" + --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/decorator.ts" + ``` + +=== "schema.ts" + + ```typescript" + --8<-- "examples/snippets/parser/schema.ts" + ``` + +This also works when using `safeParse` option. + +=== "handlerSafeParse.test.ts" + + ```typescript hl_lines="24-32 38 48" + --8<-- "examples/snippets/parser/unitTestSafeParse.ts" + ``` + + 1. Use double assertion to pass expected types to the handler + +=== "handlerSafeParse.ts" + + ```typescript + --8<-- "examples/snippets/parser/safeParseDecorator.ts" + ``` + +=== "schema.ts" + + ```typescript" + --8<-- "examples/snippets/parser/schema.ts" + ``` 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/unitTestDecorator.ts b/examples/snippets/parser/unitTestDecorator.ts new file mode 100644 index 0000000000..ca113b8eb9 --- /dev/null +++ b/examples/snippets/parser/unitTestDecorator.ts @@ -0,0 +1,34 @@ +import { orderSchema } from 'examples/snippets/parser/schema'; +import { Context } from 'aws-lambda'; +import { handler } from 'examples/snippets/parser/decorator'; +import { z } from 'zod'; + +describe('Test handler', () => { + type Order = z.infer; + + 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.not.toThrow(); + }); + + 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..174b59a4dd --- /dev/null +++ b/examples/snippets/parser/unitTestSafeParse.ts @@ -0,0 +1,53 @@ +import { orderSchema } from 'examples/snippets/parser/schema'; +import { Context } from 'aws-lambda'; +import { handler } from 'examples/snippets/parser/safeParseDecorator'; +import { z } from 'zod'; +import { + ParsedResult, + EventBridgeEvent, +} from '@aws-lambda-powertools/parser/types'; + +describe('Test handler', () => { + type Order = z.infer; + + 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.not.toThrow(); + }); + + 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(); + }); +}); From 519fd2f8f2f38824d3235e3405d22835119c875e Mon Sep 17 00:00:00 2001 From: Alexander Schueren Date: Wed, 10 Jul 2024 14:00:19 +0200 Subject: [PATCH 2/2] added new handler for test section, addressed PR feedback --- docs/utilities/parser.md | 16 ++++----- examples/snippets/parser/handlerDecorator.ts | 20 +++++++++++ .../parser/handlerSafeParseDecorator.ts | 36 +++++++++++++++++++ examples/snippets/parser/schema.ts | 4 ++- examples/snippets/parser/unitTestDecorator.ts | 11 +++--- examples/snippets/parser/unitTestSafeParse.ts | 11 +++--- 6 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 examples/snippets/parser/handlerDecorator.ts create mode 100644 examples/snippets/parser/handlerSafeParseDecorator.ts diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index b85344c036..d8e836184d 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -252,27 +252,27 @@ We recommend to use the types provided by the parser utility. If you encounter a ## Testing your code -When testing your handler with **parser decorator** you need to use double assetion to bypass TypeScript type checking in your tests. +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="29" + ```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/decorator.ts" + --8<-- "examples/snippets/parser/handlerDecorator.ts" ``` === "schema.ts" - ```typescript" + ```typescript --8<-- "examples/snippets/parser/schema.ts" ``` @@ -280,7 +280,7 @@ This also works when using `safeParse` option. === "handlerSafeParse.test.ts" - ```typescript hl_lines="24-32 38 48" + ```typescript hl_lines="21-29 35 45" --8<-- "examples/snippets/parser/unitTestSafeParse.ts" ``` @@ -289,11 +289,11 @@ This also works when using `safeParse` option. === "handlerSafeParse.ts" ```typescript - --8<-- "examples/snippets/parser/safeParseDecorator.ts" + --8<-- "examples/snippets/parser/handlerSafeParseDecorator.ts" ``` === "schema.ts" - ```typescript" + ```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/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 index ca113b8eb9..eabc1c0f59 100644 --- a/examples/snippets/parser/unitTestDecorator.ts +++ b/examples/snippets/parser/unitTestDecorator.ts @@ -1,11 +1,8 @@ -import { orderSchema } from 'examples/snippets/parser/schema'; -import { Context } from 'aws-lambda'; -import { handler } from 'examples/snippets/parser/decorator'; -import { z } from 'zod'; +import type { Context } from 'aws-lambda'; +import type { Order } from './schema.js'; +import { handler } from './decorator.js'; describe('Test handler', () => { - type Order = z.infer; - it('should parse event successfully', async () => { const testEvent = { id: 123, @@ -19,7 +16,7 @@ describe('Test handler', () => { ], }; - await expect(handler(testEvent, {} as Context)).resolves.not.toThrow(); + await expect(handler(testEvent, {} as Context)).resolves.toEqual(123); }); it('should throw error if event is invalid', async () => { diff --git a/examples/snippets/parser/unitTestSafeParse.ts b/examples/snippets/parser/unitTestSafeParse.ts index 174b59a4dd..9f83f3a0af 100644 --- a/examples/snippets/parser/unitTestSafeParse.ts +++ b/examples/snippets/parser/unitTestSafeParse.ts @@ -1,15 +1,12 @@ -import { orderSchema } from 'examples/snippets/parser/schema'; -import { Context } from 'aws-lambda'; -import { handler } from 'examples/snippets/parser/safeParseDecorator'; -import { z } from 'zod'; +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', () => { - type Order = z.infer; - it('should parse event successfully', async () => { const testEvent = { version: '0', @@ -38,7 +35,7 @@ describe('Test handler', () => { testEvent as unknown as ParsedResult, // (1)! {} as Context ) - ).resolves.not.toThrow(); + ).resolves.toEqual(10876546789); }); it('should throw error if event is invalid', async () => {