From c655f94edb89e6f46c5d7594e14a91662e5223ce Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Fri, 24 Jan 2025 10:06:46 +0100 Subject: [PATCH] fix(parser): set min length of 1 to s3 event lists --- packages/parser/src/schemas/s3.ts | 4 +- .../events/{s3Event.json => s3/base.json} | 0 .../decoded-key.json} | 0 .../delete-object.json} | 0 .../eventbridge-object-created.json} | 0 .../eventbridge-object-deleted.json} | 0 .../eventbridge-object-expired.json} | 0 .../eventbridge-object-restored.json} | 0 .../{s3EventGlacier.json => s3/glacier.json} | 0 .../object-iam-user.json} | 0 .../object-temp-credentials.json} | 0 .../{s3SqsEvent.json => s3/sqs-event.json} | 0 packages/parser/tests/unit/schema/s3.test.ts | 254 +++++++++++++----- packages/parser/tests/unit/schema/utils.ts | 12 - 14 files changed, 186 insertions(+), 84 deletions(-) rename packages/parser/tests/events/{s3Event.json => s3/base.json} (100%) rename packages/parser/tests/events/{s3EventDecodedKey.json => s3/decoded-key.json} (100%) rename packages/parser/tests/events/{s3EventDeleteObject.json => s3/delete-object.json} (100%) rename packages/parser/tests/events/{s3EventBridgeNotificationObjectCreatedEvent.json => s3/eventbridge-object-created.json} (100%) rename packages/parser/tests/events/{s3EventBridgeNotificationObjectDeletedEvent.json => s3/eventbridge-object-deleted.json} (100%) rename packages/parser/tests/events/{s3EventBridgeNotificationObjectExpiredEvent.json => s3/eventbridge-object-expired.json} (100%) rename packages/parser/tests/events/{s3EventBridgeNotificationObjectRestoreCompletedEvent.json => s3/eventbridge-object-restored.json} (100%) rename packages/parser/tests/events/{s3EventGlacier.json => s3/glacier.json} (100%) rename packages/parser/tests/events/{s3ObjectEventIAMUser.json => s3/object-iam-user.json} (100%) rename packages/parser/tests/events/{s3ObjectEventTempCredentials.json => s3/object-temp-credentials.json} (100%) rename packages/parser/tests/events/{s3SqsEvent.json => s3/sqs-event.json} (100%) diff --git a/packages/parser/src/schemas/s3.ts b/packages/parser/src/schemas/s3.ts index 68c77a9755..07f5a01e33 100644 --- a/packages/parser/src/schemas/s3.ts +++ b/packages/parser/src/schemas/s3.ts @@ -166,7 +166,7 @@ const S3EventNotificationEventBridgeSchema = EventBridgeSchema.extend({ * @see {@link https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-content-structure.html} */ const S3Schema = z.object({ - Records: z.array(S3RecordSchema), + Records: z.array(S3RecordSchema).min(1), }); const S3SqsEventNotificationRecordSchema = SqsRecordSchema.extend({ @@ -204,7 +204,7 @@ const S3SqsEventNotificationRecordSchema = SqsRecordSchema.extend({ * @see {@link types.S3SqsEventNotification | S3SqsEventNotification } */ const S3SqsEventNotificationSchema = z.object({ - Records: z.array(S3SqsEventNotificationRecordSchema), + Records: z.array(S3SqsEventNotificationRecordSchema).min(1), }); const S3ObjectContext = z.object({ diff --git a/packages/parser/tests/events/s3Event.json b/packages/parser/tests/events/s3/base.json similarity index 100% rename from packages/parser/tests/events/s3Event.json rename to packages/parser/tests/events/s3/base.json diff --git a/packages/parser/tests/events/s3EventDecodedKey.json b/packages/parser/tests/events/s3/decoded-key.json similarity index 100% rename from packages/parser/tests/events/s3EventDecodedKey.json rename to packages/parser/tests/events/s3/decoded-key.json diff --git a/packages/parser/tests/events/s3EventDeleteObject.json b/packages/parser/tests/events/s3/delete-object.json similarity index 100% rename from packages/parser/tests/events/s3EventDeleteObject.json rename to packages/parser/tests/events/s3/delete-object.json diff --git a/packages/parser/tests/events/s3EventBridgeNotificationObjectCreatedEvent.json b/packages/parser/tests/events/s3/eventbridge-object-created.json similarity index 100% rename from packages/parser/tests/events/s3EventBridgeNotificationObjectCreatedEvent.json rename to packages/parser/tests/events/s3/eventbridge-object-created.json diff --git a/packages/parser/tests/events/s3EventBridgeNotificationObjectDeletedEvent.json b/packages/parser/tests/events/s3/eventbridge-object-deleted.json similarity index 100% rename from packages/parser/tests/events/s3EventBridgeNotificationObjectDeletedEvent.json rename to packages/parser/tests/events/s3/eventbridge-object-deleted.json diff --git a/packages/parser/tests/events/s3EventBridgeNotificationObjectExpiredEvent.json b/packages/parser/tests/events/s3/eventbridge-object-expired.json similarity index 100% rename from packages/parser/tests/events/s3EventBridgeNotificationObjectExpiredEvent.json rename to packages/parser/tests/events/s3/eventbridge-object-expired.json diff --git a/packages/parser/tests/events/s3EventBridgeNotificationObjectRestoreCompletedEvent.json b/packages/parser/tests/events/s3/eventbridge-object-restored.json similarity index 100% rename from packages/parser/tests/events/s3EventBridgeNotificationObjectRestoreCompletedEvent.json rename to packages/parser/tests/events/s3/eventbridge-object-restored.json diff --git a/packages/parser/tests/events/s3EventGlacier.json b/packages/parser/tests/events/s3/glacier.json similarity index 100% rename from packages/parser/tests/events/s3EventGlacier.json rename to packages/parser/tests/events/s3/glacier.json diff --git a/packages/parser/tests/events/s3ObjectEventIAMUser.json b/packages/parser/tests/events/s3/object-iam-user.json similarity index 100% rename from packages/parser/tests/events/s3ObjectEventIAMUser.json rename to packages/parser/tests/events/s3/object-iam-user.json diff --git a/packages/parser/tests/events/s3ObjectEventTempCredentials.json b/packages/parser/tests/events/s3/object-temp-credentials.json similarity index 100% rename from packages/parser/tests/events/s3ObjectEventTempCredentials.json rename to packages/parser/tests/events/s3/object-temp-credentials.json diff --git a/packages/parser/tests/events/s3SqsEvent.json b/packages/parser/tests/events/s3/sqs-event.json similarity index 100% rename from packages/parser/tests/events/s3SqsEvent.json rename to packages/parser/tests/events/s3/sqs-event.json diff --git a/packages/parser/tests/unit/schema/s3.test.ts b/packages/parser/tests/unit/schema/s3.test.ts index dcc3a7918e..600aa6a1bf 100644 --- a/packages/parser/tests/unit/schema/s3.test.ts +++ b/packages/parser/tests/unit/schema/s3.test.ts @@ -4,98 +4,212 @@ import { S3ObjectLambdaEventSchema, S3Schema, S3SqsEventNotificationSchema, -} from '../../../src/schemas/'; -import { TestEvents } from './utils.js'; +} from '../../../src/schemas/s3.js'; +import type { + S3Event, + S3EventNotificationEventBridge, + S3ObjectLambdaEvent, + S3SqsEventNotification, +} from '../../../src/types/schema.js'; +import { getTestEvent, omit } from './utils.js'; -describe('S3 ', () => { - it('should parse s3 event', () => { - const s3Event = TestEvents.s3Event; +describe('Schema: S3', () => { + const eventsPath = 's3'; + const baseEvent = getTestEvent({ + eventsPath, + filename: 'base', + }); + const baseLambdaEvent = getTestEvent({ + eventsPath, + filename: 'object-iam-user', + }); + + it('parses an S3 event', () => { + // Prepare + const event = structuredClone(baseEvent); - expect(S3Schema.parse(s3Event)).toEqual(s3Event); + // Act + const result = S3Schema.parse(event); + + // Assess + expect(result).toStrictEqual(event); }); - it('should parse s3 event bridge notification event created', () => { - const s3EventBridgeNotificationObjectCreatedEvent = - TestEvents.s3EventBridgeNotificationObjectCreatedEvent; + it('throws if the event is not an S3 event', () => { + // Prepare + const event = { + Records: [], + }; - expect( - S3EventNotificationEventBridgeSchema.parse( - s3EventBridgeNotificationObjectCreatedEvent - ) - ).toEqual(s3EventBridgeNotificationObjectCreatedEvent); + // Act & Assess + expect(() => S3Schema.parse(event)).toThrow(); }); - it('should parse s3 event bridge notification event detelted', () => { - const s3EventBridgeNotificationObjectDeletedEvent = - TestEvents.s3EventBridgeNotificationObjectDeletedEvent; + it('throws if the event is missing required fields', () => { + // Prepare + const event = structuredClone(baseEvent); + // @ts-expect-error - Intentionally remove required field + event.Records[0].s3.bucket.name = undefined; - expect( - S3EventNotificationEventBridgeSchema.parse( - s3EventBridgeNotificationObjectDeletedEvent - ) - ).toEqual(s3EventBridgeNotificationObjectDeletedEvent); + // Act & Assess + expect(() => S3Schema.parse(event)).toThrow(); }); - it('should parse s3 event bridge notification event expired', () => { - const s3EventBridgeNotificationObjectExpiredEvent = - TestEvents.s3EventBridgeNotificationObjectExpiredEvent; - expect( - S3EventNotificationEventBridgeSchema.parse( - s3EventBridgeNotificationObjectExpiredEvent - ) - ).toEqual(s3EventBridgeNotificationObjectExpiredEvent); + it('parses an S3 Glacier event', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'glacier', + }); + + // Act + const result = S3Schema.parse(event); + + // Assess + expect(result).toStrictEqual(event); + }); + + it('parses an S3 event with a decoded key', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'decoded-key', + }); + + // Act + const result = S3Schema.parse(event); + + // Assess + expect(result).toStrictEqual(event); }); - it('should parse s3 sqs notification event', () => { - const s3SqsEvent = TestEvents.s3SqsEvent; - expect(S3SqsEventNotificationSchema.parse(s3SqsEvent)).toEqual(s3SqsEvent); + it('parses an S3 event with a deleted object', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'delete-object', + }); + + // Act + const result = S3Schema.parse(event); + + // Assess + expect(result).toStrictEqual(event); }); - it('should parse s3 event with decoded key', () => { - const s3EventDecodedKey = TestEvents.s3EventDecodedKey; - expect(S3Schema.parse(s3EventDecodedKey)).toEqual(s3EventDecodedKey); + it('parses an S3 Object Lambda with an IAM user', () => { + // Prepare + const event = structuredClone(baseLambdaEvent); + + // Act + const result = S3ObjectLambdaEventSchema.parse(event); + + // Assess + expect(result).toStrictEqual(event); }); - it('should parse s3 event delete object', () => { - const s3EventDeleteObject = TestEvents.s3EventDeleteObject; - expect(S3Schema.parse(s3EventDeleteObject)).toEqual(s3EventDeleteObject); + it('throws if the S3 Object Lambda event is missing required fields', () => { + // Prepare + const event = omit(['getObjectContext'], structuredClone(baseLambdaEvent)); + + // Act & Assess + expect(() => S3ObjectLambdaEventSchema.parse(event)).toThrow(); }); - it('should parse s3 event glacier', () => { - const s3EventGlacier = TestEvents.s3EventGlacier; - expect(S3Schema.parse(s3EventGlacier)).toEqual(s3EventGlacier); + it('parses an S3 Object Lambda with temporary credentials', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'object-temp-credentials', + }); + const expected = structuredClone(event); + // @ts-expect-error - Modifying the expected result to account for type coercion + expected.userIdentity.sessionContext.attributes.mfaAuthenticated = false; + + // Act + const result = S3ObjectLambdaEventSchema.parse(event); + + // Assess + expect(result).toStrictEqual(expected); }); - it('should parse s3 object event iam user', () => { - const s3ObjectEventIAMUser = TestEvents.s3ObjectEventIAMUser; - expect(S3ObjectLambdaEventSchema.parse(s3ObjectEventIAMUser)).toEqual( - s3ObjectEventIAMUser - ); + it('parses an S3 Object Notification EventBridge event', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'eventbridge-object-created', + }); + + // Act + const result = S3EventNotificationEventBridgeSchema.parse(event); + + // Assess + expect(result).toStrictEqual(event); + }); + + it('parses an S3 Object Notification EventBridge event for an object deleted', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'eventbridge-object-deleted', + }); + + // Act + const result = S3EventNotificationEventBridgeSchema.parse(event); + + // Assess + expect(result).toStrictEqual(event); + }); + + it('parses an S3 Object Notification EventBridge event for an object expired', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'eventbridge-object-expired', + }); + + // Act + const result = S3EventNotificationEventBridgeSchema.parse(event); + + // Assess + expect(result).toStrictEqual(event); + }); + + it('parses an S3 Object Notification EventBridge event for an object restored', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'eventbridge-object-restored', + }); + + // Act + const result = S3EventNotificationEventBridgeSchema.parse(event); + + // Assess + expect(result).toStrictEqual(event); + }); + + it('parses an S3 event notification SQS event', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'sqs-event', + }); + + // Prepare + const result = S3SqsEventNotificationSchema.parse(event); + + // Assess + expect(result).toStrictEqual(event); }); - it('should parse s3 object event temp credentials', () => { - // ignore any because we don't want typed json - const s3ObjectEventTempCredentials = - // biome-ignore lint/suspicious/noExplicitAny: no specific typing needed - TestEvents.s3ObjectEventTempCredentials as any; - const parsed = S3ObjectLambdaEventSchema.parse( - s3ObjectEventTempCredentials - ); + it('throws if the S3 event notification SQS event is not valid', () => { + // Prepare + const event = { + Records: [], + }; - expect(parsed.userRequest).toEqual( - s3ObjectEventTempCredentials.userRequest - ); - expect(parsed.getObjectContext).toEqual( - s3ObjectEventTempCredentials.getObjectContext - ); - expect(parsed.configuration).toEqual( - s3ObjectEventTempCredentials.configuration - ); - expect(parsed.userRequest).toEqual( - s3ObjectEventTempCredentials.userRequest - ); - expect( - parsed.userIdentity?.sessionContext?.attributes.mfaAuthenticated - ).toEqual(false); + // Act & Assess + expect(() => S3SqsEventNotificationSchema.parse(event)).toThrow(); }); }); diff --git a/packages/parser/tests/unit/schema/utils.ts b/packages/parser/tests/unit/schema/utils.ts index de56e12a60..048b374ba5 100644 --- a/packages/parser/tests/unit/schema/utils.ts +++ b/packages/parser/tests/unit/schema/utils.ts @@ -23,18 +23,6 @@ const filenames = [ 'lambdaFunctionUrlEvent', 'lambdaFunctionUrlEventPathTrailingSlash', 'lambdaFunctionUrlIAMEvent', - 's3Event', - 's3EventBridgeNotificationObjectCreatedEvent', - 's3EventBridgeNotificationObjectDeletedEvent', - 's3EventBridgeNotificationObjectExpiredEvent', - 's3EventBridgeNotificationObjectRestoreCompletedEvent', - 's3EventDecodedKey', - 's3EventDeleteObject', - 's3EventDeleteObjectWithoutEtagSize', - 's3EventGlacier', - 's3ObjectEventIAMUser', - 's3ObjectEventTempCredentials', - 's3SqsEvent', 'sesEvent', 'vpcLatticeEvent', 'vpcLatticeEventPathTrailingSlash',