All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awscdk.integtests.alpha.package-info Maven / Gradle / Ivy

There is a newer version: 2.169.0-alpha.0
Show newest version
/**
 * 

integ-tests

*

* --- *

* cdk-constructs: Experimental *

*

*

* The APIs of higher level constructs in this module are experimental and under active development. * They are subject to non-backward compatible changes or removal in any future version. These are * not subject to the Semantic Versioning model and breaking changes will be * announced in the release notes. This means that while you may use them, you may need to update * your source code when upgrading to a newer version of this package. *

*

*

*


*

* *

*

Overview

*

* This library is meant to be used in combination with the integ-runner CLI * to enable users to write and execute integration tests for AWS CDK Constructs. *

* An integration test should be defined as a CDK application, and * there should be a 1:1 relationship between an integration test and a CDK application. *

* So for example, in order to create an integration test called my-function * we would need to create a file to contain our integration test application. *

* test/integ.my-function.ts *

*

 * App app = new App();
 * Stack stack = new Stack();
 * Function.Builder.create(stack, "MyFunction")
 *         .runtime(Runtime.NODEJS_LATEST)
 *         .handler("index.handler")
 *         .code(Code.fromAsset(join(__dirname, "lambda-handler")))
 *         .build();
 * 
*

* This is a self contained CDK application which we could deploy by running *

*

 * cdk deploy --app 'node test/integ.my-function.js'
 * 
*

* In order to turn this into an integration test, all that is needed is to * use the IntegTest construct. *

*

 * App app;
 * Stack stack;
 * 
 * IntegTest.Builder.create(app, "Integ").testCases(List.of(stack)).build();
 * 
*

* You will notice that the stack is registered to the IntegTest as a test case. * Each integration test can contain multiple test cases, which are just instances * of a stack. See the Usage section for more details. *

*

Usage

*

*

IntegTest

*

* Suppose you have a simple stack, that only encapsulates a Lambda function with a * certain handler: *

*

 * public class StackUnderTestProps extends StackProps {
 *     private Architecture architecture;
 *     public Architecture getArchitecture() {
 *         return this.architecture;
 *     }
 *     public StackUnderTestProps architecture(Architecture architecture) {
 *         this.architecture = architecture;
 *         return this;
 *     }
 * }
 * 
 * public class StackUnderTest extends Stack {
 *     public StackUnderTest(Construct scope, String id, StackUnderTestProps props) {
 *         super(scope, id, props);
 * 
 *         Function.Builder.create(this, "Handler")
 *                 .runtime(Runtime.NODEJS_LATEST)
 *                 .handler("index.handler")
 *                 .code(Code.fromAsset(join(__dirname, "lambda-handler")))
 *                 .architecture(props.getArchitecture())
 *                 .build();
 *     }
 * }
 * 
*

* You may want to test this stack under different conditions. For example, we want * this stack to be deployed correctly, regardless of the architecture we choose * for the Lambda function. In particular, it should work for both ARM_64 and * X86_64. So you can create an IntegTestCase that exercises both scenarios: *

*

 * public class StackUnderTestProps extends StackProps {
 *     private Architecture architecture;
 *     public Architecture getArchitecture() {
 *         return this.architecture;
 *     }
 *     public StackUnderTestProps architecture(Architecture architecture) {
 *         this.architecture = architecture;
 *         return this;
 *     }
 * }
 * 
 * public class StackUnderTest extends Stack {
 *     public StackUnderTest(Construct scope, String id, StackUnderTestProps props) {
 *         super(scope, id, props);
 * 
 *         Function.Builder.create(this, "Handler")
 *                 .runtime(Runtime.NODEJS_LATEST)
 *                 .handler("index.handler")
 *                 .code(Code.fromAsset(join(__dirname, "lambda-handler")))
 *                 .architecture(props.getArchitecture())
 *                 .build();
 *     }
 * }
 * 
 * // Beginning of the test suite
 * App app = new App();
 * 
 * IntegTest.Builder.create(app, "DifferentArchitectures")
 *         .testCases(List.of(
 *             new StackUnderTest(app, "Stack1", new StackUnderTestProps()
 *                     .architecture(Architecture.ARM_64)
 *                     ),
 *             new StackUnderTest(app, "Stack2", new StackUnderTestProps()
 *                     .architecture(Architecture.X86_64)
 *                     )))
 *         .build();
 * 
*

* This is all the instruction you need for the integration test runner to know * which stacks to synthesize, deploy and destroy. But you may also need to * customize the behavior of the runner by changing its parameters. For example: *

*

 * App app = new App();
 * 
 * Stack stackUnderTest = new Stack(app, "StackUnderTest");
 * 
 * Stack stack = new Stack(app, "stack");
 * 
 * IntegTest testCase = IntegTest.Builder.create(app, "CustomizedDeploymentWorkflow")
 *         .testCases(List.of(stackUnderTest))
 *         .diffAssets(true)
 *         .stackUpdateWorkflow(true)
 *         .cdkCommandOptions(CdkCommands.builder()
 *                 .deploy(DeployCommand.builder()
 *                         .args(DeployOptions.builder()
 *                                 .requireApproval(RequireApproval.NEVER)
 *                                 .json(true)
 *                                 .build())
 *                         .build())
 *                 .destroy(DestroyCommand.builder()
 *                         .args(DestroyOptions.builder()
 *                                 .force(true)
 *                                 .build())
 *                         .build())
 *                 .build())
 *         .build();
 * 
*

*

IntegTestCaseStack

*

* In the majority of cases an integration test will contain a single IntegTestCase. * By default when you create an IntegTest an IntegTestCase is created for you * and all of your test cases are registered to this IntegTestCase. The IntegTestCase * and IntegTestCaseStack constructs are only needed when it is necessary to * defined different options for individual test cases. *

* For example, you might want to have one test case where diffAssets is enabled. *

*

 * App app;
 * Stack stackUnderTest;
 * 
 * IntegTestCaseStack testCaseWithAssets = IntegTestCaseStack.Builder.create(app, "TestCaseAssets")
 *         .diffAssets(true)
 *         .build();
 * 
 * IntegTest.Builder.create(app, "Integ").testCases(List.of(stackUnderTest, testCaseWithAssets)).build();
 * 
*

*

Assertions

*

* This library also provides a utility to make assertions against the infrastructure that the integration test deploys. *

* There are two main scenarios in which assertions are created. *

*

    *
  • Part of an integration test using integ-runner
  • *
*

* In this case you would create an integration test using the IntegTest construct and then make assertions using the assert property. * You should not utilize the assertion constructs directly, but should instead use the methods on IntegTest.assertions. *

*

 * App app;
 * Stack stack;
 * 
 * 
 * IntegTest integ = IntegTest.Builder.create(app, "Integ").testCases(List.of(stack)).build();
 * integ.assertions.awsApiCall("S3", "getObject");
 * 
*

* By default an assertions stack is automatically generated for you. You may however provide your own stack to use. *

*

 * App app;
 * Stack stack;
 * Stack assertionStack;
 * 
 * 
 * IntegTest integ = IntegTest.Builder.create(app, "Integ").testCases(List.of(stack)).assertionStack(assertionStack).build();
 * integ.assertions.awsApiCall("S3", "getObject");
 * 
*

*

    *
  • Part of a normal CDK deployment
  • *
*

* In this case you may be using assertions as part of a normal CDK deployment in order to make an assertion on the infrastructure * before the deployment is considered successful. In this case you can utilize the assertions constructs directly. *

*

 * Stack myAppStack;
 * 
 * 
 * AwsApiCall.Builder.create(myAppStack, "GetObject")
 *         .service("S3")
 *         .api("getObject")
 *         .build();
 * 
*

*

DeployAssert

*

* Assertions are created by using the DeployAssert construct. This construct creates it's own Stack separate from * any stacks that you create as part of your integration tests. This Stack is treated differently from other stacks * by the integ-runner tool. For example, this stack will not be diffed by the integ-runner. *

* DeployAssert also provides utilities to register your own assertions. *

*

 * CustomResource myCustomResource;
 * Stack stack;
 * App app;
 * 
 * 
 * IntegTest integ = IntegTest.Builder.create(app, "Integ").testCases(List.of(stack)).build();
 * integ.assertions.expect("CustomAssertion", ExpectedResult.objectLike(Map.of("foo", "bar")), ActualResult.fromCustomResource(myCustomResource, "data"));
 * 
*

* In the above example an assertion is created that will trigger a user defined CustomResource * and assert that the data attribute is equal to { foo: 'bar' }. *

*

API Calls

*

* A common method to retrieve the "actual" results to compare with what is expected is to make an * API call to receive some data. This library does this by utilizing CloudFormation custom resources * which means that CloudFormation will call out to a Lambda Function which will * make the API call. *

*

HttpApiCall

*

* Using the HttpApiCall will use the * node-fetch JavaScript library to * make the HTTP call. *

* This can be done by using the class directory (in the case of a normal deployment): *

*

 * Stack stack;
 * 
 * 
 * HttpApiCall.Builder.create(stack, "MyAsssertion")
 *         .url("https://example-api.com/abc")
 *         .build();
 * 
*

* Or by using the httpApiCall method on DeployAssert (when writing integration tests): *

*

 * App app;
 * Stack stack;
 * 
 * IntegTest integ = IntegTest.Builder.create(app, "Integ")
 *         .testCases(List.of(stack))
 *         .build();
 * integ.assertions.httpApiCall("https://example-api.com/abc");
 * 
*

*

AwsApiCall

*

* Using the AwsApiCall construct will use the AWS JavaScript SDK to make the API call. *

* This can be done by using the class directory (in the case of a normal deployment): *

*

 * Stack stack;
 * 
 * 
 * AwsApiCall.Builder.create(stack, "MyAssertion")
 *         .service("SQS")
 *         .api("receiveMessage")
 *         .parameters(Map.of(
 *                 "QueueUrl", "url"))
 *         .build();
 * 
*

* Or by using the awsApiCall method on DeployAssert (when writing integration tests): *

*

 * App app;
 * Stack stack;
 * 
 * IntegTest integ = IntegTest.Builder.create(app, "Integ")
 *         .testCases(List.of(stack))
 *         .build();
 * integ.assertions.awsApiCall("SQS", "receiveMessage", Map.of(
 *         "QueueUrl", "url"));
 * 
*

* You must specify the service and the api when using The AwsApiCall construct. * The service is the name of an AWS service, in one of the following forms: *

*

    *
  • An AWS SDK for JavaScript v3 package name (@aws-sdk/client-api-gateway)
  • *
  • An AWS SDK for JavaScript v3 client name (api-gateway)
  • *
  • An AWS SDK for JavaScript v2 constructor name (APIGateway)
  • *
  • A lowercase AWS SDK for JavaScript v2 constructor name (apigateway)
  • *
*

* The api is the name of an AWS API call, in one of the following forms: *

*

    *
  • An API call name as found in the API Reference documentation (GetObject)
  • *
  • The API call name starting with a lowercase letter (getObject)
  • *
  • The AWS SDK for JavaScript v3 command class name (GetObjectCommand)
  • *
*

* By default, the AwsApiCall construct will automatically add the correct IAM policies * to allow the Lambda function to make the API call. It does this based on the service * and api that is provided. In the above example the service is SQS and the api is * receiveMessage so it will create a policy with Action: 'sqs:ReceiveMessage. *

* There are some cases where the permissions do not exactly match the service/api call, for * example the S3 listObjectsV2 api. In these cases it is possible to add the correct policy * by accessing the provider object. *

*

 * App app;
 * Stack stack;
 * IntegTest integ;
 * 
 * 
 * IApiCall apiCall = integ.assertions.awsApiCall("S3", "listObjectsV2", Map.of(
 *         "Bucket", "mybucket"));
 * 
 * apiCall.provider.addToRolePolicy(Map.of(
 *         "Effect", "Allow",
 *         "Action", List.of("s3:GetObject", "s3:ListBucket"),
 *         "Resource", List.of("*")));
 * 
*

* Note that addToRolePolicy() uses direct IAM JSON policy blobs, not a iam.PolicyStatement * object like you will see in the rest of the CDK. *

*

EqualsAssertion

*

* This library currently provides the ability to assert that two values are equal * to one another by utilizing the EqualsAssertion class. This utilizes a Lambda * backed CustomResource which in tern uses the Match utility from the * @aws-cdk/assertions library. *

*

 * App app;
 * Stack stack;
 * Queue queue;
 * IFunction fn;
 * 
 * 
 * IntegTest integ = IntegTest.Builder.create(app, "Integ")
 *         .testCases(List.of(stack))
 *         .build();
 * 
 * integ.assertions.invokeFunction(LambdaInvokeFunctionProps.builder()
 *         .functionName(fn.getFunctionName())
 *         .invocationType(InvocationType.EVENT)
 *         .payload(JSON.stringify(Map.of("status", "OK")))
 *         .build());
 * 
 * IApiCall message = integ.assertions.awsApiCall("SQS", "receiveMessage", Map.of(
 *         "QueueUrl", queue.getQueueUrl(),
 *         "WaitTimeSeconds", 20));
 * 
 * message.assertAtPath("Messages.0.Body", ExpectedResult.objectLike(Map.of(
 *         "requestContext", Map.of(
 *                 "condition", "Success"),
 *         "requestPayload", Map.of(
 *                 "status", "OK"),
 *         "responseContext", Map.of(
 *                 "statusCode", 200),
 *         "responsePayload", "success")));
 * 
*

*

Match

*

* integ-tests also provides a Match utility similar to the @aws-cdk/assertions module. Match * can be used to construct the ExpectedResult. While the utility is similar, only a subset of methods are currently available on the Match utility of this module: arrayWith, objectLike, stringLikeRegexp and serializedJson. *

*

 * AwsApiCall message;
 * 
 * 
 * message.expect(ExpectedResult.objectLike(Map.of(
 *         "Messages", Match.arrayWith(List.of(Map.of(
 *                 "Payload", Match.serializedJson(Map.of("key", "value"))), Map.of(
 *                 "Body", Map.of(
 *                         "Values", Match.arrayWith(List.of(Map.of("Asdf", 3))),
 *                         "Message", Match.stringLikeRegexp("message"))))))));
 * 
*

*

Examples

*

*

Invoke a Lambda Function

*

* In this example there is a Lambda Function that is invoked and * we assert that the payload that is returned is equal to '200'. *

*

 * IFunction lambdaFunction;
 * App app;
 * 
 * 
 * Stack stack = new Stack(app, "cdk-integ-lambda-bundling");
 * 
 * IntegTest integ = IntegTest.Builder.create(app, "IntegTest")
 *         .testCases(List.of(stack))
 *         .build();
 * 
 * IApiCall invoke = integ.assertions.invokeFunction(LambdaInvokeFunctionProps.builder()
 *         .functionName(lambdaFunction.getFunctionName())
 *         .build());
 * invoke.expect(ExpectedResult.objectLike(Map.of(
 *         "Payload", "200")));
 * 
*

* The above example will by default create a CloudWatch log group that's never * expired. If you want to configure it with custom log retention days, you need * to specify the logRetention property. *

*

 * import software.amazon.awscdk.services.logs.*;
 * 
 * IFunction lambdaFunction;
 * App app;
 * 
 * 
 * Stack stack = new Stack(app, "cdk-integ-lambda-bundling");
 * 
 * IntegTest integ = IntegTest.Builder.create(app, "IntegTest")
 *         .testCases(List.of(stack))
 *         .build();
 * 
 * IApiCall invoke = integ.assertions.invokeFunction(LambdaInvokeFunctionProps.builder()
 *         .functionName(lambdaFunction.getFunctionName())
 *         .logRetention(RetentionDays.ONE_WEEK)
 *         .build());
 * 
*

*

Make an AWS API Call

*

* In this example there is a StepFunctions state machine that is executed * and then we assert that the result of the execution is successful. *

*

 * App app;
 * Stack stack;
 * IStateMachine sm;
 * 
 * 
 * IntegTest testCase = IntegTest.Builder.create(app, "IntegTest")
 *         .testCases(List.of(stack))
 *         .build();
 * 
 * // Start an execution
 * IApiCall start = testCase.assertions.awsApiCall("StepFunctions", "startExecution", Map.of(
 *         "stateMachineArn", sm.getStateMachineArn()));
 * 
 * // describe the results of the execution
 * IApiCall describe = testCase.assertions.awsApiCall("StepFunctions", "describeExecution", Map.of(
 *         "executionArn", start.getAttString("executionArn")));
 * 
 * // assert the results
 * describe.expect(ExpectedResult.objectLike(Map.of(
 *         "status", "SUCCEEDED")));
 * 
*

*

Chain ApiCalls

*

* Sometimes it may be necessary to chain API Calls. Since each API call is its own resource, all you * need to do is add a dependency between the calls. There is an helper method next that can be used. *

*

 * IntegTest integ;
 * 
 * 
 * integ.assertions.awsApiCall("S3", "putObject", Map.of(
 *         "Bucket", "my-bucket",
 *         "Key", "my-key",
 *         "Body", "helloWorld")).next(integ.assertions.awsApiCall("S3", "getObject", Map.of(
 *         "Bucket", "my-bucket",
 *         "Key", "my-key")));
 * 
*

*

Wait for results

*

* A common use case when performing assertions is to wait for a condition to pass. Sometimes the thing * that you are asserting against is not done provisioning by the time the assertion runs. In these * cases it is possible to run the assertion asynchronously by calling the waitForAssertions() method. *

* Taking the example above of executing a StepFunctions state machine, depending on the complexity of * the state machine, it might take a while for it to complete. *

*

 * App app;
 * Stack stack;
 * IStateMachine sm;
 * 
 * 
 * IntegTest testCase = IntegTest.Builder.create(app, "IntegTest")
 *         .testCases(List.of(stack))
 *         .build();
 * 
 * // Start an execution
 * IApiCall start = testCase.assertions.awsApiCall("StepFunctions", "startExecution", Map.of(
 *         "stateMachineArn", sm.getStateMachineArn()));
 * 
 * // describe the results of the execution
 * IApiCall describe = testCase.assertions.awsApiCall("StepFunctions", "describeExecution", Map.of(
 *         "executionArn", start.getAttString("executionArn"))).expect(ExpectedResult.objectLike(Map.of(
 *         "status", "SUCCEEDED"))).waitForAssertions();
 * 
*

* When you call waitForAssertions() the assertion provider will continuously make the awsApiCall until the * ExpectedResult is met. You can also control the parameters for waiting, for example: *

*

 * IntegTest testCase;
 * IApiCall start;
 * 
 * 
 * IApiCall describe = testCase.assertions.awsApiCall("StepFunctions", "describeExecution", Map.of(
 *         "executionArn", start.getAttString("executionArn"))).expect(ExpectedResult.objectLike(Map.of(
 *         "status", "SUCCEEDED"))).waitForAssertions(WaiterStateMachineOptions.builder()
 *         .totalTimeout(Duration.minutes(5))
 *         .interval(Duration.seconds(15))
 *         .backoffRate(3)
 *         .build());
 * 
*/ @software.amazon.jsii.Stability(software.amazon.jsii.Stability.Level.Experimental) package software.amazon.awscdk.integtests.alpha;




© 2015 - 2024 Weber Informatics LLC | Privacy Policy