Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 129 additions & 2 deletions lib/src/apigateway/openapi-gateway-lambda.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import {
import z from 'zod';
import type { oas30, oas31 } from 'openapi3-ts';
import { Converter } from '@apiture/openapi-down-convert';
import { EndpointType } from 'aws-cdk-lib/aws-apigateway';
import {
AccessLogFormat,
EndpointType,
LogGroupLogDestination,
MethodLoggingLevel,
} from 'aws-cdk-lib/aws-apigateway';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
import { App, Stack } from 'aws-cdk-lib/core';
import { App, Duration, Stack } from 'aws-cdk-lib/core';
import { Template } from 'aws-cdk-lib/assertions';
import { Construct } from 'constructs';
import { CfnFunction } from 'aws-cdk-lib/aws-lambda';
Expand All @@ -27,6 +32,7 @@ import {
generateOpenapiDocWithExtensions,
generateOperationId,
getPropsWithDefaults,
addLogGroupForTracing,
} from './openapi-gateway-lambda';

extendZodWithOpenApi(z);
Expand Down Expand Up @@ -475,6 +481,127 @@ describe('openapi-gateway-lambda', () => {
Principal: 'apigateway.amazonaws.com',
});
});

it('should correctly construct deployOptionsAccessLog without provided deploy options', () => {
const stack = new Stack();
const props: OpenApiGatewayLambdaProps = {
stage: 'dev',
openapiBasic: {
openapi: '3.0.3',
info: {
title: 'test api',
version: 'v1',
},
},
openapiOperations: [],
accessLogEnable: true,
};

const { deployOptions, logGroupAccessLog } = addLogGroupForTracing(stack, props);

if (!logGroupAccessLog) {
throw new Error('logGroupAccessLog should be defined');
}
expect(deployOptions.accessLogDestination).toStrictEqual(
new LogGroupLogDestination(logGroupAccessLog),
);
expect(deployOptions.accessLogFormat).toStrictEqual(AccessLogFormat.jsonWithStandardFields());
expect(deployOptions.loggingLevel).toBe(MethodLoggingLevel.INFO);
expect(deployOptions.dataTraceEnabled).toBe(false);
expect(deployOptions.tracingEnabled).toBe(true);
expect(deployOptions.metricsEnabled).toBe(true);
expect(logGroupAccessLog).toStrictEqual(logGroupAccessLog);
});

it('should correctly construct deployOptionsAccessLog with provided deployOptions', () => {
const stack = new Stack();
const props: OpenApiGatewayLambdaProps = {
stage: 'dev',
openapiBasic: {
openapi: '3.0.3',
info: {
title: 'test api',
version: 'v1',
},
},
openapiOperations: [],
accessLogEnable: true,
deployOptions: {
loggingLevel: MethodLoggingLevel.ERROR,
dataTraceEnabled: true,
stageName: 'dev',
cachingEnabled: true,
methodOptions: {
'/messages/GET': {
cachingEnabled: true,
cacheTtl: Duration.seconds(3600),
},
},
},
};

const { deployOptions, logGroupAccessLog } = addLogGroupForTracing(stack, props);

if (!logGroupAccessLog) {
throw new Error('logGroupAccessLog should be defined');
}
expect(deployOptions.accessLogDestination).toStrictEqual(
new LogGroupLogDestination(logGroupAccessLog),
);
expect(deployOptions.accessLogFormat).toStrictEqual(AccessLogFormat.jsonWithStandardFields());
expect(deployOptions.loggingLevel).toBe(MethodLoggingLevel.ERROR);
expect(deployOptions.dataTraceEnabled).toBe(true);
expect(deployOptions.tracingEnabled).toBe(true);
expect(deployOptions.metricsEnabled).toBe(true);
expect(deployOptions.stageName).toBe('dev');
expect(deployOptions.cachingEnabled).toBe(true);
expect(deployOptions.methodOptions).toStrictEqual({
'/messages/GET': {
cachingEnabled: true,
cacheTtl: Duration.seconds(3600),
},
});
expect(logGroupAccessLog).toStrictEqual(logGroupAccessLog);
});

it('should correctly merge lambdaOp.integrationOptions into x-amazon-apigateway-integration', () => {
const awsRegion = 'eu-west-1';
const lambdaArn = 'arn:aws:lambda:eu-west-1:123123123:function:my-function:live';
const gatewayProps = {
...testGatewayProps(),
openapiOperations: [
{
...testGatewayProps().openapiOperations[0],
integrationOptions: {
cacheKeyParameters: [
'method.request.querystring.portal',
'method.request.querystring.active',
],
requestParameters: {
'integration.request.querystring.portal': 'method.request.querystring.portal',
'integration.request.querystring.active': 'method.request.querystring.active',
},
},
},
],
};
const openapidoc = generateOpenapiDocWithExtensions(gatewayProps, awsRegion);

// @ts-ignore
expect(openapidoc.paths['/users/{id}'].get['x-amazon-apigateway-integration']).toStrictEqual({
uri: `arn:aws:apigateway:${awsRegion}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations`,
httpMethod: 'POST',
type: 'AWS_PROXY',
cacheKeyParameters: [
'method.request.querystring.portal',
'method.request.querystring.active',
],
requestParameters: {
'integration.request.querystring.portal': 'method.request.querystring.portal',
'integration.request.querystring.active': 'method.request.querystring.active',
},
});
});
});

const testUserGetOperation = (scope: Construct, lambdaConfig: BaseNodeJsProps): LambdaOperation => {
Expand Down
19 changes: 9 additions & 10 deletions lib/src/apigateway/openapi-gateway-lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,10 @@ const getRestApiExecutionArn = (scope: Construct, specRestApi: SpecRestApi): str
/**
* Setup log groups for access log
*/
const addLogGroupForTracing = (
export const addLogGroupForTracing = (
scope: Construct,
props: OpenApiGatewayLambdaProps,
): { deployOptions: StageOptions; logGroupAccessLog?: LogGroup } => {
if (props.accessLogEnable && props.deployOptions) {
throw new Error("When 'accessLogEnable' is defined, 'deployOptions' cannot be defined");
}

if (props.accessLogEnable) {
const logGroupAccessLog = new LogGroup(scope, 'AccessLogGroup', {
logGroupName: `apigateway-accesslogs-${scope.node.id}`,
Expand All @@ -182,11 +178,13 @@ const addLogGroupForTracing = (

const deployOptionsAccessLog: StageOptions = {
...props.deployOptions,
accessLogDestination: new LogGroupLogDestination(logGroupAccessLog),
accessLogFormat: AccessLogFormat.jsonWithStandardFields(),
loggingLevel: MethodLoggingLevel.INFO,
dataTraceEnabled: false,
tracingEnabled: true,
accessLogDestination:
props.deployOptions?.accessLogDestination ?? new LogGroupLogDestination(logGroupAccessLog),
accessLogFormat:
props.deployOptions?.accessLogFormat ?? AccessLogFormat.jsonWithStandardFields(),
loggingLevel: props.deployOptions?.loggingLevel ?? MethodLoggingLevel.INFO,
dataTraceEnabled: props.deployOptions?.dataTraceEnabled ?? false,
tracingEnabled: props.deployOptions?.tracingEnabled ?? true,
metricsEnabled: props.deployOptions?.metricsEnabled ?? true,
};

Expand Down Expand Up @@ -307,6 +305,7 @@ export const generateOpenapiDocWithExtensions = (
// https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html
// @ts-ignore
jsonObj['x-amazon-apigateway-integration'] = {
...lambdaOp.integrationOptions,
// We need to use a api gw lambda invocation function to invocate the lambda we are integrating
// https://stackoverflow.com/a/50696321
uri: `arn:aws:apigateway:${awsRegion}:lambda:path/2015-03-31/functions/${lambdaOp.lambdaAlias.functionArn}/invocations`,
Expand Down
3 changes: 2 additions & 1 deletion lib/src/apigateway/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EndpointType, SpecRestApiProps } from 'aws-cdk-lib/aws-apigateway';
import { EndpointType, IntegrationOptions, SpecRestApiProps } from 'aws-cdk-lib/aws-apigateway';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
import { RouteConfig } from '@asteasolutions/zod-to-openapi';
import { IAlias } from 'aws-cdk-lib/aws-lambda';
Expand Down Expand Up @@ -46,4 +46,5 @@ export type LambdaOperation = {
routeConfig: RouteConfig;
lambdaAlias: LambdaAlias;
awsExtensions?: AwsExtensions;
integrationOptions?: IntegrationOptions;
};