Skip to content

Commit

Permalink
feat(integ-tests): allow for user provided assertions stack (#22404)
Browse files Browse the repository at this point in the history
Closes #22332

----

### All Submissions:

* [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
dontirun authored Oct 10, 2022
1 parent c186e2d commit 39089f5
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 13 deletions.
11 changes: 11 additions & 0 deletions packages/@aws-cdk/integ-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,17 @@ const integ = new IntegTest(app, 'Integ', { testCases: [stack] });
integ.assertions.awsApiCall('S3', 'getObject');
```

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

```ts
declare const app: App;
declare const stack: Stack;
declare const assertionStack: Stack;

const integ = new IntegTest(app, 'Integ', { testCases: [stack], assertionStack: assertionStack });
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Stack } from '@aws-cdk/core';
import { Construct, IConstruct, Node } from 'constructs';
import { IApiCall } from '../api-call-base';
import { EqualsAssertion } from '../assertions';
import { ExpectedResult, ActualResult } from '../common';
import { ActualResult, ExpectedResult } from '../common';
import { md5hash } from '../private/hash';
import { AwsApiCall, LambdaInvokeFunction, LambdaInvokeFunctionProps } from '../sdk';
import { IDeployAssert } from '../types';
Expand All @@ -13,7 +13,15 @@ const DEPLOY_ASSERT_SYMBOL = Symbol.for('@aws-cdk/integ-tests.DeployAssert');
/**
* Options for DeployAssert
*/
export interface DeployAssertProps { }
export interface DeployAssertProps {

/**
* A stack to use for assertions
*
* @default - a stack is created for you
*/
readonly stack?: Stack
}

/**
* Construct that allows for registering a list of assertions
Expand All @@ -25,7 +33,7 @@ export class DeployAssert extends Construct implements IDeployAssert {
* Returns whether the construct is a DeployAssert construct
*/
public static isDeployAssert(x: any): x is DeployAssert {
return x !== null && typeof(x) === 'object' && DEPLOY_ASSERT_SYMBOL in x;
return x !== null && typeof (x) === 'object' && DEPLOY_ASSERT_SYMBOL in x;
}

/**
Expand All @@ -42,10 +50,10 @@ export class DeployAssert extends Construct implements IDeployAssert {

public scope: Stack;

constructor(scope: Construct) {
constructor(scope: Construct, props?: DeployAssertProps) {
super(scope, 'Default');

this.scope = new Stack(scope, 'DeployAssert');
this.scope = props?.stack ?? new Stack(scope, 'DeployAssert');

Object.defineProperty(this, DEPLOY_ASSERT_SYMBOL, { value: true });
}
Expand Down
25 changes: 20 additions & 5 deletions packages/@aws-cdk/integ-tests/lib/test-case.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IntegManifest, Manifest, TestCase, TestOptions } from '@aws-cdk/cloud-assembly-schema';
import { attachCustomSynthesis, Stack, ISynthesisSession, StackProps } from '@aws-cdk/core';
import { attachCustomSynthesis, ISynthesisSession, Stack, StackProps } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { IDeployAssert } from './assertions';
import { DeployAssert } from './assertions/private/deploy-assert';
Expand All @@ -15,6 +15,13 @@ export interface IntegTestCaseProps extends TestOptions {
* Stacks to be deployed during the test
*/
readonly stacks: Stack[];

/**
* Specify a stack to use for assertions
*
* @default - a stack is created for you
*/
readonly assertionStack?: Stack
}

/**
Expand All @@ -35,7 +42,7 @@ export class IntegTestCase extends Construct {
constructor(scope: Construct, id: string, private readonly props: IntegTestCaseProps) {
super(scope, id);

this._assert = new DeployAssert(this);
this._assert = new DeployAssert(this, { stack: props.assertionStack });
this.assertions = this._assert;
}

Expand Down Expand Up @@ -63,7 +70,7 @@ export class IntegTestCase extends Construct {
/**
* Properties of an integration test case stack
*/
export interface IntegTestCaseStackProps extends TestOptions, StackProps {}
export interface IntegTestCaseStackProps extends TestOptions, StackProps { }

/**
* An integration test case stack. Allows the definition of test properties
Expand All @@ -78,7 +85,7 @@ export class IntegTestCaseStack extends Stack {
* Returns whether the construct is a IntegTestCaseStack
*/
public static isIntegTestCaseStack(x: any): x is IntegTestCaseStack {
return x !== null && typeof(x) === 'object' && TEST_CASE_STACK_SYMBOL in x;
return x !== null && typeof (x) === 'object' && TEST_CASE_STACK_SYMBOL in x;
}

/**
Expand Down Expand Up @@ -119,12 +126,19 @@ export interface IntegTestProps extends TestOptions {
/**
* Enable lookups for this test. If lookups are enabled
* then `stackUpdateWorkflow` must be set to false.
* Lookups should only be enabled when you are explicitely testing
* Lookups should only be enabled when you are explicitly testing
* lookups.
*
* @default false
*/
readonly enableLookups?: boolean;

/**
* Specify a stack to use for assertions
*
* @default - a stack is created for you
*/
readonly assertionStack?: Stack
}

/**
Expand All @@ -150,6 +164,7 @@ export class IntegTest extends Construct {
allowDestroy: props.allowDestroy,
cdkCommandOptions: props.cdkCommandOptions,
stackUpdateWorkflow: props.stackUpdateWorkflow,
assertionStack: props.assertionStack,
});
this.assertions = defaultTestCase.assertions;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Template } from '@aws-cdk/assertions';
import { App, Stack } from '@aws-cdk/core';
import { App, CustomResource, Stack } from '@aws-cdk/core';
import { ActualResult, ExpectedResult, InvocationType, LogType } from '../../lib/assertions';
import { DeployAssert } from '../../lib/assertions/private/deploy-assert';
import { IntegTest } from '../../lib/test-case';

describe('DeployAssert', () => {

Expand Down Expand Up @@ -164,3 +165,65 @@ describe('DeployAssert', () => {
});
});
});

describe('User provided assertions stack', () => {
test('Same stack for integration test and assertions', () => {
//GIVEN
const app = new App();
const stack = new Stack(app, 'TestStack');

// WHEN
const cr = new CustomResource(stack, 'cr', { resourceType: 'Custom::Bar', serviceToken: 'foo' });
const integ = new IntegTest(app, 'integ', {
testCases: [stack],
assertionStack: stack,
});
integ.assertions.awsApiCall('Service', 'Api', { Reference: cr.ref });

// THEN
const template = Template.fromStack(stack);
template.resourceCountIs('Custom::DeployAssert@SdkCallServiceApi', 1);
template.resourceCountIs('Custom::Bar', 1);
});

test('Different stack for integration test and assertions', () => {
//GIVEN
const app = new App();
const integStack = new Stack(app, 'TestStack');
const assertionStack = new Stack(app, 'AssertionsStack');
const integ = new IntegTest(app, 'integ', {
testCases: [integStack],
assertionStack: assertionStack,
});

// WHEN
const cr = new CustomResource(integStack, 'cr', { resourceType: 'Custom::Bar', serviceToken: 'foo' });
integ.assertions.awsApiCall('Service', 'Api', { Reference: cr.ref });

// THEN
const integTemplate = Template.fromStack(integStack);
const assertionTemplate = Template.fromStack(assertionStack);
integTemplate.resourceCountIs('Custom::Bar', 1);
assertionTemplate.resourceCountIs('Custom::DeployAssert@SdkCallServiceApi', 1);
});

test('not throw when environment matches', () => {
//GIVEN
const app = new App();
const env = { region: 'us-west-2' };
const integStack = new Stack(app, 'IntegStack', { env: env });
const assertionStack = new Stack(app, 'AssertionsStack', { env: env });
const cr = new CustomResource(integStack, 'cr', { serviceToken: 'foo' });
const integ = new IntegTest(app, 'integ', {
testCases: [integStack],
assertionStack: assertionStack,
});
integ.assertions.awsApiCall('Service', 'api', { Reference: cr.getAttString('bar') });

// WHEN
expect(() => {
// THEN
app.synth();
}).not.toThrow(/only supported for stacks deployed to the same environment/);
});
});
4 changes: 2 additions & 2 deletions packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Template, Match } from '@aws-cdk/assertions';
import { Match, Template } from '@aws-cdk/assertions';
import { App, CfnOutput } from '@aws-cdk/core';
import { LogType, InvocationType, ExpectedResult } from '../../lib/assertions';
import { ExpectedResult, InvocationType, LogType } from '../../lib/assertions';
import { DeployAssert } from '../../lib/assertions/private/deploy-assert';

describe('AwsApiCall', () => {
Expand Down

0 comments on commit 39089f5

Please sign in to comment.