diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index bc22b26932..3784c37376 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -64,7 +64,10 @@ Parser comes with the following built-in schemas: | -------------------------------------------- | ------------------------------------------------------------------------------------- | | **AlbSchema** | Lambda Event Source payload for Amazon Application Load Balancer | | **APIGatewayProxyEventSchema** | Lambda Event Source payload for Amazon API Gateway | +| **APIGatewayRequestAuthorizerEventSchema** | Lambda Event Source payload for Amazon API Gateway Request Authorizer | +| **APIGatewayTokenAuthorizerEventSchema** | Lambda Event Source payload for Amazon API Gateway Token Authorizer | | **APIGatewayProxyEventV2Schema** | Lambda Event Source payload for Amazon API Gateway v2 payload | +| **APIGatewayRequestAuthorizerEventV2Schema** | Lambda Event Source payload for Amazon API Gateway v2 Authorizer | | **CloudFormationCustomResourceCreateSchema** | Lambda Event Source payload for AWS CloudFormation `CREATE` operation | | **CloudFormationCustomResourceUpdateSchema** | Lambda Event Source payload for AWS CloudFormation `UPDATE` operation | | **CloudFormationCustomResourceDeleteSchema** | Lambda Event Source payload for AWS CloudFormation `DELETE` operation | diff --git a/packages/parser/jest.config.cjs b/packages/parser/jest.config.cjs index e1dca130b9..23684da5b4 100644 --- a/packages/parser/jest.config.cjs +++ b/packages/parser/jest.config.cjs @@ -9,8 +9,7 @@ module.exports = { '^(\\.{1,2}/.*)\\.js$': '$1', }, transform: { - '^.+\\.ts?$': ['ts-jest', {tsconfig: './tests/tsconfig.json'}], - + '^.+\\.ts?$': ['ts-jest'], }, moduleFileExtensions: ['js', 'ts'], collectCoverageFrom: ['**/src/**/*.ts', '!**/node_modules/**'], diff --git a/packages/parser/package.json b/packages/parser/package.json index 0e5cf39fcd..b99b307422 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -43,6 +43,14 @@ "require": "./lib/cjs/schemas/index.js", "import": "./lib/esm/schemas/index.js" }, + "./schemas/api-gateway": { + "require": "./lib/cjs/schemas/apigw.js", + "import": "./lib/esm/schemas/apigw.js" + }, + "./schemas/api-gatewayv2": { + "require": "./lib/cjs/schemas/apigwv2.js", + "import": "./lib/esm/schemas/apigwv2.js" + }, "./envelopes": { "require": "./lib/cjs/envelopes/index.js", "import": "./lib/esm/envelopes/index.js" @@ -66,6 +74,14 @@ "./lib/cjs/schemas/index.d.ts", "./lib/esm/schemas/index.d.ts" ], + "schemas/api-gateway": [ + "./lib/cjs/schemas/apigw.d.ts", + "./lib/esm/schemas/apigw.d.ts" + ], + "schemas/api-gatewayv2": [ + "./lib/cjs/schemas/apigwv2.d.ts", + "./lib/esm/schemas/apigwv2.d.ts" + ], "envelopes": [ "./lib/cjs/envelopes/index.d.ts", "./lib/esm/envelopes/index.d.ts" diff --git a/packages/parser/src/schemas/apigw-proxy.ts b/packages/parser/src/schemas/apigw-proxy.ts new file mode 100644 index 0000000000..ea35ee5191 --- /dev/null +++ b/packages/parser/src/schemas/apigw-proxy.ts @@ -0,0 +1,45 @@ +import { z } from 'zod'; + +/** + * A zod schema for an API Gateway Certificate + */ +const APIGatewayCert = z.object({ + clientCertPem: z.string(), + subjectDN: z.string(), + issuerDN: z.string(), + serialNumber: z.string(), + validity: z.object({ + notBefore: z.string(), + notAfter: z.string(), + }), +}); + +/** + * A zod schema for an object with string keys and string values + */ +const APIGatewayRecord = z.record(z.string()); + +/** + * A zod schema for an array of strings + */ +const APIGatewayStringArray = z.array(z.string()); + +/** + * A zod schema for API Gateway HTTP methods + */ +const APIGatewayHttpMethod = z.enum([ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'HEAD', + 'OPTIONS', +]); + +export { + APIGatewayCert, + APIGatewayRecord, + APIGatewayStringArray, + APIGatewayHttpMethod, +}; diff --git a/packages/parser/src/schemas/apigw.ts b/packages/parser/src/schemas/apigw.ts index ab4e20c6b2..b31255ab49 100644 --- a/packages/parser/src/schemas/apigw.ts +++ b/packages/parser/src/schemas/apigw.ts @@ -1,16 +1,16 @@ import { z } from 'zod'; +import { + APIGatewayCert, + APIGatewayRecord, + APIGatewayStringArray, + APIGatewayHttpMethod, +} from './apigw-proxy.js'; -const APIGatewayCert = z.object({ - clientCertPem: z.string(), - subjectDN: z.string(), - issuerDN: z.string(), - serialNumber: z.string(), - validity: z.object({ - notBefore: z.string(), - notAfter: z.string(), - }), -}); - +/** + * A zod schema for an API Gateway Event Identity + * + * @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html} + */ const APIGatewayEventIdentity = z.object({ accessKey: z.string().nullish(), accountId: z.string().nullish(), @@ -38,15 +38,27 @@ const APIGatewayEventIdentity = z.object({ clientCert: APIGatewayCert.nullish(), }); +/** + * A zod schema for an API Gateway Event Request Context + * + * @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference} + */ const APIGatewayEventRequestContext = z .object({ accountId: z.string(), apiId: z.string(), + deploymentId: z.string().nullish(), authorizer: z - .object({ - claims: z.record(z.string(), z.any()).nullish(), - scopes: z.array(z.string()).nullish(), - }) + .union([ + z.object({ + integrationLatency: z.number(), + principalId: z.string(), + }), + z.object({ + claims: z.record(z.string(), z.any()), + scopes: APIGatewayStringArray.optional(), + }), + ]) .nullish(), stage: z.string(), protocol: z.string(), @@ -88,32 +100,165 @@ const APIGatewayEventRequestContext = z } ); +/** + * A zod schema for an API Gateway Proxy event + * + * @example + * ```json + * { + * "type": "REQUEST", + * "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + * "resource": "/request", + * "path": "/request", + * "httpMethod": "GET", + * "headers": { + * "X-AMZ-Date": "20170718T062915Z", + * "Accept": "application/json", + * "HeaderAuth1": "headerValue1" + * }, + * "queryStringParameters": { + * "QueryString1": "queryValue1" + * }, + * "pathParameters": {}, + * "stageVariables": null, + * "requestContext": { + * "path": "/request", + * "accountId": "123456789012", + * "resourceId": "05c7jb", + * "stage": "test", + * "requestId": "...", + * "identity": { + * "cognitoIdentityPoolId": null, + * "accountId": null, + * "cognitoIdentityId": null, + * "caller": null, + * "sourceIp": "192.168.1.1", + * "principalOrgId": null, + * "accessKey": null, + * "cognitoAuthenticationType": null, + * "cognitoAuthenticationProvider": null, + * "userArn": null, + * "userAgent": "HTTPie/3.2.2", + * "user": null + * } + * }, + * "resourcePath": "/request", + * "httpMethod": "GET", + * "apiId": "abcdef123" + * } + * ``` + * + * @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html} + */ const APIGatewayProxyEventSchema = z.object({ - version: z.string().optional(), - authorizationToken: z.string().optional(), - identitySource: z.string().optional(), - methodArn: z.string().optional(), - type: z.enum(['TOKEN', 'REQUEST']).optional(), resource: z.string(), path: z.string(), - httpMethod: z.enum([ - 'GET', - 'POST', - 'PUT', - 'PATCH', - 'DELETE', - 'HEAD', - 'OPTIONS', - ]), - headers: z.record(z.string()).optional(), - queryStringParameters: z.record(z.string()).nullable(), - multiValueHeaders: z.record(z.array(z.string())).optional(), - multiValueQueryStringParameters: z.record(z.array(z.string())).nullable(), + httpMethod: APIGatewayHttpMethod, + headers: APIGatewayRecord.nullish(), + multiValueHeaders: z.record(APIGatewayStringArray).nullish(), + queryStringParameters: APIGatewayRecord.nullable(), + multiValueQueryStringParameters: z.record(APIGatewayStringArray).nullable(), + pathParameters: APIGatewayRecord.nullish(), + stageVariables: APIGatewayRecord.nullish(), requestContext: APIGatewayEventRequestContext, - pathParameters: z.record(z.string()).optional().nullish(), - stageVariables: z.record(z.string()).optional().nullish(), - isBase64Encoded: z.boolean().optional(), body: z.string().nullable(), + isBase64Encoded: z.boolean(), +}); + +/** + * A zod schema for an API Gateway Request Authorizer event + * + * @example + * ```json + * { + * "type": "REQUEST", + * "methodArn": "arn:aws:execute-api:us-west-2:123456789012:ymy8tbxw7b/prod/GET/", + * "resource": "/{proxy+}", + * "path": "/hello/world", + * "httpMethod": "GET", + * "headers": { + * "X-AMZ-Date": "20170718T062915Z", + * "Accept": "application/json", + * "HeaderAuth1": "headerValue1" + * }, + * "multiValueHeaders": { + * "X-AMZ-Date": ["20170718T062915Z"], + * "Accept": ["application/json"], + * "HeaderAuth1": ["headerValue1"] + * }, + * "queryStringParameters": {}, + * "multiValueQueryStringParameters": {}, + * "pathParameters": {}, + * "stageVariables": {}, + * "requestContext": { + * "path": "/request", + * "accountId": "123456789012", + * "resourceId": "05c7jb", + * "stage": "test", + * "requestId": "...", + * "identity": { + * "cognitoIdentityPoolId": null, + * "accountId": null, + * "cognitoIdentityId": null, + * "caller": null, + * "sourceIp": "192.168.1.1", + * "principalOrgId": null, + * "accessKey": null, + * "cognitoAuthenticationType": null, + * "cognitoAuthenticationProvider": null, + * "userArn": null, + * "userAgent": "HTTPie/3.2.2", + * "user": null + * } + * }, + * "domainName": "id.execute-api.us-west-2.amazonaws.com", + * "deploymentId": "lle82z", + * "apiId": "ymy8tbxw7b" + * } + * ``` + * + * @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-input.html#w76aac15b9c21c25c21b5} + */ +const APIGatewayRequestAuthorizerEventSchema = z.object({ + type: z.literal('REQUEST'), + methodArn: z.string(), + resource: z.string(), + path: z.string(), + httpMethod: APIGatewayHttpMethod, + headers: APIGatewayRecord, + multiValueHeaders: z.record(APIGatewayStringArray), + queryStringParameters: APIGatewayRecord, + multiValueQueryStringParameters: z.record(APIGatewayStringArray), + pathParameters: APIGatewayRecord, + stageVariables: APIGatewayRecord, + requestContext: APIGatewayEventRequestContext, + domainName: z.string().optional(), + deploymentId: z.string().optional(), + apiId: z.string().optional(), +}); + +/** + * A zod schema for an API Gateway Token Authorizer event + * + * @example + * ```json + * { + * "type": "TOKEN", + * "authorizationToken": "Bearer abcd1234", + * "methodArn": "arn:aws:execute-api:us-west-2:123456789012:ymy8tbxw7b/prod/GET/" + * } + * ``` + * + * @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-input.html#w76aac15b9c21c25c21b3} + */ +const APIGatewayTokenAuthorizerEventSchema = z.object({ + type: z.literal('TOKEN'), + authorizationToken: z.string(), + methodArn: z.string(), }); -export { APIGatewayProxyEventSchema, APIGatewayCert }; +export { + APIGatewayProxyEventSchema, + APIGatewayRequestAuthorizerEventSchema, + APIGatewayTokenAuthorizerEventSchema, +}; diff --git a/packages/parser/src/schemas/apigwv2.ts b/packages/parser/src/schemas/apigwv2.ts index 77491d4864..7d7031fc24 100644 --- a/packages/parser/src/schemas/apigwv2.ts +++ b/packages/parser/src/schemas/apigwv2.ts @@ -1,11 +1,48 @@ import { z } from 'zod'; -import { APIGatewayCert } from './apigw.js'; +import { + APIGatewayCert, + APIGatewayHttpMethod, + APIGatewayRecord, + APIGatewayStringArray, +} from './apigw-proxy.js'; -const RequestContextV2Authorizer = z.object({ +/** + * A zod schema for an API Gateway HTTP API Request Authorizer + * + * If Lambda authorizer is used, the `lambda` property will be set to an object + * containing the `context` object returned by the Lambda authorizer function. + * If no `context` object is returned, the `lambda` property will be set to `null`. + * + * If JWT authorizer is used, the `jwt` property will be set to an object + * containing the `claims` object returned by the JWT authorizer function. Optionally, + * the `scopes` property will be set to an array of scopes returned by the JWT authorizer. + * + * @example + * ```json + * { + * "jwt": { + * "claims": { + * "claim1": "value1", + * "claim2": "value2" + * }, + * "scopes": [ + * "scope1", + * "scope2" + * ] + * } + * } + * ``` + * + * If IAM authorizer is used, the `iam` property will be set to an object + * containing the details of the IAM user making the request. + * + * @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control.html} + */ +const APIGatewayV2RequestAuthorizer = z.object({ jwt: z .object({ claims: z.record(z.string(), z.any()), - scopes: z.array(z.string()).optional(), + scopes: APIGatewayStringArray.nullable(), }) .optional(), iam: z @@ -18,28 +55,46 @@ const RequestContextV2Authorizer = z.object({ userId: z.string().optional(), cognitoIdentity: z .object({ - amr: z.array(z.string()), + amr: APIGatewayStringArray, identityId: z.string(), identityPoolId: z.string(), }) .nullish(), }) .optional(), - lambda: z.record(z.string(), z.any()).optional(), + lambda: z.record(z.string(), z.any()).nullish(), }); -const RequestContextV2Http = z.object({ - method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']), - path: z.string(), - protocol: z.string(), - sourceIp: z.string().ip(), - userAgent: z.string(), -}); - -const RequestContextV2 = z.object({ +/** + * A zod schema for an API Gateway HTTP API Request Context + * + * @example + * ```json + * { + * "accountId": "123456789012", + * "apiId": "api-id", + * "authentication": {} + * "domainName": "id.execute-api.us-east-1.amazonaws.com", + * "domainPrefix": "id", + * "http": { + * "method": "POST", + * "path": "/my/path", + * "protocol": "HTTP/1.1", + * "sourceIp": "...", + * "userAgent": "..." + * }, + * "requestId": "...", + * "routeKey": "$default", + * "stage": "$default", + * "time": "12/Mar/2020:19:03:58 +0000", + * "timeEpoch": 1583348638390 + * } + * ``` + */ +const APIGatewayV2RequestContext = z.object({ accountId: z.string(), apiId: z.string(), - authorizer: RequestContextV2Authorizer.optional(), + authorizer: APIGatewayV2RequestAuthorizer.optional(), authentication: z .object({ clientCert: APIGatewayCert.optional(), @@ -47,7 +102,13 @@ const RequestContextV2 = z.object({ .nullish(), domainName: z.string(), domainPrefix: z.string(), - http: RequestContextV2Http, + http: z.object({ + method: APIGatewayHttpMethod, + path: z.string(), + protocol: z.string(), + sourceIp: z.string().ip(), + userAgent: z.string(), + }), requestId: z.string(), routeKey: z.string(), stage: z.string(), @@ -55,19 +116,133 @@ const RequestContextV2 = z.object({ timeEpoch: z.number(), }); +/** + * A zod schema for an API Gateway HTTP API Proxy event + * + * @example + * ```json + * { + * "version": "2.0", + * "routeKey": "$default", + * "rawPath": "/my/path", + * "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + * "cookies": ["cookie1", "cookie2"], + * "headers": { + * "header1": "value1", + * "header2": "value1,value2" + * }, + * "queryStringParameters": { + * "parameter1": "value1,value2", + * "parameter2": "value" + * }, + * "requestContext": { + * "accountId": "123456789012", + * "apiId": "api-id", + * "authentication": {} + * "domainName": "id.execute-api.us-east-1.amazonaws.com", + * "domainPrefix": "id", + * "http": { + * "method": "POST", + * "path": "/my/path", + * "protocol": "HTTP/1.1", + * "sourceIp": "...", + * "userAgent": "..." + * }, + * "requestId": "...", + * "routeKey": "$default", + * "stage": "$default", + * "time": "12/Mar/2020:19:03:58 +0000", + * "timeEpoch": 1583348638390 + * }, + * "body": "Hello from Lambda", + * "pathParameters": {}, + * "isBase64Encoded": false, + * "stageVariables": {} + * } + * ``` + * + * @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html} + */ const APIGatewayProxyEventV2Schema = z.object({ version: z.string(), routeKey: z.string(), rawPath: z.string(), rawQueryString: z.string(), - cookies: z.array(z.string()).optional(), - headers: z.record(z.string()), - queryStringParameters: z.record(z.string()).optional(), - pathParameters: z.record(z.string()).optional().nullish(), - stageVariables: z.record(z.string()).optional().nullish(), - requestContext: RequestContextV2, + cookies: APIGatewayStringArray.optional(), + headers: APIGatewayRecord, + queryStringParameters: APIGatewayRecord.optional(), + requestContext: APIGatewayV2RequestContext, body: z.string().optional(), + pathParameters: APIGatewayRecord.nullish(), isBase64Encoded: z.boolean(), + stageVariables: APIGatewayRecord.nullish(), +}); + +/** + * A zod schema for an API Gateway HTTP API Request Authorizer event + * + * @example + * ```json + * { + * "version": "2.0", + * "type": "REQUEST", + * "routeArn": "arn:aws:execute-api:us-east-1:123456789012:api-id/stage-name/GET/mydemoresource", + * "identitySource": ["user1", "123"], + * "routeKey": "$default", + * "rawPath": "/mydemoresource", + * "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + * "cookies": ["cookie1", "cookie2"], + * "headers": { + * "header1": "value1", + * "header2": "value1,value2" + * }, + * "queryStringParameters": { + * "parameter1": "value1,value2", + * "parameter2": "value" + * }, + * "requestContext": { + * "accountId": "123456789012", + * "apiId": "api-id", + * "authentication": {} + * "domainName": "id.execute-api.us-east-1.amazonaws.com", + * "domainPrefix": "id", + * "http": { + * "method": "POST", + * "path": "/my/path", + * "protocol": "HTTP/1.1", + * "sourceIp": "...", + * "userAgent": "..." + * }, + * "requestId": "...", + * "routeKey": "$default", + * "stage": "$default", + * "time": "12/Mar/2020:19:03:58 +0000", + * "timeEpoch": 1583348638390 + * }, + * "pathParameters": {}, + * "stageVariables": {} + * } + * ``` + * + * @see {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html} + */ +const APIGatewayRequestAuthorizerEventV2Schema = z.object({ + version: z.literal('2.0'), + type: z.literal('REQUEST'), + routeArn: z.string(), + identitySource: APIGatewayStringArray, + routeKey: z.string(), + rawPath: z.string(), + rawQueryString: z.string(), + cookies: APIGatewayStringArray.optional(), + headers: APIGatewayRecord.optional(), + queryStringParameters: APIGatewayRecord.optional(), + requestContext: APIGatewayV2RequestContext, + pathParameters: APIGatewayRecord.nullish(), + stageVariables: APIGatewayRecord.nullish(), }); -export { APIGatewayProxyEventV2Schema }; +export { + APIGatewayProxyEventV2Schema, + APIGatewayRequestAuthorizerEventV2Schema, +}; diff --git a/packages/parser/src/schemas/index.ts b/packages/parser/src/schemas/index.ts index ff56350195..e4044d8e9d 100644 --- a/packages/parser/src/schemas/index.ts +++ b/packages/parser/src/schemas/index.ts @@ -1,6 +1,13 @@ export { AlbSchema, AlbMultiValueHeadersSchema } from './alb.js'; -export { APIGatewayProxyEventSchema } from './apigw.js'; -export { APIGatewayProxyEventV2Schema } from './apigwv2.js'; +export { + APIGatewayProxyEventSchema, + APIGatewayRequestAuthorizerEventSchema, + APIGatewayTokenAuthorizerEventSchema, +} from './apigw.js'; +export { + APIGatewayProxyEventV2Schema, + APIGatewayRequestAuthorizerEventV2Schema, +} from './apigwv2.js'; export { CloudFormationCustomResourceCreateSchema, CloudFormationCustomResourceDeleteSchema, diff --git a/packages/parser/tests/events/apigw-http/authorizer-request.json b/packages/parser/tests/events/apigw-http/authorizer-request.json new file mode 100644 index 0000000000..28529efbbb --- /dev/null +++ b/packages/parser/tests/events/apigw-http/authorizer-request.json @@ -0,0 +1,41 @@ +{ + "version": "2.0", + "type": "REQUEST", + "routeArn": "arn:aws:execute-api:eu-west-1:123456789012:lsw1ro4ipb/$default/POST/lambda", + "identitySource": [ + "Bearer foo" + ], + "routeKey": "POST /lambda", + "rawPath": "/lambda", + "rawQueryString": "", + "headers": { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "authorization": "Bearer foo", + "content-length": "0", + "host": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "user-agent": "HTTPie/3.2.2", + "x-amzn-trace-id": "Root=1-66705bc7-2b4257df30cbee696ef2cf28", + "x-forwarded-for": "15.248.3.126", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "lsw1ro4ipb", + "domainName": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "domainPrefix": "lsw1ro4ipb", + "http": { + "method": "POST", + "path": "/lambda", + "protocol": "HTTP/1.1", + "sourceIp": "15.248.3.126", + "userAgent": "HTTPie/3.2.2" + }, + "requestId": "ZhNHJhhLjoEEPiw=", + "routeKey": "POST /lambda", + "stage": "$default", + "time": "17/Jun/2024:15:52:39 +0000", + "timeEpoch": 1718639559080 + } +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-http/iam-auth.json b/packages/parser/tests/events/apigw-http/iam-auth.json new file mode 100644 index 0000000000..b3b5276f26 --- /dev/null +++ b/packages/parser/tests/events/apigw-http/iam-auth.json @@ -0,0 +1,51 @@ +{ + "version": "2.0", + "routeKey": "POST /iam", + "rawPath": "/iam", + "rawQueryString": "", + "headers": { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "authorization": "AWS4-HMAC-SHA256 Credential=ABCAXZWZ5ZDPIFV7JHWH/20240617/eu-west-1/execute-api/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=2ec027cabfd1c83da3a0f7d53f5edfda4bb30469be552966303bdbe7fda5ad6d", + "content-length": "0", + "host": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "user-agent": "HTTPie/3.2.2", + "x-amz-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "x-amz-date": "20240617T153941Z", + "x-amz-security-token": "IQoJb3JpZ2luX2Vj[...]==", + "x-amzn-trace-id": "Root=1-667058bd-3265707259baa0695cdc20c5", + "x-forwarded-for": "15.248.3.126", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "lsw1ro4ipb", + "authorizer": { + "iam": { + "accessKey": "ABCAXZWZ5ZDPIFV7JHWH", + "accountId": "123456789012", + "callerId": "ABCAXZWZ5ZDPNIH6C6ODK:aamorosi", + "cognitoIdentity": null, + "principalOrgId": null, + "userArn": "arn:aws:sts::123456789012:assumed-role/Admin/aamorosi", + "userId": "ABCOAXZWZ5ZDPNIH6C6ODK:aamorosi" + } + }, + "domainName": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "domainPrefix": "lsw1ro4ipb", + "http": { + "method": "POST", + "path": "/iam", + "protocol": "HTTP/1.1", + "sourceIp": "15.248.3.126", + "userAgent": "HTTPie/3.2.2" + }, + "requestId": "ZhLNpgotDoEEJUQ=", + "routeKey": "POST /iam", + "stage": "$default", + "time": "17/Jun/2024:15:39:41 +0000", + "timeEpoch": 1718638781472 + }, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-http/invalid.json b/packages/parser/tests/events/apigw-http/invalid.json new file mode 100644 index 0000000000..37a8fcecf9 --- /dev/null +++ b/packages/parser/tests/events/apigw-http/invalid.json @@ -0,0 +1,22 @@ +{ + "resource": "/", + "path": "/invalid", + "httpMethod": "GET", + "headers": {}, + "multiValueHeaders": {}, + "isBase64Encoded": false, + "body": "Foo!", + "requestContext": { + "accountId": "1234", + "apiId": "myApi", + "httpMethod": "GET", + "identity": { + "sourceIp": "127.0.0.1" + }, + "path": "/", + "protocol": "Https", + "requestId": "1234", + "requestTime": "2018-09-07T16: 20: 46Z", + "requestTimeEpoch": 1536992496000 + } +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-http/jwt-authorizer-auth.json b/packages/parser/tests/events/apigw-http/jwt-authorizer-auth.json new file mode 100644 index 0000000000..73ba0c0f34 --- /dev/null +++ b/packages/parser/tests/events/apigw-http/jwt-authorizer-auth.json @@ -0,0 +1,55 @@ +{ + "version": "2.0", + "routeKey": "POST /cognito", + "rawPath": "/cognito", + "rawQueryString": "", + "headers": { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "authorization": "Bearer eyJra[...]A", + "content-length": "0", + "host": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "user-agent": "HTTPie/3.2.2", + "x-amzn-trace-id": "Root=1-66705b09-5f85233d2fb7bd54034f7e30", + "x-forwarded-for": "15.248.3.126", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "lsw1ro4ipb", + "authorizer": { + "jwt": { + "claims": { + "aud": "3nkoep4151b7umeggnottc30u9", + "auth_time": "1718636404", + "cognito:username": "aamorosi", + "event_id": "c7f6cc62-c476-4c6a-ba5b-745244dfe5ee", + "exp": "1718722804", + "iat": "1718636404", + "iss": "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_y0bLG4i27", + "jti": "13727aba-89e6-4896-bd9e-8cbc0f478f9c", + "origin_jti": "37cc02cf-5c14-4636-beb3-4f8ca887935b", + "sub": "d2f50444-0041-7036-aea2-a955fca33d37", + "token_use": "id" + }, + "scopes": null + } + }, + "domainName": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "domainPrefix": "lsw1ro4ipb", + "http": { + "method": "POST", + "path": "/cognito", + "protocol": "HTTP/1.1", + "sourceIp": "15.248.3.126", + "userAgent": "HTTPie/3.2.2" + }, + "requestId": "ZhMpgigvDoEEPlg=", + "routeKey": "POST /cognito", + "stage": "$default", + "time": "17/Jun/2024:15:49:29 +0000", + "timeEpoch": 1718639369348 + }, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-http/lambda-authorizer-auth.json b/packages/parser/tests/events/apigw-http/lambda-authorizer-auth.json new file mode 100644 index 0000000000..0b6441284e --- /dev/null +++ b/packages/parser/tests/events/apigw-http/lambda-authorizer-auth.json @@ -0,0 +1,40 @@ +{ + "version": "2.0", + "routeKey": "POST /lambda", + "rawPath": "/lambda", + "rawQueryString": "", + "headers": { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "authorization": "Bearer foo", + "content-length": "0", + "host": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "user-agent": "HTTPie/3.2.2", + "x-amzn-trace-id": "Root=1-66705bc7-2b4257df30cbee696ef2cf28", + "x-forwarded-for": "15.248.3.126", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "lsw1ro4ipb", + "authorizer": { + "lambda": null + }, + "domainName": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "domainPrefix": "lsw1ro4ipb", + "http": { + "method": "POST", + "path": "/lambda", + "protocol": "HTTP/1.1", + "sourceIp": "15.248.3.126", + "userAgent": "HTTPie/3.2.2" + }, + "requestId": "ZhNHJhhLjoEEPiw=", + "routeKey": "POST /lambda", + "stage": "$default", + "time": "17/Jun/2024:15:52:39 +0000", + "timeEpoch": 1718639559080 + }, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-http/no-auth.json b/packages/parser/tests/events/apigw-http/no-auth.json new file mode 100644 index 0000000000..0b6441284e --- /dev/null +++ b/packages/parser/tests/events/apigw-http/no-auth.json @@ -0,0 +1,40 @@ +{ + "version": "2.0", + "routeKey": "POST /lambda", + "rawPath": "/lambda", + "rawQueryString": "", + "headers": { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "authorization": "Bearer foo", + "content-length": "0", + "host": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "user-agent": "HTTPie/3.2.2", + "x-amzn-trace-id": "Root=1-66705bc7-2b4257df30cbee696ef2cf28", + "x-forwarded-for": "15.248.3.126", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "lsw1ro4ipb", + "authorizer": { + "lambda": null + }, + "domainName": "lsw1ro4ipb.execute-api.eu-west-1.amazonaws.com", + "domainPrefix": "lsw1ro4ipb", + "http": { + "method": "POST", + "path": "/lambda", + "protocol": "HTTP/1.1", + "sourceIp": "15.248.3.126", + "userAgent": "HTTPie/3.2.2" + }, + "requestId": "ZhNHJhhLjoEEPiw=", + "routeKey": "POST /lambda", + "stage": "$default", + "time": "17/Jun/2024:15:52:39 +0000", + "timeEpoch": 1718639559080 + }, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/authorizer-request.json b/packages/parser/tests/events/apigw-rest/authorizer-request.json new file mode 100644 index 0000000000..31d3d46966 --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/authorizer-request.json @@ -0,0 +1,122 @@ +{ + "type": "REQUEST", + "methodArn": "arn:aws:execute-api:eu-west-1:123456789012:puhdx84jy9/prod/POST/lambda-request", + "resource": "/lambda-request", + "path": "/lambda-request", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "Authorization": "Bearer foo", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-ASN": "7224", + "CloudFront-Viewer-Country": "GB", + "Content-Length": "0", + "Host": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "User-Agent": "HTTPie/3.2.2", + "Via": "1.1 e20527248be1eebaced63108ab7e73d6.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "sIEfafSQxwenKloeWOp-4eyo_-grMyVNzwnkBmx5q7OcvBJV7knerQ==", + "X-Amzn-Trace-Id": "Root=1-66704feb-118151122b2ad2d3488844e7", + "X-Forwarded-For": "15.248.3.126, 130.176.209.37", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Bearer foo" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-ASN": [ + "7224" + ], + "CloudFront-Viewer-Country": [ + "GB" + ], + "Content-Length": [ + "0" + ], + "Host": [ + "puhdx84jy9.execute-api.eu-west-1.amazonaws.com" + ], + "User-Agent": [ + "HTTPie/3.2.2" + ], + "Via": [ + "1.1 e20527248be1eebaced63108ab7e73d6.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "sIEfafSQxwenKloeWOp-4eyo_-grMyVNzwnkBmx5q7OcvBJV7knerQ==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-66704feb-118151122b2ad2d3488844e7" + ], + "X-Forwarded-For": [ + "15.248.3.126, 130.176.209.37" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": {}, + "multiValueQueryStringParameters": {}, + "pathParameters": {}, + "stageVariables": {}, + "requestContext": { + "resourceId": "w567jh", + "resourcePath": "/lambda-request", + "httpMethod": "POST", + "extendedRequestId": "ZhFsxGQDDoEEMig=", + "requestTime": "17/Jun/2024:15:02:03 +0000", + "path": "/prod/lambda-request", + "accountId": "123456789012", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "puhdx84jy9", + "requestTimeEpoch": 1718636523035, + "requestId": "141d6671-99b4-40ca-9997-7a22f01eac74", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "15.248.3.126", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "HTTPie/3.2.2", + "user": null + }, + "domainName": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "deploymentId": "v99qix", + "apiId": "puhdx84jy9" + } +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/authorizer-token.json b/packages/parser/tests/events/apigw-rest/authorizer-token.json new file mode 100644 index 0000000000..b4fa5a844d --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/authorizer-token.json @@ -0,0 +1,5 @@ +{ + "type": "TOKEN", + "methodArn": "arn:aws:execute-api:eu-west-1:123456789012:puhdx84jy9/prod/POST/lambda-token", + "authorizationToken": "Bearer foo" +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/console-test-ui.json b/packages/parser/tests/events/apigw-rest/console-test-ui.json new file mode 100644 index 0000000000..10ab457088 --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/console-test-ui.json @@ -0,0 +1,45 @@ +{ + "resource": "/no-auth", + "path": "/no-auth", + "httpMethod": "POST", + "headers": null, + "multiValueHeaders": null, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": null, + "stageVariables": null, + "requestContext": { + "resourceId": "3nq1cq", + "resourcePath": "/no-auth", + "httpMethod": "POST", + "extendedRequestId": "ZhGnYGtqjoEFyzw=", + "requestTime": "17/Jun/2024:15:08:18 +0000", + "path": "/no-auth", + "accountId": "123456789012", + "protocol": "HTTP/1.1", + "stage": "test-invoke-stage", + "domainPrefix": "testPrefix", + "requestTimeEpoch": 1718636898112, + "requestId": "28422e1a-c975-42da-895e-165f882dafef", + "identity": { + "cognitoIdentityPoolId": null, + "cognitoIdentityId": null, + "apiKey": "test-invoke-api-key", + "principalOrgId": null, + "cognitoAuthenticationType": null, + "userArn": "arn:aws:sts::123456789012:assumed-role/Admin/aamorosi", + "apiKeyId": "test-invoke-api-key-id", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15", + "accountId": "123456789012", + "caller": "ABCAXZWZ5ZDPNIH6C6ODK:aamorosi", + "sourceIp": "test-invoke-source-ip", + "accessKey": "ABCAXZWZ5ZDPFDRIWYXX", + "cognitoAuthenticationProvider": null, + "user": "ABCAXZWZ5ZDPNIH6C6ODK:aamorosi" + }, + "domainName": "testPrefix.testDomainName", + "apiId": "puhdx84jy9" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/iam-auth.json b/packages/parser/tests/events/apigw-rest/iam-auth.json new file mode 100644 index 0000000000..180ed4e10a --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/iam-auth.json @@ -0,0 +1,126 @@ +{ + "resource": "/iam", + "path": "/iam", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-ASN": "7224", + "CloudFront-Viewer-Country": "GB", + "Host": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "User-Agent": "HTTPie/3.2.2", + "Via": "1.1 d3e65123eab254da0d61a912409e06b4.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "tP3wYavwGbIRVlinJVTXtHLSXkXwPi6WYz7EarjB1QnsqC0giphxmA==", + "x-amz-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "x-amz-date": "20240617T145824Z", + "X-Amz-Security-Token": "IQoJb3JpZ[...]==", + "X-Amzn-Trace-Id": "Root=1-66704f11-56b0f1d611b392cb68ad2b83", + "X-Forwarded-For": "15.248.3.126, 130.176.209.38", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-ASN": [ + "7224" + ], + "CloudFront-Viewer-Country": [ + "GB" + ], + "Host": [ + "puhdx84jy9.execute-api.eu-west-1.amazonaws.com" + ], + "User-Agent": [ + "HTTPie/3.2.2" + ], + "Via": [ + "1.1 d3e65123eab254da0d61a912409e06b4.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "tP3wYavwGbIRVlinJVTXtHLSXkXwPi6WYz7EarjB1QnsqC0giphxmA==" + ], + "x-amz-content-sha256": [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + ], + "x-amz-date": [ + "20240617T145824Z" + ], + "X-Amz-Security-Token": [ + "IQoJb3JpZ[...]==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-66704f11-56b0f1d611b392cb68ad2b83" + ], + "X-Forwarded-For": [ + "15.248.3.126, 130.176.209.38" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": null, + "stageVariables": null, + "requestContext": { + "resourceId": "f6ylys", + "resourcePath": "/iam", + "httpMethod": "POST", + "extendedRequestId": "ZhFKtGnnDoEEMig=", + "requestTime": "17/Jun/2024:14:58:25 +0000", + "path": "/prod/iam", + "accountId": "123456789012", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "puhdx84jy9", + "requestTimeEpoch": 1718636305078, + "requestId": "89f9261a-2765-4d49-b72e-f76726991e44", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": "123456789012", + "cognitoIdentityId": null, + "caller": "ABCAXZWZ5ZDPNIH6C6ODK:aamorosi", + "sourceIp": "15.248.3.126", + "principalOrgId": null, + "accessKey": "ABCAXZWZ5ZDPIFV7JHWH", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": "arn:aws:sts::123456789012:assumed-role/Admin/aamorosi", + "userAgent": "HTTPie/3.2.2", + "user": "ABCAXZWZ5ZDPNIH6C6ODK:aamorosi" + }, + "domainName": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "deploymentId": "v99qix", + "apiId": "puhdx84jy9" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/invalid.json b/packages/parser/tests/events/apigw-rest/invalid.json new file mode 100644 index 0000000000..9c2e70fe75 --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/invalid.json @@ -0,0 +1,22 @@ +{ + "resource": "/", + "path": "/", + "httpMethod": "GET", + "headers": {}, + "multiValueHeaders": {}, + "isBase64Encoded": false, + "body": "Foo!", + "requestContext": { + "accountId": "1234", + "apiId": "myApi", + "httpMethod": "GET", + "identity": { + "sourceIp": "127.0.0.1" + }, + "path": "/", + "protocol": "Https", + "requestId": "1234", + "requestTime": "2018-09-07T16: 20: 46Z", + "requestTimeEpoch": 1536992496000 + } +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/jwt-authorizer-auth.json b/packages/parser/tests/events/apigw-rest/jwt-authorizer-auth.json new file mode 100644 index 0000000000..a4ec248d17 --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/jwt-authorizer-auth.json @@ -0,0 +1,133 @@ +{ + "resource": "/cognito", + "path": "/cognito", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "Authorization": "Bearer eyJraWQiOiJadVlaXC9KdmtST2hpN1hBT3FIekx0TVwvdUpKXC9kWUtPMUs1QkVZSjVUM2xZPSIsImFsZyI6IlJTMjU2In0.eyJvcmlnaW5fanRpIjoiMzdjYzAyY2YtNWMxNC00NjM2LWJlYjMtNGY4Y2E4ODc5MzViIiwic3ViIjoiZDJmNTA0NDQtMDA0MS03MDM2LWFlYTItYTk1NWZjYTMzZDM3IiwiYXVkIjoiM25rb2VwNDE1MWI3dW1lZ2dub3R0YzMwdTkiLCJldmVudF9pZCI6ImM3ZjZjYzYyLWM0NzYtNGM2YS1iYTViLTc0NTI0NGRmZTVlZSIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNzE4NjM2NDA0LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuZXUtd2VzdC0xLmFtYXpvbmF3cy5jb21cL2V1LXdlc3QtMV95MGJMRzRpMjciLCJjb2duaXRvOnVzZXJuYW1lIjoiYWFtb3Jvc2kiLCJleHAiOjE3MTg3MjI4MDQsImlhdCI6MTcxODYzNjQwNCwianRpIjoiMTM3MjdhYmEtODllNi00ODk2LWJkOWUtOGNiYzBmNDc4ZjljIn0.k9GKFY028MbL8hyLpsq2aCsMYt0P_im93qSyDF5awnhePlnfpNVXGXGGIcoBPyj-wlsZPdJAS_3Y324VhYlRzX76xTlew1lAtM665RAe4tncLl6MFlicTgHqsFXeOyXroSAe_X9wlP1jfMGiqW6oOkxrl_dajz7GJFFTEX8ztnTLD7FZvE5KophYihxq6RFZlOQFLWmx6w378ZQ49kHg-H5FF6JluI1G9sQ3P77rrhfL7MGv5xi1YiDeWa7T27PhHcgqll9fuLFA-_8LrdN-G5XMEt0Zlfb-WxF8YaStJ4e519GfnVWUuNWJo9BnFj0ChCiyhlK7cAWDjm1mz-Y4FA", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-ASN": "7224", + "CloudFront-Viewer-Country": "GB", + "Host": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "User-Agent": "HTTPie/3.2.2", + "Via": "1.1 ff7cafeac35b91a7af23c56e3b9691e8.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "zTKRjCprnRv8GXztLX3l3gQVmT0MLysk_-FcFmxxBfz0qSPCBQsGIg==", + "X-Amzn-Trace-Id": "Root=1-66704f8a-626bb89a0b2bf33b547f581c", + "X-Forwarded-For": "15.248.3.126, 130.176.209.4", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Bearer eyJraWQiOiJadVlaXC9KdmtST2hpN1hBT3FIekx0TVwvdUpKXC9kWUtPMUs1QkVZSjVUM2xZPSIsImFsZyI6IlJTMjU2In0.eyJvcmlnaW5fanRpIjoiMzdjYzAyY2YtNWMxNC00NjM2LWJlYjMtNGY4Y2E4ODc5MzViIiwic3ViIjoiZDJmNTA0NDQtMDA0MS03MDM2LWFlYTItYTk1NWZjYTMzZDM3IiwiYXVkIjoiM25rb2VwNDE1MWI3dW1lZ2dub3R0YzMwdTkiLCJldmVudF9pZCI6ImM3ZjZjYzYyLWM0NzYtNGM2YS1iYTViLTc0NTI0NGRmZTVlZSIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNzE4NjM2NDA0LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuZXUtd2VzdC0xLmFtYXpvbmF3cy5jb21cL2V1LXdlc3QtMV95MGJMRzRpMjciLCJjb2duaXRvOnVzZXJuYW1lIjoiYWFtb3Jvc2kiLCJleHAiOjE3MTg3MjI4MDQsImlhdCI6MTcxODYzNjQwNCwianRpIjoiMTM3MjdhYmEtODllNi00ODk2LWJkOWUtOGNiYzBmNDc4ZjljIn0.k9GKFY028MbL8hyLpsq2aCsMYt0P_im93qSyDF5awnhePlnfpNVXGXGGIcoBPyj-wlsZPdJAS_3Y324VhYlRzX76xTlew1lAtM665RAe4tncLl6MFlicTgHqsFXeOyXroSAe_X9wlP1jfMGiqW6oOkxrl_dajz7GJFFTEX8ztnTLD7FZvE5KophYihxq6RFZlOQFLWmx6w378ZQ49kHg-H5FF6JluI1G9sQ3P77rrhfL7MGv5xi1YiDeWa7T27PhHcgqll9fuLFA-_8LrdN-G5XMEt0Zlfb-WxF8YaStJ4e519GfnVWUuNWJo9BnFj0ChCiyhlK7cAWDjm1mz-Y4FA" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-ASN": [ + "7224" + ], + "CloudFront-Viewer-Country": [ + "GB" + ], + "Host": [ + "puhdx84jy9.execute-api.eu-west-1.amazonaws.com" + ], + "User-Agent": [ + "HTTPie/3.2.2" + ], + "Via": [ + "1.1 ff7cafeac35b91a7af23c56e3b9691e8.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "zTKRjCprnRv8GXztLX3l3gQVmT0MLysk_-FcFmxxBfz0qSPCBQsGIg==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-66704f8a-626bb89a0b2bf33b547f581c" + ], + "X-Forwarded-For": [ + "15.248.3.126, 130.176.209.4" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": null, + "stageVariables": null, + "requestContext": { + "resourceId": "5x3n2k", + "authorizer": { + "claims": { + "origin_jti": "37cc02cf-5c14-4636-beb3-4f8ca887935b", + "sub": "d2f50444-0041-7036-aea2-a955fca33d37", + "aud": "3nkoep4151b7umeggnottc30u9", + "event_id": "c7f6cc62-c476-4c6a-ba5b-745244dfe5ee", + "token_use": "id", + "auth_time": "1718636404", + "iss": "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_y0bLG4i27", + "cognito:username": "aamorosi", + "exp": "Tue Jun 18 15:00:04 UTC 2024", + "iat": "Mon Jun 17 15:00:04 UTC 2024", + "jti": "13727aba-89e6-4896-bd9e-8cbc0f478f9c" + } + }, + "resourcePath": "/cognito", + "httpMethod": "POST", + "extendedRequestId": "ZhFduHZrDoEEORQ=", + "requestTime": "17/Jun/2024:15:00:26 +0000", + "path": "/prod/cognito", + "accountId": "123456789012", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "puhdx84jy9", + "requestTimeEpoch": 1718636426760, + "requestId": "e56434a3-0b5d-453c-9183-f07a5e91fbb3", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "15.248.3.126", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "HTTPie/3.2.2", + "user": null + }, + "domainName": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "deploymentId": "v99qix", + "apiId": "puhdx84jy9" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/lambda-authorizer-auth.json b/packages/parser/tests/events/apigw-rest/lambda-authorizer-auth.json new file mode 100644 index 0000000000..51465ba840 --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/lambda-authorizer-auth.json @@ -0,0 +1,122 @@ +{ + "resource": "/lambda-token", + "path": "/lambda-token", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "Authorization": "Bearer foo", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-ASN": "7224", + "CloudFront-Viewer-Country": "GB", + "Host": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "User-Agent": "HTTPie/3.2.2", + "Via": "1.1 8313bbb5b34d1ea0742b64ffbb83b692.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "lrbUQoVv2blphI3jBSXQNqA3oq4s8VCCimuaSxMrX4YbEZjVKWaLWg==", + "X-Amzn-Trace-Id": "Root=1-66704db9-6136092d02f4e3424fe2ce95", + "X-Forwarded-For": "15.248.3.126, 130.176.209.10", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Bearer foo" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-ASN": [ + "7224" + ], + "CloudFront-Viewer-Country": [ + "GB" + ], + "Host": [ + "puhdx84jy9.execute-api.eu-west-1.amazonaws.com" + ], + "User-Agent": [ + "HTTPie/3.2.2" + ], + "Via": [ + "1.1 8313bbb5b34d1ea0742b64ffbb83b692.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "lrbUQoVv2blphI3jBSXQNqA3oq4s8VCCimuaSxMrX4YbEZjVKWaLWg==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-66704db9-6136092d02f4e3424fe2ce95" + ], + "X-Forwarded-For": [ + "15.248.3.126, 130.176.209.10" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": null, + "stageVariables": null, + "requestContext": { + "resourceId": "nmnny7", + "authorizer": { + "principalId": "user", + "integrationLatency": 422 + }, + "resourcePath": "/lambda-token", + "httpMethod": "POST", + "extendedRequestId": "ZhEVGHTzDoEEB2A=", + "requestTime": "17/Jun/2024:14:52:41 +0000", + "path": "/prod/lambda-token", + "accountId": "123456789012", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "puhdx84jy9", + "requestTimeEpoch": 1718635961959, + "requestId": "3c177581-2d03-4af4-9b68-5924083f37fe", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "15.248.3.126", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "HTTPie/3.2.2", + "user": null + }, + "domainName": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "deploymentId": "v99qix", + "apiId": "puhdx84jy9" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/no-auth.json b/packages/parser/tests/events/apigw-rest/no-auth.json new file mode 100644 index 0000000000..5168cfbf2f --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/no-auth.json @@ -0,0 +1,114 @@ +{ + "resource": "/no-auth", + "path": "/no-auth", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-ASN": "7224", + "CloudFront-Viewer-Country": "GB", + "Host": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "User-Agent": "HTTPie/3.2.2", + "Via": "1.1 49c0c4776e390b983c9f9f5365e3140c.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "x9VncJ8IMo4X5UQEL8-Xg94vWwwuokofx236RgW1CrFCTh9DnPrgJA==", + "X-Amzn-Trace-Id": "Root=1-66704cad-0fbaf1491365339d168b2a7d", + "X-Forwarded-For": "15.248.3.126, 130.176.209.21", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-ASN": [ + "7224" + ], + "CloudFront-Viewer-Country": [ + "GB" + ], + "Host": [ + "puhdx84jy9.execute-api.eu-west-1.amazonaws.com" + ], + "User-Agent": [ + "HTTPie/3.2.2" + ], + "Via": [ + "1.1 49c0c4776e390b983c9f9f5365e3140c.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "x9VncJ8IMo4X5UQEL8-Xg94vWwwuokofx236RgW1CrFCTh9DnPrgJA==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-66704cad-0fbaf1491365339d168b2a7d" + ], + "X-Forwarded-For": [ + "15.248.3.126, 130.176.209.21" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": null, + "stageVariables": null, + "requestContext": { + "resourceId": "3nq1cq", + "resourcePath": "/no-auth", + "httpMethod": "POST", + "extendedRequestId": "ZhDrKF5dDoEEHfA=", + "requestTime": "17/Jun/2024:14:48:13 +0000", + "path": "/prod/no-auth", + "accountId": "123456789012", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "puhdx84jy9", + "requestTimeEpoch": 1718635693506, + "requestId": "f88e38e3-2060-4a56-93c0-9efbc6a659a0", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "15.248.3.126", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "HTTPie/3.2.2", + "user": null + }, + "domainName": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "deploymentId": "v99qix", + "apiId": "puhdx84jy9" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/events/apigw-rest/websocket.json b/packages/parser/tests/events/apigw-rest/websocket.json new file mode 100644 index 0000000000..df32840f31 --- /dev/null +++ b/packages/parser/tests/events/apigw-rest/websocket.json @@ -0,0 +1,118 @@ +{ + "resource": "/websocket", + "path": "/websocket", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-ASN": "7224", + "CloudFront-Viewer-Country": "GB", + "Host": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "User-Agent": "HTTPie/3.2.2", + "Via": "1.1 49c0c4776e390b983c9f9f5365e3140c.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "x9VncJ8IMo4X5UQEL8-Xg94vWwwuokofx236RgW1CrFCTh9DnPrgJA==", + "X-Amzn-Trace-Id": "Root=1-66704cad-0fbaf1491365339d168b2a7d", + "X-Forwarded-For": "15.248.3.126, 130.176.209.21", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-ASN": [ + "7224" + ], + "CloudFront-Viewer-Country": [ + "GB" + ], + "Host": [ + "puhdx84jy9.execute-api.eu-west-1.amazonaws.com" + ], + "User-Agent": [ + "HTTPie/3.2.2" + ], + "Via": [ + "1.1 49c0c4776e390b983c9f9f5365e3140c.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "x9VncJ8IMo4X5UQEL8-Xg94vWwwuokofx236RgW1CrFCTh9DnPrgJA==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-66704cad-0fbaf1491365339d168b2a7d" + ], + "X-Forwarded-For": [ + "15.248.3.126, 130.176.209.21" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": null, + "stageVariables": null, + "requestContext": { + "resourceId": "3nq1cq", + "resourcePath": "/no-auth", + "httpMethod": "POST", + "extendedRequestId": "ZhDrKF5dDoEEHfA=", + "requestTime": "17/Jun/2024:14:48:13 +0000", + "path": "/prod/no-auth", + "accountId": "123456789012", + "protocol": "HTTP/1.1", + "stage": "prod", + "domainPrefix": "puhdx84jy9", + "requestTimeEpoch": 1718635693506, + "requestId": "f88e38e3-2060-4a56-93c0-9efbc6a659a0", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "15.248.3.126", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "HTTPie/3.2.2", + "user": null + }, + "domainName": "puhdx84jy9.execute-api.eu-west-1.amazonaws.com", + "deploymentId": "v99qix", + "apiId": "puhdx84jy9", + "connectionId": "GgCJcFJGoAMCJ8w=", + "routeKey": "$connect", + "eventType": "MESSAGE", + "messageId": "f88e38e3-2060-4a56-93c0-9efbc6a659a0" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/packages/parser/tests/tsconfig.json b/packages/parser/tests/tsconfig.json index dc4ebdfc5f..5654b3e15f 100644 --- a/packages/parser/tests/tsconfig.json +++ b/packages/parser/tests/tsconfig.json @@ -6,6 +6,6 @@ }, "include": [ "../src/**/*", - "./**/*.*" + "./**/*", ] } \ No newline at end of file diff --git a/packages/parser/tests/unit/envelopes/apigw.test.ts b/packages/parser/tests/unit/envelopes/apigw.test.ts new file mode 100644 index 0000000000..e4c0a6cb78 --- /dev/null +++ b/packages/parser/tests/unit/envelopes/apigw.test.ts @@ -0,0 +1,119 @@ +/** + * Test built-in API Gateway REST envelope + * + * @group unit/parser/envelopes/apigw + */ +import { TestSchema, getTestEvent } from '../schema/utils.js'; +import { APIGatewayProxyEvent } from '../../../src/types/schema.js'; +import { ApiGatewayEnvelope } from '../../../src/envelopes/index.js'; +import { ParseError } from '../../../src/errors.js'; + +describe('API Gateway REST Envelope', () => { + const eventsPath = 'apigw-rest'; + const eventPrototype = getTestEvent({ + eventsPath, + filename: 'no-auth', + }); + + describe('Method: parse', () => { + it('should throw if the payload does not match the schema', () => { + // Prepare + const event = { ...eventPrototype }; + event.body = JSON.stringify({ name: 'foo' }); + + // Act & Assess + expect(() => ApiGatewayEnvelope.parse(event, TestSchema)).toThrow( + ParseError + ); + }); + + it('should throw if the body is null', () => { + // Prepare + const event = { ...eventPrototype }; + event.body = null; + + // Act & Assess + expect(() => ApiGatewayEnvelope.parse(event, TestSchema)).toThrow( + ParseError + ); + }); + + it('should parse and return the inner schema in an envelope', () => { + // Prepare + const event = { ...eventPrototype }; + const payload = { name: 'foo', age: 42 }; + event.body = JSON.stringify(payload); + + // Act + const parsedEvent = ApiGatewayEnvelope.parse(event, TestSchema); + + // Assess + expect(parsedEvent).toEqual(payload); + }); + }); + + describe('Method: safeParse', () => { + it('should not throw if the payload does not match the schema', () => { + // Prepare + const event = { ...eventPrototype }; + event.body = JSON.stringify({ name: 'foo' }); + + // Act + const parseResult = ApiGatewayEnvelope.safeParse(event, TestSchema); + + // Assess + expect(parseResult).toEqual({ + success: false, + error: expect.any(ParseError), + originalEvent: event, + }); + }); + + it('should not throw if the body is null', () => { + // Prepare + const event = { ...eventPrototype }; + event.body = null; + + // Act + const parseResult = ApiGatewayEnvelope.safeParse(event, TestSchema); + + // Assess + expect(parseResult).toEqual({ + success: false, + error: expect.any(ParseError), + originalEvent: event, + }); + }); + + it('should not throw if the event is invalid', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'invalid' }); + + // Act + const parseResult = ApiGatewayEnvelope.safeParse(event, TestSchema); + + // Assess + expect(parseResult).toEqual({ + success: false, + error: expect.any(ParseError), + originalEvent: event, + }); + }); + + it('should parse and return the inner schema in an envelope', () => { + // Prepare + const event = { ...eventPrototype }; + const payload = { name: 'foo', age: 42 }; + event.body = JSON.stringify(payload); + + // Act + const parsedEvent = ApiGatewayEnvelope.safeParse(event, TestSchema); + + // Assess + expect(parsedEvent).toEqual({ + success: true, + data: payload, + }); + }); + }); +}); diff --git a/packages/parser/tests/unit/envelopes/apigwt.test.ts b/packages/parser/tests/unit/envelopes/apigwt.test.ts deleted file mode 100644 index 1055e15f43..0000000000 --- a/packages/parser/tests/unit/envelopes/apigwt.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Test built in schema envelopes for api gateway - * - * @group unit/parser/envelopes - */ - -import { generateMock } from '@anatine/zod-mock'; -import { TestEvents, TestSchema } from '../schema/utils.js'; -import { APIGatewayProxyEvent } from '../../../src/types/'; -import { ApiGatewayEnvelope } from '../../../src/envelopes/index.js'; -import { ParseError } from '../../../src/errors.js'; - -describe('ApigwEnvelope ', () => { - describe('parse', () => { - it('should parse custom schema in envelope', () => { - const testCustomSchemaObject = generateMock(TestSchema); - const testEvent = TestEvents.apiGatewayProxyEvent as APIGatewayProxyEvent; - - testEvent.body = JSON.stringify(testCustomSchemaObject); - - const resp = ApiGatewayEnvelope.parse(testEvent, TestSchema); - expect(resp).toEqual(testCustomSchemaObject); - }); - - it('should throw no body provided', () => { - const testEvent = TestEvents.apiGatewayProxyEvent as APIGatewayProxyEvent; - testEvent.body = null; - - expect(() => ApiGatewayEnvelope.parse(testEvent, TestSchema)).toThrow( - ParseError - ); - }); - it('should throw invalid event provided', () => { - const testEvent = TestEvents.apiGatewayProxyEvent as APIGatewayProxyEvent; - testEvent.body = 'invalid'; - - expect(() => ApiGatewayEnvelope.parse(testEvent, TestSchema)).toThrow( - ParseError - ); - }); - }); - - describe('safeParse', () => { - it('should parse custom schema in envelope', () => { - const testCustomSchemaObject = generateMock(TestSchema); - const testEvent = TestEvents.apiGatewayProxyEvent as APIGatewayProxyEvent; - - testEvent.body = JSON.stringify(testCustomSchemaObject); - - const resp = ApiGatewayEnvelope.safeParse(testEvent, TestSchema); - expect(resp).toEqual({ - success: true, - data: testCustomSchemaObject, - }); - }); - - it('should return success false with original body if no body provided', () => { - const testEvent = TestEvents.apiGatewayProxyEvent as APIGatewayProxyEvent; - testEvent.body = null; - - const resp = ApiGatewayEnvelope.safeParse(testEvent, TestSchema); - expect(resp).toEqual({ - success: false, - error: expect.any(Error), - originalEvent: testEvent, - }); - }); - - it('should return success false with original body if invalid body provided', () => { - const testEvent = TestEvents.apiGatewayProxyEvent as APIGatewayProxyEvent; - testEvent.body = 'invalid'; - - const resp = ApiGatewayEnvelope.safeParse(testEvent, TestSchema); - expect(resp).toEqual({ - success: false, - error: expect.any(ParseError), - originalEvent: testEvent, - }); - }); - it('should return success false if event is invalid', () => { - const resp = ApiGatewayEnvelope.safeParse( - 'invalid' as unknown, - TestSchema - ); - expect(resp).toEqual({ - success: false, - error: expect.any(ParseError), - originalEvent: 'invalid', - }); - }); - }); -}); diff --git a/packages/parser/tests/unit/envelopes/apigwv2.test.ts b/packages/parser/tests/unit/envelopes/apigwv2.test.ts index dd5a82609b..d7bdefe92a 100644 --- a/packages/parser/tests/unit/envelopes/apigwv2.test.ts +++ b/packages/parser/tests/unit/envelopes/apigwv2.test.ts @@ -1,93 +1,118 @@ /** - * Test built in schema envelopes for api gateway v2 + * Test built-in API Gateway HTTP API (v2) envelope * - * @group unit/parser/envelopes + * @group unit/parser/envelopes/apigwv2 */ - -import { TestEvents, TestSchema } from '../schema/utils.js'; -import { generateMock } from '@anatine/zod-mock'; -import { APIGatewayProxyEventV2 } from 'aws-lambda'; +import { TestSchema, getTestEvent } from '../schema/utils.js'; +import { APIGatewayProxyEventV2 } from '../../../src/types/schema.js'; import { ApiGatewayV2Envelope } from '../../../src/envelopes/index.js'; +import { ParseError } from '../../../src/errors.js'; -describe('ApiGwV2Envelope ', () => { - describe('parse', () => { - it('should parse custom schema in envelope', () => { - const testEvent = - TestEvents.apiGatewayProxyV2Event as APIGatewayProxyEventV2; - const data = generateMock(TestSchema); +describe('API Gateway HTTP Envelope', () => { + const eventsPath = 'apigw-http'; + const eventPrototype = getTestEvent({ + eventsPath, + filename: 'no-auth', + }); - testEvent.body = JSON.stringify(data); + describe('Method: parse', () => { + it('should throw if the payload does not match the schema', () => { + // Prepare + const event = { ...eventPrototype }; + event.body = JSON.stringify({ name: 'foo' }); - expect(ApiGatewayV2Envelope.parse(testEvent, TestSchema)).toEqual(data); + // Act & Assess + expect(() => ApiGatewayV2Envelope.parse(event, TestSchema)).toThrow( + ParseError + ); }); - it('should throw when no body provided', () => { - const testEvent = - TestEvents.apiGatewayProxyV2Event as APIGatewayProxyEventV2; - testEvent.body = undefined; + it('should throw if the body is undefined', () => { + // Prepare + const event = { ...eventPrototype }; + event.body = undefined; - expect(() => ApiGatewayV2Envelope.parse(testEvent, TestSchema)).toThrow(); + // Act & Assess + expect(() => ApiGatewayV2Envelope.parse(event, TestSchema)).toThrow( + ParseError + ); }); - it('should throw when invalid body provided', () => { - const testEvent = - TestEvents.apiGatewayProxyV2Event as APIGatewayProxyEventV2; - testEvent.body = 'invalid'; + it('should parse and return the inner schema in an envelope', () => { + // Prepare + const event = { ...eventPrototype }; + const payload = { name: 'foo', age: 42 }; + event.body = JSON.stringify(payload); - expect(() => ApiGatewayV2Envelope.parse(testEvent, TestSchema)).toThrow(); - }); - it('should throw when invalid event provided', () => { - expect(() => - ApiGatewayV2Envelope.parse({ foo: 'bar' }, TestSchema) - ).toThrow(); + // Act + const parsedEvent = ApiGatewayV2Envelope.parse(event, TestSchema); + + // Assess + expect(parsedEvent).toEqual(payload); }); }); - describe('safeParse', () => { - it('should parse custom schema in envelope', () => { - const testEvent = - TestEvents.apiGatewayProxyV2Event as APIGatewayProxyEventV2; - const data = generateMock(TestSchema); + describe('Method: safeParse', () => { + it('should not throw if the payload does not match the schema', () => { + // Prepare + const event = { ...eventPrototype }; + event.body = JSON.stringify({ name: 'foo' }); - testEvent.body = JSON.stringify(data); + // Act + const parseResult = ApiGatewayV2Envelope.safeParse(event, TestSchema); - expect(ApiGatewayV2Envelope.safeParse(testEvent, TestSchema)).toEqual({ - success: true, - data, + // Assess + expect(parseResult).toEqual({ + success: false, + error: expect.any(ParseError), + originalEvent: event, }); }); - it('should return success false with original body if no body provided', () => { - const testEvent = - TestEvents.apiGatewayProxyV2Event as APIGatewayProxyEventV2; - testEvent.body = undefined; + it('should not throw if the body is undefined', () => { + // Prepare + const event = { ...eventPrototype }; + event.body = undefined; - expect(ApiGatewayV2Envelope.safeParse(testEvent, TestSchema)).toEqual({ + // Act + const parseResult = ApiGatewayV2Envelope.safeParse(event, TestSchema); + + // Assess + expect(parseResult).toEqual({ success: false, - error: expect.any(Error), - originalEvent: testEvent, + error: expect.any(ParseError), + originalEvent: event, }); }); - it('should return success false with original body if invalid body provided', () => { - const testEvent = - TestEvents.apiGatewayProxyV2Event as APIGatewayProxyEventV2; - testEvent.body = 'invalid'; + it('should not throw if the event is invalid', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'invalid' }); + + // Act + const parseResult = ApiGatewayV2Envelope.safeParse(event, TestSchema); - expect(ApiGatewayV2Envelope.safeParse(testEvent, TestSchema)).toEqual({ + // Assess + expect(parseResult).toEqual({ success: false, - error: expect.any(Error), - originalEvent: testEvent, + error: expect.any(ParseError), + originalEvent: event, }); }); - it('should return success false with original event if invalid event provided', () => { - expect( - ApiGatewayV2Envelope.safeParse({ foo: 'bar' }, TestSchema) - ).toEqual({ - success: false, - error: expect.any(Error), - originalEvent: { foo: 'bar' }, + it('should parse and return the inner schema in an envelope', () => { + // Prepare + const event = { ...eventPrototype }; + const payload = { name: 'foo', age: 42 }; + event.body = JSON.stringify(payload); + + // Act + const parsedEvent = ApiGatewayV2Envelope.safeParse(event, TestSchema); + + // Assess + expect(parsedEvent).toEqual({ + success: true, + data: payload, }); }); }); diff --git a/packages/parser/tests/unit/schema/apigw.test.ts b/packages/parser/tests/unit/schema/apigw.test.ts index 802169f313..64a3ea965d 100644 --- a/packages/parser/tests/unit/schema/apigw.test.ts +++ b/packages/parser/tests/unit/schema/apigw.test.ts @@ -1,106 +1,153 @@ /** - * Test built in schema + * Test built-in API Gateway REST schemas * - * @group unit/parser/schema/ + * @group unit/parser/schema/apigw */ +import { + APIGatewayProxyEventSchema, + APIGatewayRequestAuthorizerEventSchema, + APIGatewayTokenAuthorizerEventSchema, +} from '../../../src/schemas/index.js'; +import { getTestEvent } from './utils.js'; -import { APIGatewayProxyEventSchema } from '../../../src/schemas/'; -import { TestEvents } from './utils.js'; +describe('API Gateway REST Schemas', () => { + const eventsPath = 'apigw-rest'; -describe('APIGateway ', () => { - it('should parse api gateway event', () => { - const apiGatewayProxyEvent = TestEvents.apiGatewayProxyEvent; + describe('APIGatewayProxyEventSchema', () => { + it('should throw when the event is invalid', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'invalid' }); - expect(APIGatewayProxyEventSchema.parse(apiGatewayProxyEvent)).toEqual( - apiGatewayProxyEvent - ); - }); - it('should parse api gateway authorizer request event', () => { - const apiGatewayAuthorizerRequestEvent = - TestEvents.apiGatewayAuthorizerRequestEvent; + // Act & Assess + expect(() => APIGatewayProxyEventSchema.parse(event)).toThrow(); + }); - expect( - APIGatewayProxyEventSchema.parse(apiGatewayAuthorizerRequestEvent) - ).toEqual(apiGatewayAuthorizerRequestEvent); - }); - it('should parse schema middleware invalid event', () => { - const apiGatewaySchemaMiddlewareInvalidEvent = - TestEvents.apiGatewaySchemaMiddlewareInvalidEvent; + it('should parse an event with no authorizer', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'no-auth' }); - expect( - APIGatewayProxyEventSchema.parse(apiGatewaySchemaMiddlewareInvalidEvent) - ).toEqual(apiGatewaySchemaMiddlewareInvalidEvent); - }); - it('should parse schema middleware valid event', () => { - const apiGatewaySchemaMiddlewareValidEvent = - TestEvents.apiGatewaySchemaMiddlewareValidEvent; + // Act + const parsedEvent = APIGatewayProxyEventSchema.parse(event); - expect( - APIGatewayProxyEventSchema.parse(apiGatewaySchemaMiddlewareValidEvent) - ).toEqual(apiGatewaySchemaMiddlewareValidEvent); - }); - it('should parse proxy event with no version auth', () => { - const apiGatewayProxyEvent_noVersionAuth = - TestEvents.apiGatewayProxyEvent_noVersionAuth; + // Assess + expect(parsedEvent).toEqual(event); + }); - expect( - APIGatewayProxyEventSchema.parse(apiGatewayProxyEvent_noVersionAuth) - ).toEqual(apiGatewayProxyEvent_noVersionAuth); - }); - it('should parse proxy event with another path', () => { - const apiGatewayProxyEventAnotherPath = - TestEvents.apiGatewayProxyEventAnotherPath; + it('should parse an event with a lambda authorizer', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'lambda-authorizer-auth', + }); - expect( - APIGatewayProxyEventSchema.parse(apiGatewayProxyEventAnotherPath) - ).toEqual(apiGatewayProxyEventAnotherPath); - }); - it('should parse proxy event with path trailing slash', () => { - const apiGatewayProxyEventPathTrailingSlash = - TestEvents.apiGatewayProxyEventPathTrailingSlash; - expect( - APIGatewayProxyEventSchema.parse(apiGatewayProxyEventPathTrailingSlash) - ).toEqual(apiGatewayProxyEventPathTrailingSlash); - }); - it('should parse other proxy event', () => { - const apiGatewayProxyOtherEvent = TestEvents.apiGatewayProxyOtherEvent; - expect(APIGatewayProxyEventSchema.parse(apiGatewayProxyOtherEvent)).toEqual( - apiGatewayProxyOtherEvent - ); + // Act + const parsedEvent = APIGatewayProxyEventSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + + it('should parse an event with a JWT authorizer', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'jwt-authorizer-auth', + }); + + // Act + const parsedEvent = APIGatewayProxyEventSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + + it('should parse an event with an IAM authorizer', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'iam-auth', + }); + + // Act + const parsedEvent = APIGatewayProxyEventSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + + it('should parse an event sent by the AWS console test UI', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'console-test-ui', + }); + + // Act + const parsedEvent = APIGatewayProxyEventSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + + it('should parse an event sent as a part of a websocket API', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'websocket', + }); + + // Act + const parsedEvent = APIGatewayProxyEventSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); }); - it('should not throw when event has sourceIp as test-invoke-source-ip', () => { - const apiGatewayProxyEventTestUi = TestEvents.apiGatewayProxyEventTestUI; - expect(() => - APIGatewayProxyEventSchema.parse(apiGatewayProxyEventTestUi) - ).not.toThrow(); + + describe('APIGatewayRequestAuthorizerEventSchema', () => { + it('should throw when the event is invalid', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'invalid' }); + + // Act & Assess + expect(() => + APIGatewayRequestAuthorizerEventSchema.parse(event) + ).toThrow(); + }); + + it('should parse the authorizer event', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'authorizer-request', + }); + + // Act + const parsedEvent = APIGatewayRequestAuthorizerEventSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); }); - it('should throw error when event is not a valid proxy event', () => { - const event = { - resource: '/', - path: '/', - httpMethod: 'GET', - headers: {}, - multiValueHeaders: {}, - isBase64Encoded: false, - body: 'Foo!', - requestContext: { - accountId: '1234', - apiId: 'myApi', - httpMethod: 'GET', - identity: { - sourceIp: '127.0.0.1', - }, - path: '/', - protocol: 'Https', - requestId: '1234', - requestTime: '2018-09-07T16:20:46Z', - requestTimeEpoch: 1536992496000, - resourcePath: '/', - stage: 'test', - eventType: 'DISCONNECT', - messageId: 'messageId', - }, - }; - expect(() => APIGatewayProxyEventSchema.parse(event)).toThrow(); + + describe('APIGatewayTokenAuthorizerEventSchema', () => { + it('should throw when the event is invalid', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'invalid' }); + + // Act & Assess + expect(() => APIGatewayTokenAuthorizerEventSchema.parse(event)).toThrow(); + }); + + it('should parse the event', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'authorizer-token' }); + + // Act + const parsedEvent = APIGatewayTokenAuthorizerEventSchema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); }); }); diff --git a/packages/parser/tests/unit/schema/apigwv2.test.ts b/packages/parser/tests/unit/schema/apigwv2.test.ts index 1ebb547c7f..15716ab30f 100644 --- a/packages/parser/tests/unit/schema/apigwv2.test.ts +++ b/packages/parser/tests/unit/schema/apigwv2.test.ts @@ -1,67 +1,103 @@ /** - * Test built in schema + * Test built-in API Gateway HTTP API (v2) schemas * - * @group unit/parser/schema/ + * @group unit/parser/schema/apigwv2 */ +import { + APIGatewayProxyEventV2Schema, + APIGatewayRequestAuthorizerEventV2Schema, +} from '../../../src/schemas/index.js'; +import { getTestEvent } from './utils.js'; -import { APIGatewayProxyEventV2Schema } from '../../../src/schemas/'; -import { TestEvents } from './utils.js'; +describe('API Gateway HTTP (v2) Schemas', () => { + const eventsPath = 'apigw-http'; -describe('API GW v2 ', () => { - it('should parse api gateway v2 event', () => { - const apiGatewayProxyV2Event = TestEvents.apiGatewayProxyV2Event; + describe('APIGatewayProxyEventV2Schema', () => { + it('should throw when the event is invalid', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'invalid' }); - expect(APIGatewayProxyEventV2Schema.parse(apiGatewayProxyV2Event)).toEqual( - apiGatewayProxyV2Event - ); - }); - it('should parse api gateway v2 event with GET method', () => { - const apiGatewayProxyV2Event_GET = TestEvents.apiGatewayProxyV2Event_GET; - expect( - APIGatewayProxyEventV2Schema.parse(apiGatewayProxyV2Event_GET) - ).toEqual(apiGatewayProxyV2Event_GET); - }); - it('should parse api gateway v2 event with path trailing slash', () => { - const apiGatewayProxyV2EventPathTrailingSlash = - TestEvents.apiGatewayProxyV2EventPathTrailingSlash; - - expect( - APIGatewayProxyEventV2Schema.parse( - apiGatewayProxyV2EventPathTrailingSlash - ) - ).toEqual(apiGatewayProxyV2EventPathTrailingSlash); - }); - it('should parse api gateway v2 event with iam', () => { - const apiGatewayProxyV2IamEvent = TestEvents.apiGatewayProxyV2IamEvent; + // Act & Assess + expect(() => APIGatewayProxyEventV2Schema.parse(event)).toThrow(); + }); - expect( - APIGatewayProxyEventV2Schema.parse(apiGatewayProxyV2IamEvent) - ).toEqual(apiGatewayProxyV2IamEvent); - }); - it('should parse api gateway v2 event with lambda authorizer', () => { - const apiGatewayProxyV2LambdaAuthorizerEvent = - TestEvents.apiGatewayProxyV2LambdaAuthorizerEvent; + it('should parse an event with no authorizer', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'no-auth' }); - expect( - APIGatewayProxyEventV2Schema.parse(apiGatewayProxyV2LambdaAuthorizerEvent) - ).toEqual(apiGatewayProxyV2LambdaAuthorizerEvent); - }); - it('should parse api gateway v2 event with other get event', () => { - const apiGatewayProxyV2OtherGetEvent = - TestEvents.apiGatewayProxyV2OtherGetEvent; + // Act + const parsedEvent = APIGatewayProxyEventV2Schema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + + it('should parse an event with a lambda authorizer', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'lambda-authorizer-auth', + }); + + // Act + const parsedEvent = APIGatewayProxyEventV2Schema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + + it('should parse an event with a JWT authorizer', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'jwt-authorizer-auth', + }); - expect( - APIGatewayProxyEventV2Schema.parse(apiGatewayProxyV2OtherGetEvent) - ).toEqual(apiGatewayProxyV2OtherGetEvent); + // Act + const parsedEvent = APIGatewayProxyEventV2Schema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); + + it('should parse an event with an IAM authorizer', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'iam-auth', + }); + + // Act + const parsedEvent = APIGatewayProxyEventV2Schema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); }); - it('should parse api gateway v2 event with schema middleware', () => { - const apiGatewayProxyV2SchemaMiddlewareValidEvent = - TestEvents.apiGatewayProxyV2SchemaMiddlewareValidEvent; - - expect( - APIGatewayProxyEventV2Schema.parse( - apiGatewayProxyV2SchemaMiddlewareValidEvent - ) - ).toEqual(apiGatewayProxyV2SchemaMiddlewareValidEvent); + + describe('APIGatewayRequestAuthorizerEventV2Schema', () => { + it('should throw when the event is invalid', () => { + // Prepare + const event = getTestEvent({ eventsPath, filename: 'invalid' }); + + // Act & Assess + expect(() => + APIGatewayRequestAuthorizerEventV2Schema.parse(event) + ).toThrow(); + }); + + it('should parse the authorizer event', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'authorizer-request', + }); + + // Act + const parsedEvent = APIGatewayRequestAuthorizerEventV2Schema.parse(event); + + // Assess + expect(parsedEvent).toEqual(event); + }); }); }); diff --git a/packages/parser/tests/unit/schema/utils.ts b/packages/parser/tests/unit/schema/utils.ts index 03ab950348..3e1ad00133 100644 --- a/packages/parser/tests/unit/schema/utils.ts +++ b/packages/parser/tests/unit/schema/utils.ts @@ -1,4 +1,5 @@ import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; import { z } from 'zod'; export const TestSchema = z.object({ @@ -115,3 +116,17 @@ const createTestEvents = (fileList: readonly string[]): TestEvents => { }; export const TestEvents = createTestEvents(filenames); + +export const getTestEvent = >({ + eventsPath, + filename, +}: { + eventsPath: string; + filename: string; +}): T => + JSON.parse( + readFileSync( + join(__dirname, '..', '..', 'events', eventsPath, `${filename}.json`), + 'utf-8' + ) + ) as T;