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 1 commit
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
20 changes: 20 additions & 0 deletions packages/@aws-cdk/integ-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,26 @@ integ.assertions.awsApiCall('SQS', 'receiveMessage', {
});
```


Additionally, you can test non environment-agnostic stacks by setting the environment for the integration tests:

```ts
declare const app: App;
const env = { region: 'us-west-2' }
const stack = new Stack(app, 'nlb-test', { env: env } );

const nlb = new elbv2.NetworkLoadBalancer(stack, 'nlb', { vpc: new ec2.Vpc(stack, 'vpc') })
// requires region to be set
nlb.logAccessLogs(new s3.Bucket(stack, 'logbucket'))
const integ = new IntegTest(app, 'Integ', {
testCases: [stack],
env: env,
});
integ.assertions.awsApiCall('Elbv2', 'describeLoadBalancers', {
Names: [nlb.loadBalancerName]
});
```

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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Stack } from '@aws-cdk/core';
import { Environment, 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 {

/**
* The environment for the assertions stack
*
* @default - environment-agnostic
*/
readonly env?: Environment
}

/**
* 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 = new Stack(scope, 'DeployAssert', { env: props?.env });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be better to actually allow the user to pass their own Stack. I think that is
what I originally intended to do. That also opens up the posibility to put the assertions in the
same Stack as your test stack.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that a lot more! Let me refactor this PR

I also think it's beneficial in the case the user wanting to keep the stacks separated. It allows for creating lambda functions for the Lambda assertions independent from what is being integration tested


Object.defineProperty(this, DEPLOY_ASSERT_SYMBOL, { value: true });
}
Expand Down
23 changes: 19 additions & 4 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, Environment, 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 an environment for the assertions stack
*
* @default - environment-agnostic
*/
readonly env?: Environment
}

/**
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, { env: props.env });
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 @@ -125,6 +132,13 @@ export interface IntegTestProps extends TestOptions {
* @default false
*/
readonly enableLookups?: boolean;

/**
* Set an AWS environment (account/region) for the assertions stack.
*
* @default - environment-agnostic
*/
readonly env?: Environment
}

/**
Expand All @@ -150,6 +164,7 @@ export class IntegTest extends Construct {
allowDestroy: props.allowDestroy,
cdkCommandOptions: props.cdkCommandOptions,
stackUpdateWorkflow: props.stackUpdateWorkflow,
env: props.env,
});
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,42 @@ describe('DeployAssert', () => {
});
});
});

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

// WHEN
expect(() => {
// THEN
app.synth();
}).toThrow(/only supported for stacks deployed to the same environment/);
});

test('not throw when environment matches', () => {
//GIVEN
const app = new App();
const env = { region: 'us-west-2' };
const stack = new Stack(app, 'TestStack', { env: env });
const cr = new CustomResource(stack, 'cr', { serviceToken: 'foo' });
const integ = new IntegTest(app, 'integ', {
testCases: [stack],
env: env,
});
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