
Testing the configuration of an AWS API Gateway in a CDK deployment
Unit testing IaC? Really...
I have recently been messing around with the free tier of GitHub Copilot, and my chosen playground has been AWS Cloudformation.
I asked copilot to generate some code to create a Cognito User Pool, and then an API Gateway with protected and unprotected endpoints. And this went really well:
// Create an API Gateway
const api = new apigateway.RestApi(this, 'CodemunkiesApi', {
restApiName: 'Codemunkies Service',
description: 'This service serves Codemunkies site.',
});
// Add a public GET method
const getIntegration = new apigateway.MockIntegration({
integrationResponses: [{
statusCode: '200',
responseTemplates: {
'application/json': JSON.stringify({ message: 'Hello, world!' }),
},
}],
passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
requestTemplates: {
'application/json': '{"statusCode": 200}',
},
});
// Add a public GET method to /unprotected
const unprotectedResource = api.root.addResource('unprotected');
unprotectedResource.addMethod('GET', getIntegration, {
methodResponses: [{ statusCode: '200' }],
});
// Add a protected GET method to /protected
const protectedResource = api.root.addResource('protected');
const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, 'CognitoAuthorizer', {
cognitoUserPools: [userPool],
});
protectedResource.addMethod('GET', getIntegration, {
authorizer,
authorizationType: apigateway.AuthorizationType.COGNITO,
methodResponses: [{ statusCode: '200' }],
});
When you setup a CDK project you also get given a sample test, to go with the sample infrastructure. So I asked copilot to create tests as well. And this is where the copilot train slightly undershot the station. This is very similar to the test generated by copilot:
test('API Gateway Created with /unprotected GET Method', () => {
const app = new cdk.App();
// WHEN
const stack = new CodemunkiesSite.CodemunkiesSiteStack(app, 'MyTestStack');
// THEN
const template = Template.fromStack(stack);
template.hasResourceProperties('AWS::ApiGateway::RestApi', {
Name: 'Codemunkies Service',
});
template.hasResourceProperties('AWS::ApiGateway::Method', {
HttpMethod: 'GET',
ResourceId: {
Ref: 'CodemunkiesApiunprotectedResourceId'
},
});
});
If run this test will fail, and after a bit of headscratching I worked out that the error was related to the second condition:
template.hasResourceProperties('AWS::ApiGateway::Method', {
HttpMethod: 'GET',
ResourceId: {
Ref: 'CodemunkiesApiunprotectedResourceId'
},
});
The problem is that resources and methods added to an API gateway don’t have a name set by the code, instead a name is generated using some of the input parameters. This means that you need to be able to either get the reference at the time test is run or do something different. Eventually I found an article describing some of the matchers available for using tests, and was able to make the following code:
test('API Gateway Created with /unprotected GET Method', () => {
const app = new cdk.App();
// WHEN
const stack = new CodemunkiesSite.CodemunkiesSiteStack(app, 'MyTestStack');
// THEN
const template = Template.fromStack(stack);
template.hasResourceProperties('AWS::ApiGateway::RestApi', {
Name: 'Codemunkies Service',
});
template.hasResourceProperties('AWS::ApiGateway::Resource', {
PathPart: 'unprotected'
});
template.hasResourceProperties('AWS::ApiGateway::Method', {
HttpMethod: 'GET',
AuthorizationType: 'NONE',
ResourceId: {
Ref: Match.stringLikeRegexp('CodemunkiesApiunprotected*')
},
});
});