Skip to content

Commit

Permalink
fix(integ-tests): DeployAssert should be private (aws#20382)
Browse files Browse the repository at this point in the history
The `DeployAssert` construct is really an implementation detail that is
only used when making assertions as part of integration tests. This PR
makes the construct private and creates a public interface
(`IDeployAssert`).

This PR also:

- Removes the scope swap since we no longer need to pass around
`DeployAssert`.
- Removes some unused code (ResultsCollector).


----

### All Submissions:

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

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/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/master/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
corymhall authored and wphilipw committed May 23, 2022
1 parent 0fd186a commit 68ce21b
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 327 deletions.
76 changes: 42 additions & 34 deletions packages/@aws-cdk/integ-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,12 @@ new IntegTest(app, 'Integ', { testCases: [stackUnderTest, testCaseWithAssets] })

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

The easiest way to do this is to create a `TestCase` and then access the `DeployAssert` that is automatically created.
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.assert`.

```ts
declare const app: App;
Expand All @@ -187,31 +192,35 @@ const integ = new IntegTest(app, 'Integ', { testCases: [stack] });
integ.assert.awsApiCall('S3', 'getObject');
```

### 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`.
- Part of a normal CDK deployment

Any assertions that you create should be created in the scope of `DeployAssert`. For example,
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.

```ts
declare const app: App;
declare const myAppStack: Stack;

const assert = new DeployAssert(app);
new AwsApiCall(assert, 'GetObject', {
new AwsApiCall(myAppStack, 'GetObject', {
service: 'S3',
api: 'getObject',
});
```

### 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.

```ts
declare const myCustomResource: CustomResource;
declare const stack: Stack;
declare const app: App;
const assert = new DeployAssert(app);
assert.assert(

const integ = new IntegTest(app, 'Integ', { testCases: [stack] });
integ.assert.assert(
'CustomAssertion',
ExpectedResult.objectLike({ foo: 'bar' }),
ActualResult.fromCustomResource(myCustomResource, 'data'),
Expand All @@ -228,12 +237,12 @@ AWS API call to receive some data. This library does this by utilizing CloudForm
which means that CloudFormation will call out to a Lambda Function which will
use the AWS JavaScript SDK to make the API call.

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

```ts
declare const assert: DeployAssert;
declare const stack: Stack;

new AwsApiCall(assert, 'MyAssertion', {
new AwsApiCall(stack, 'MyAssertion', {
service: 'SQS',
api: 'receiveMessage',
parameters: {
Expand All @@ -242,12 +251,15 @@ new AwsApiCall(assert, 'MyAssertion', {
});
```

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

```ts
declare const app: App;
const assert = new DeployAssert(app);
assert.awsApiCall('SQS', 'receiveMessage', {
declare const stack: Stack;
const integ = new IntegTest(app, 'Integ', {
testCases: [stack],
});
integ.assert.awsApiCall('SQS', 'receiveMessage', {
QueueUrl: 'url',
});
```
Expand Down Expand Up @@ -281,21 +293,18 @@ const message = integ.assert.awsApiCall('SQS', 'receiveMessage', {
WaitTimeSeconds: 20,
});

new EqualsAssertion(integ.assert, 'ReceiveMessage', {
actual: ActualResult.fromAwsApiCall(message, 'Messages.0.Body'),
expected: ExpectedResult.objectLike({
requestContext: {
condition: 'Success',
},
requestPayload: {
status: 'OK',
},
responseContext: {
statusCode: 200,
},
responsePayload: 'success',
}),
});
message.assertAtPath('Messages.0.Body', ExpectedResult.objectLike({
requestContext: {
condition: 'Success',
},
requestPayload: {
status: 'OK',
},
responseContext: {
statusCode: 200,
},
responsePayload: 'success',
}));
```

#### Match
Expand All @@ -305,7 +314,6 @@ can be used to construct the `ExpectedResult`.

```ts
declare const message: AwsApiCall;
declare const assert: DeployAssert;

message.assert(ExpectedResult.objectLike({
Messages: Match.arrayWith([
Expand Down
5 changes: 3 additions & 2 deletions packages/@aws-cdk/integ-tests/lib/assertions/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CustomResource } from '@aws-cdk/core';
import { AwsApiCall } from './sdk';
import { IAwsApiCall } from './sdk';

/**
* Represents the "actual" results to compare
*/
Expand All @@ -16,7 +17,7 @@ export abstract class ActualResult {
/**
* Get the actual results from a AwsApiCall
*/
public static fromAwsApiCall(query: AwsApiCall, attribute: string): ActualResult {
public static fromAwsApiCall(query: IAwsApiCall, attribute: string): ActualResult {
return {
result: query.getAttString(attribute),
};
Expand Down
128 changes: 0 additions & 128 deletions packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/@aws-cdk/integ-tests/lib/assertions/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from './assertions';
export * from './types';
export * from './sdk';
export * from './deploy-assert';
export * from './assertions';
export * from './providers';
export * from './common';
export * from './match';
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Stack } from '@aws-cdk/core';
import { Construct, IConstruct, Node } from 'constructs';
import { EqualsAssertion } from '../assertions';
import { ExpectedResult, ActualResult } from '../common';
import { md5hash } from '../private/hash';
import { AwsApiCall, LambdaInvokeFunction, IAwsApiCall, LambdaInvokeFunctionProps } from '../sdk';
import { IDeployAssert } from '../types';


const DEPLOY_ASSERT_SYMBOL = Symbol.for('@aws-cdk/integ-tests.DeployAssert');


// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct as CoreConstruct } from '@aws-cdk/core';

/**
* Options for DeployAssert
*/
export interface DeployAssertProps { }

/**
* Construct that allows for registering a list of assertions
* that should be performed on a construct
*/
export class DeployAssert extends CoreConstruct 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;
}

/**
* Finds a DeployAssert construct in the given scope
*/
public static of(construct: IConstruct): DeployAssert {
const scopes = Node.of(Node.of(construct).root).findAll();
const deployAssert = scopes.find(s => DeployAssert.isDeployAssert(s));
if (!deployAssert) {
throw new Error('No DeployAssert construct found in scopes');
}
return deployAssert as DeployAssert;
}

public scope: Stack;

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

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

Object.defineProperty(this, DEPLOY_ASSERT_SYMBOL, { value: true });
}

public awsApiCall(service: string, api: string, parameters?: any): IAwsApiCall {
return new AwsApiCall(this.scope, `AwsApiCall${service}${api}`, {
api,
service,
parameters,
});
}

public invokeFunction(props: LambdaInvokeFunctionProps): IAwsApiCall {
const hash = md5hash(this.scope.resolve(props));
return new LambdaInvokeFunction(this.scope, `LambdaInvoke${hash}`, props);
}

public assert(id: string, expected: ExpectedResult, actual: ActualResult): void {
new EqualsAssertion(this.scope, `EqualsAssertion${id}`, {
expected,
actual,
});
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { AssertionHandler } from './assertion';
import { ResultsCollectionHandler } from './results';
import { AwsApiCallHandler } from './sdk';
import * as types from './types';

Expand All @@ -14,7 +13,6 @@ function createResourceHandler(event: AWSLambda.CloudFormationCustomResourceEven
}
switch (event.ResourceType) {
case types.ASSERT_RESOURCE_TYPE: return new AssertionHandler(event, context);
case types.RESULTS_RESOURCE_TYPE: return new ResultsCollectionHandler(event, context);
default:
throw new Error(`Unsupported resource type "${event.ResourceType}`);
}
Expand Down

This file was deleted.

Loading

0 comments on commit 68ce21b

Please sign in to comment.