diff --git a/packages/@aws-cdk/integ-tests/README.md b/packages/@aws-cdk/integ-tests/README.md index e35714a844fd4..08c2e70fa811b 100644 --- a/packages/@aws-cdk/integ-tests/README.md +++ b/packages/@aws-cdk/integ-tests/README.md @@ -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 diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts b/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts index 321082d7f8ce4..3cce887cc27b0 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts @@ -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'; @@ -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 @@ -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; } /** @@ -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 }); } diff --git a/packages/@aws-cdk/integ-tests/lib/test-case.ts b/packages/@aws-cdk/integ-tests/lib/test-case.ts index 1d3cce2026bb5..50aa946ac155c 100644 --- a/packages/@aws-cdk/integ-tests/lib/test-case.ts +++ b/packages/@aws-cdk/integ-tests/lib/test-case.ts @@ -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'; @@ -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 } /** @@ -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; } @@ -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 @@ -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; } /** @@ -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 } /** @@ -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; diff --git a/packages/@aws-cdk/integ-tests/test/assertions/deploy-assert.test.ts b/packages/@aws-cdk/integ-tests/test/assertions/deploy-assert.test.ts index bbc2609822356..df7a5253dba62 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/deploy-assert.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/deploy-assert.test.ts @@ -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', () => { @@ -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/); + }); +}); diff --git a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts index 672888f937892..3d0cbaa57f13b 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts @@ -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', () => {