Skip to content

Commit 0367fe8

Browse files
authored
chore: revert "DeployAssert should be private" (#20405)
Reverts #20382 This PR causes CDK Java packaging to fail because 'assert' cannot be used as an identifier in Java interfaces: ``` [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project cdk-integ-tests: Compilation failure: [ERROR] /tmp/npm-packrjmLgY/_aws-cdk_integ-tests/src/main/java/software/amazon/awscdk/integtests/IAwsApiCall.java:[28,10] as of release 1.4, 'assert' is a keyword, and may not be used as an identifier [ERROR] /tmp/npm-packrjmLgY/_aws-cdk_integ-tests/src/main/java/software/amazon/awscdk/integtests/IDeployAssert.java:[31,10] as of release 1.4, 'assert' is a keyword, and may not be used as an identifier ```
1 parent dc9536a commit 0367fe8

File tree

14 files changed

+327
-306
lines changed

14 files changed

+327
-306
lines changed

packages/@aws-cdk/integ-tests/README.md

+34-42
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,7 @@ new IntegTest(app, 'Integ', { testCases: [stackUnderTest, testCaseWithAssets] })
177177

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

180-
There are two main scenarios in which assertions are created.
181-
182-
- Part of an integration test using `integ-runner`
183-
184-
In this case you would create an integration test using the `IntegTest` construct and then make assertions using the `assert` property.
185-
You should **not** utilize the assertion constructs directly, but should instead use the `methods` on `IntegTest.assert`.
180+
The easiest way to do this is to create a `TestCase` and then access the `DeployAssert` that is automatically created.
186181

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

195-
- Part of a normal CDK deployment
190+
### DeployAssert
191+
192+
Assertions are created by using the `DeployAssert` construct. This construct creates it's own `Stack` separate from
193+
any stacks that you create as part of your integration tests. This `Stack` is treated differently from other stacks
194+
by the `integ-runner` tool. For example, this stack will not be diffed by the `integ-runner`.
196195

197-
In this case you may be using assertions as part of a normal CDK deployment in order to make an assertion on the infrastructure
198-
before the deployment is considered successful. In this case you can utilize the assertions constructs directly.
196+
Any assertions that you create should be created in the scope of `DeployAssert`. For example,
199197

200198
```ts
201-
declare const myAppStack: Stack;
199+
declare const app: App;
202200

203-
new AwsApiCall(myAppStack, 'GetObject', {
201+
const assert = new DeployAssert(app);
202+
new AwsApiCall(assert, 'GetObject', {
204203
service: 'S3',
205204
api: 'getObject',
206205
});
207206
```
208207

209-
### DeployAssert
210-
211-
Assertions are created by using the `DeployAssert` construct. This construct creates it's own `Stack` separate from
212-
any stacks that you create as part of your integration tests. This `Stack` is treated differently from other stacks
213-
by the `integ-runner` tool. For example, this stack will not be diffed by the `integ-runner`.
214-
215208
`DeployAssert` also provides utilities to register your own assertions.
216209

217210
```ts
218211
declare const myCustomResource: CustomResource;
219-
declare const stack: Stack;
220212
declare const app: App;
221-
222-
const integ = new IntegTest(app, 'Integ', { testCases: [stack] });
223-
integ.assert.assert(
213+
const assert = new DeployAssert(app);
214+
assert.assert(
224215
'CustomAssertion',
225216
ExpectedResult.objectLike({ foo: 'bar' }),
226217
ActualResult.fromCustomResource(myCustomResource, 'data'),
@@ -237,12 +228,12 @@ AWS API call to receive some data. This library does this by utilizing CloudForm
237228
which means that CloudFormation will call out to a Lambda Function which will
238229
use the AWS JavaScript SDK to make the API call.
239230

240-
This can be done by using the class directory (in the case of a normal deployment):
231+
This can be done by using the class directory:
241232

242233
```ts
243-
declare const stack: Stack;
234+
declare const assert: DeployAssert;
244235

245-
new AwsApiCall(stack, 'MyAssertion', {
236+
new AwsApiCall(assert, 'MyAssertion', {
246237
service: 'SQS',
247238
api: 'receiveMessage',
248239
parameters: {
@@ -251,15 +242,12 @@ new AwsApiCall(stack, 'MyAssertion', {
251242
});
252243
```
253244

254-
Or by using the `awsApiCall` method on `DeployAssert` (when writing integration tests):
245+
Or by using the `awsApiCall` method on `DeployAssert`:
255246

256247
```ts
257248
declare const app: App;
258-
declare const stack: Stack;
259-
const integ = new IntegTest(app, 'Integ', {
260-
testCases: [stack],
261-
});
262-
integ.assert.awsApiCall('SQS', 'receiveMessage', {
249+
const assert = new DeployAssert(app);
250+
assert.awsApiCall('SQS', 'receiveMessage', {
263251
QueueUrl: 'url',
264252
});
265253
```
@@ -293,18 +281,21 @@ const message = integ.assert.awsApiCall('SQS', 'receiveMessage', {
293281
WaitTimeSeconds: 20,
294282
});
295283

296-
message.assertAtPath('Messages.0.Body', ExpectedResult.objectLike({
297-
requestContext: {
298-
condition: 'Success',
299-
},
300-
requestPayload: {
301-
status: 'OK',
302-
},
303-
responseContext: {
304-
statusCode: 200,
305-
},
306-
responsePayload: 'success',
307-
}));
284+
new EqualsAssertion(integ.assert, 'ReceiveMessage', {
285+
actual: ActualResult.fromAwsApiCall(message, 'Messages.0.Body'),
286+
expected: ExpectedResult.objectLike({
287+
requestContext: {
288+
condition: 'Success',
289+
},
290+
requestPayload: {
291+
status: 'OK',
292+
},
293+
responseContext: {
294+
statusCode: 200,
295+
},
296+
responsePayload: 'success',
297+
}),
298+
});
308299
```
309300

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

315306
```ts
316307
declare const message: AwsApiCall;
308+
declare const assert: DeployAssert;
317309

318310
message.assert(ExpectedResult.objectLike({
319311
Messages: Match.arrayWith([

packages/@aws-cdk/integ-tests/lib/assertions/common.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { CustomResource } from '@aws-cdk/core';
2-
import { IAwsApiCall } from './sdk';
3-
2+
import { AwsApiCall } from './sdk';
43
/**
54
* Represents the "actual" results to compare
65
*/
@@ -17,7 +16,7 @@ export abstract class ActualResult {
1716
/**
1817
* Get the actual results from a AwsApiCall
1918
*/
20-
public static fromAwsApiCall(query: IAwsApiCall, attribute: string): ActualResult {
19+
public static fromAwsApiCall(query: AwsApiCall, attribute: string): ActualResult {
2120
return {
2221
result: query.getAttString(attribute),
2322
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { Stack } from '@aws-cdk/core';
2+
import { Construct, IConstruct, Node } from 'constructs';
3+
import { EqualsAssertion } from './assertions';
4+
import { ExpectedResult, ActualResult } from './common';
5+
import { md5hash } from './private/hash';
6+
import { AwsApiCall, LambdaInvokeFunction, LambdaInvokeFunctionProps } from './sdk';
7+
8+
const DEPLOY_ASSERT_SYMBOL = Symbol.for('@aws-cdk/integ-tests.DeployAssert');
9+
10+
11+
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
12+
// eslint-disable-next-line no-duplicate-imports, import/order
13+
import { Construct as CoreConstruct } from '@aws-cdk/core';
14+
15+
/**
16+
* Options for DeployAssert
17+
*/
18+
export interface DeployAssertProps { }
19+
20+
/**
21+
* Construct that allows for registering a list of assertions
22+
* that should be performed on a construct
23+
*/
24+
export class DeployAssert extends CoreConstruct {
25+
26+
/**
27+
* Returns whether the construct is a DeployAssert construct
28+
*/
29+
public static isDeployAssert(x: any): x is DeployAssert {
30+
return x !== null && typeof(x) === 'object' && DEPLOY_ASSERT_SYMBOL in x;
31+
}
32+
33+
/**
34+
* Finds a DeployAssert construct in the given scope
35+
*/
36+
public static of(construct: IConstruct): DeployAssert {
37+
const scopes = Node.of(Node.of(construct).root).findAll();
38+
const deployAssert = scopes.find(s => DeployAssert.isDeployAssert(s));
39+
if (!deployAssert) {
40+
throw new Error('No DeployAssert construct found in scopes');
41+
}
42+
return deployAssert as DeployAssert;
43+
}
44+
45+
constructor(scope: Construct) {
46+
/**
47+
* Normally we would not want to do a scope swapparoo like this
48+
* but in this case this it allows us to provide a better experience
49+
* for the user. This allows DeployAssert to be created _not_ in the
50+
* scope of a Stack. DeployAssert is treated like a Stack, but doesn't
51+
* exose any of the stack functionality (the methods that the user sees
52+
* are just DeployAssert methods and not any Stack methods). So you can do
53+
* something like this, which you would not normally be allowed to do
54+
*
55+
* const deployAssert = new DeployAssert(app);
56+
* new AwsApiCall(deployAssert, 'AwsApiCall', {...});
57+
*/
58+
scope = new Stack(scope, 'DeployAssert');
59+
super(scope, 'Default');
60+
61+
Object.defineProperty(this, DEPLOY_ASSERT_SYMBOL, { value: true });
62+
}
63+
64+
/**
65+
* Query AWS using JavaScript SDK V2 API calls. This can be used to either
66+
* trigger an action or to return a result that can then be asserted against
67+
* an expected value
68+
*
69+
* @example
70+
* declare const app: App;
71+
* const assert = new DeployAssert(app);
72+
* assert.awsApiCall('SQS', 'sendMessage', {
73+
* QueueUrl: 'url',
74+
* MessageBody: 'hello',
75+
* });
76+
* const message = assert.awsApiCall('SQS', 'receiveMessage', {
77+
* QueueUrl: 'url',
78+
* });
79+
* message.assert(ExpectedResult.objectLike({
80+
* Messages: [{ Body: 'hello' }],
81+
* }));
82+
*/
83+
public awsApiCall(service: string, api: string, parameters?: any): AwsApiCall {
84+
return new AwsApiCall(this, `AwsApiCall${service}${api}`, {
85+
api,
86+
service,
87+
parameters,
88+
});
89+
}
90+
91+
/**
92+
* Invoke a lambda function and return the response which can be asserted
93+
*
94+
* @example
95+
* declare const app: App;
96+
* const assert = new DeployAssert(app);
97+
* const invoke = assert.invokeFunction({
98+
* functionName: 'my-function',
99+
* });
100+
* invoke.assert(ExpectedResult.objectLike({
101+
* Payload: '200',
102+
* }));
103+
*/
104+
public invokeFunction(props: LambdaInvokeFunctionProps): LambdaInvokeFunction {
105+
const hash = md5hash(Stack.of(this).resolve(props));
106+
return new LambdaInvokeFunction(this, `LambdaInvoke${hash}`, props);
107+
}
108+
109+
/**
110+
* Assert that the ExpectedResult is equal
111+
* to the ActualResult
112+
*
113+
* @example
114+
* declare const deployAssert: DeployAssert;
115+
* declare const apiCall: AwsApiCall;
116+
* deployAssert.assert(
117+
* 'invoke',
118+
* ExpectedResult.objectLike({ Payload: 'OK' }),
119+
* ActualResult.fromAwsApiCall(apiCall, 'Body'),
120+
* );
121+
*/
122+
public assert(id: string, expected: ExpectedResult, actual: ActualResult): void {
123+
new EqualsAssertion(this, `EqualsAssertion${id}`, {
124+
expected,
125+
actual,
126+
});
127+
}
128+
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
export * from './types';
2-
export * from './sdk';
31
export * from './assertions';
2+
export * from './sdk';
3+
export * from './deploy-assert';
44
export * from './providers';
55
export * from './common';
66
export * from './match';

packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts

-76
This file was deleted.

packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AssertionHandler } from './assertion';
2+
import { ResultsCollectionHandler } from './results';
23
import { AwsApiCallHandler } from './sdk';
34
import * as types from './types';
45

@@ -13,6 +14,7 @@ function createResourceHandler(event: AWSLambda.CloudFormationCustomResourceEven
1314
}
1415
switch (event.ResourceType) {
1516
case types.ASSERT_RESOURCE_TYPE: return new AssertionHandler(event, context);
17+
case types.RESULTS_RESOURCE_TYPE: return new ResultsCollectionHandler(event, context);
1618
default:
1719
throw new Error(`Unsupported resource type "${event.ResourceType}`);
1820
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CustomResourceHandler } from './base';
2+
import { ResultsCollectionRequest, ResultsCollectionResult } from './types';
3+
4+
export class ResultsCollectionHandler extends CustomResourceHandler<ResultsCollectionRequest, ResultsCollectionResult> {
5+
protected async processEvent(request: ResultsCollectionRequest): Promise<ResultsCollectionResult | undefined> {
6+
const reduced: string = request.assertionResults.reduce((agg, result, idx) => {
7+
const msg = result.status === 'pass' ? 'pass' : `fail - ${result.message}`;
8+
return `${agg}\nTest${idx}: ${msg}`;
9+
}, '').trim();
10+
return { message: reduced };
11+
}
12+
}

0 commit comments

Comments
 (0)