Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(integ-tests): allow for user provided assertions stack #22404

Merged
merged 5 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
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