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

chore: revert "DeployAssert should be private" #20405

Merged
merged 4 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
76 changes: 34 additions & 42 deletions packages/@aws-cdk/integ-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,7 @@ new IntegTest(app, 'Integ', { testCases: [stackUnderTest, testCaseWithAssets] })

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

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

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

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

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

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

new AwsApiCall(myAppStack, 'GetObject', {
const assert = new DeployAssert(app);
new AwsApiCall(assert, '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 integ = new IntegTest(app, 'Integ', { testCases: [stack] });
integ.assert.assert(
const assert = new DeployAssert(app);
assert.assert(
'CustomAssertion',
ExpectedResult.objectLike({ foo: 'bar' }),
ActualResult.fromCustomResource(myCustomResource, 'data'),
Expand All @@ -237,12 +228,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 (in the case of a normal deployment):
This can be done by using the class directory:

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

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

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

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

message.assertAtPath('Messages.0.Body', ExpectedResult.objectLike({
requestContext: {
condition: 'Success',
},
requestPayload: {
status: 'OK',
},
responseContext: {
statusCode: 200,
},
responsePayload: 'success',
}));
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',
}),
});
```

#### Match
Expand All @@ -314,6 +305,7 @@ 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: 2 additions & 3 deletions packages/@aws-cdk/integ-tests/lib/assertions/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { CustomResource } from '@aws-cdk/core';
import { IAwsApiCall } from './sdk';

import { AwsApiCall } from './sdk';
/**
* Represents the "actual" results to compare
*/
Expand All @@ -17,7 +16,7 @@ export abstract class ActualResult {
/**
* Get the actual results from a AwsApiCall
*/
public static fromAwsApiCall(query: IAwsApiCall, attribute: string): ActualResult {
public static fromAwsApiCall(query: AwsApiCall, attribute: string): ActualResult {
return {
result: query.getAttString(attribute),
};
Expand Down
128 changes: 128 additions & 0 deletions packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
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, LambdaInvokeFunctionProps } from './sdk';

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 {

/**
* 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;
}

constructor(scope: Construct) {
/**
* Normally we would not want to do a scope swapparoo like this
* but in this case this it allows us to provide a better experience
* for the user. This allows DeployAssert to be created _not_ in the
* scope of a Stack. DeployAssert is treated like a Stack, but doesn't
* exose any of the stack functionality (the methods that the user sees
* are just DeployAssert methods and not any Stack methods). So you can do
* something like this, which you would not normally be allowed to do
*
* const deployAssert = new DeployAssert(app);
* new AwsApiCall(deployAssert, 'AwsApiCall', {...});
*/
scope = new Stack(scope, 'DeployAssert');
super(scope, 'Default');

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

/**
* Query AWS using JavaScript SDK V2 API calls. This can be used to either
* trigger an action or to return a result that can then be asserted against
* an expected value
*
* @example
* declare const app: App;
* const assert = new DeployAssert(app);
* assert.awsApiCall('SQS', 'sendMessage', {
* QueueUrl: 'url',
* MessageBody: 'hello',
* });
* const message = assert.awsApiCall('SQS', 'receiveMessage', {
* QueueUrl: 'url',
* });
* message.assert(ExpectedResult.objectLike({
* Messages: [{ Body: 'hello' }],
* }));
*/
public awsApiCall(service: string, api: string, parameters?: any): AwsApiCall {
return new AwsApiCall(this, `AwsApiCall${service}${api}`, {
api,
service,
parameters,
});
}

/**
* Invoke a lambda function and return the response which can be asserted
*
* @example
* declare const app: App;
* const assert = new DeployAssert(app);
* const invoke = assert.invokeFunction({
* functionName: 'my-function',
* });
* invoke.assert(ExpectedResult.objectLike({
* Payload: '200',
* }));
*/
public invokeFunction(props: LambdaInvokeFunctionProps): LambdaInvokeFunction {
const hash = md5hash(Stack.of(this).resolve(props));
return new LambdaInvokeFunction(this, `LambdaInvoke${hash}`, props);
}

/**
* Assert that the ExpectedResult is equal
* to the ActualResult
*
* @example
* declare const deployAssert: DeployAssert;
* declare const apiCall: AwsApiCall;
* deployAssert.assert(
* 'invoke',
* ExpectedResult.objectLike({ Payload: 'OK' }),
* ActualResult.fromAwsApiCall(apiCall, 'Body'),
* );
*/
public assert(id: string, expected: ExpectedResult, actual: ActualResult): void {
new EqualsAssertion(this, `EqualsAssertion${id}`, {
expected,
actual,
});
}
}
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 './types';
export * from './sdk';
export * from './assertions';
export * from './sdk';
export * from './deploy-assert';
export * from './providers';
export * from './common';
export * from './match';

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AssertionHandler } from './assertion';
import { ResultsCollectionHandler } from './results';
import { AwsApiCallHandler } from './sdk';
import * as types from './types';

Expand All @@ -13,6 +14,7 @@ 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CustomResourceHandler } from './base';
import { ResultsCollectionRequest, ResultsCollectionResult } from './types';

export class ResultsCollectionHandler extends CustomResourceHandler<ResultsCollectionRequest, ResultsCollectionResult> {
protected async processEvent(request: ResultsCollectionRequest): Promise<ResultsCollectionResult | undefined> {
const reduced: string = request.assertionResults.reduce((agg, result, idx) => {
const msg = result.status === 'pass' ? 'pass' : `fail - ${result.message}`;
return `${agg}\nTest${idx}: ${msg}`;
}, '').trim();
return { message: reduced };
}
}
Loading