From 8f7d95cb8db270527bc8c6dab9175d07d3c48aa8 Mon Sep 17 00:00:00 2001 From: Christopher Rybicki Date: Wed, 18 May 2022 12:44:19 -0700 Subject: [PATCH 1/3] Revert "fix(integ-tests): DeployAssert should be private (#20382)" This reverts commit 0f3d33113ad61cf008c6e11511f0ec6a47ea6ab8. --- packages/@aws-cdk/integ-tests/README.md | 76 +++++----- .../integ-tests/lib/assertions/common.ts | 5 +- .../lib/assertions/deploy-assert.ts | 128 +++++++++++++++++ .../integ-tests/lib/assertions/index.ts | 4 +- .../lib/assertions/private/deploy-assert.ts | 76 ---------- .../providers/lambda-handler/index.ts | 2 + .../providers/lambda-handler/results.ts | 12 ++ .../providers/lambda-handler/types.ts | 22 +++ .../integ-tests/lib/assertions/sdk.ts | 135 ++++++++---------- .../integ-tests/lib/assertions/types.ts | 60 -------- .../@aws-cdk/integ-tests/lib/test-case.ts | 16 +-- .../integ-tests/rosetta/default.ts-fixture | 1 + .../test/assertions/deploy-assert.test.ts | 17 ++- .../integ-tests/test/assertions/sdk.test.ts | 79 ++++++---- 14 files changed, 327 insertions(+), 306 deletions(-) create mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts delete mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts create mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/results.ts delete mode 100644 packages/@aws-cdk/integ-tests/lib/assertions/types.ts diff --git a/packages/@aws-cdk/integ-tests/README.md b/packages/@aws-cdk/integ-tests/README.md index f428f7d683a7d..0e8fc9b1ca501 100644 --- a/packages/@aws-cdk/integ-tests/README.md +++ b/packages/@aws-cdk/integ-tests/README.md @@ -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; @@ -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'), @@ -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: { @@ -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', }); ``` @@ -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 @@ -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([ diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/common.ts b/packages/@aws-cdk/integ-tests/lib/assertions/common.ts index 6daa9e510133c..6e4fadf5a0388 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/common.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/common.ts @@ -1,6 +1,5 @@ import { CustomResource } from '@aws-cdk/core'; -import { IAwsApiCall } from './sdk'; - +import { AwsApiCall } from './sdk'; /** * Represents the "actual" results to compare */ @@ -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), }; diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts b/packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts new file mode 100644 index 0000000000000..24bbfd6789fbf --- /dev/null +++ b/packages/@aws-cdk/integ-tests/lib/assertions/deploy-assert.ts @@ -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, + }); + } +} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/index.ts b/packages/@aws-cdk/integ-tests/lib/assertions/index.ts index 6622ddabcb560..3a9defd954be9 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/index.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/index.ts @@ -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'; 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 deleted file mode 100644 index 361e340bc4a9c..0000000000000 --- a/packages/@aws-cdk/integ-tests/lib/assertions/private/deploy-assert.ts +++ /dev/null @@ -1,76 +0,0 @@ -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, - }); - } -} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts index 72ca3544cb66d..78a47c83be1ef 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/index.ts @@ -1,4 +1,5 @@ import { AssertionHandler } from './assertion'; +import { ResultsCollectionHandler } from './results'; import { AwsApiCallHandler } from './sdk'; import * as types from './types'; @@ -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}`); } diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/results.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/results.ts new file mode 100644 index 0000000000000..784ff68a05ab6 --- /dev/null +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/results.ts @@ -0,0 +1,12 @@ +import { CustomResourceHandler } from './base'; +import { ResultsCollectionRequest, ResultsCollectionResult } from './types'; + +export class ResultsCollectionHandler extends CustomResourceHandler { + protected async processEvent(request: ResultsCollectionRequest): Promise { + 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 }; + } +} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/types.ts b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/types.ts index 68bd63202afe8..ae9f545476dac 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/types.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/providers/lambda-handler/types.ts @@ -2,6 +2,7 @@ // Kept in a separate file for sharing between the handler and the provider constructs. export const ASSERT_RESOURCE_TYPE = 'Custom::DeployAssert@AssertEquals'; +export const RESULTS_RESOURCE_TYPE = 'Custom::DeployAssert@ResultsCollection'; export const SDK_RESOURCE_TYPE_PREFIX = 'Custom::DeployAssert@SdkCall'; /** @@ -154,3 +155,24 @@ export interface AssertionResultData { */ readonly message?: string; } + +/** + * Represents a collection of assertion request results + */ +export interface ResultsCollectionRequest { + /** + * The results of all the assertions that have been + * registered + */ + readonly assertionResults: AssertionResultData[]; +} + +/** + * The result of a results request + */ +export interface ResultsCollectionResult { + /** + * A message containing the results of the assertion + */ + readonly message: string; +} diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts index da54b1dbe24db..b176c13456f37 100644 --- a/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts +++ b/packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts @@ -4,84 +4,10 @@ import { EqualsAssertion } from './assertions'; import { ExpectedResult, ActualResult } from './common'; import { AssertionsProvider, SDK_RESOURCE_TYPE_PREFIX } from './providers'; -// 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 { IConstruct } from '@aws-cdk/core'; - // 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'; -/** - * Interface for creating a custom resource that will perform - * an API call using the AWS SDK - */ -export interface IAwsApiCall extends IConstruct { - /** - * Returns the value of an attribute of the custom resource of an arbitrary - * type. Attributes are returned from the custom resource provider through the - * `Data` map where the key is the attribute name. - * - * @param attributeName the name of the attribute - * @returns a token for `Fn::GetAtt`. Use `Token.asXxx` to encode the returned `Reference` as a specific type or - * use the convenience `getAttString` for string attributes. - */ - getAtt(attributeName: string): Reference; - - /** - * Returns the value of an attribute of the custom resource of type string. - * Attributes are returned from the custom resource provider through the - * `Data` map where the key is the attribute name. - * - * @param attributeName the name of the attribute - * @returns a token for `Fn::GetAtt` encoded as a string. - */ - getAttString(attributeName: string): string; - - /** - * Assert that the ExpectedResult is equal - * to the result of the AwsApiCall - * - * @example - * declare const integ: IntegTest; - * const invoke = integ.assert.invokeFunction({ - * functionName: 'my-func', - * }); - * invoke.assert(ExpectedResult.objectLike({ Payload: 'OK' })); - */ - assert(expected: ExpectedResult): void; - - /** - * Assert that the ExpectedResult is equal - * to the result of the AwsApiCall at the given path. - * - * For example the SQS.receiveMessage api response would look - * like: - * - * If you wanted to assert the value of `Body` you could do - * - * @example - * const actual = { - * Messages: [{ - * MessageId: '', - * ReceiptHandle: '', - * MD5OfBody: '', - * Body: 'hello', - * Attributes: {}, - * MD5OfMessageAttributes: {}, - * MessageAttributes: {} - * }] - * }; - * - * - * declare const integ: IntegTest; - * const message = integ.assert.awsApiCall('SQS', 'receiveMessage'); - * - * message.assertAtPath('Messages.0.Body', ExpectedResult.stringLikeRegexp('hello')); - */ - assertAtPath(path: string, expected: ExpectedResult): void; -} - /** * Options to perform an AWS JavaScript V2 API call */ @@ -113,7 +39,7 @@ export interface AwsApiCallProps extends AwsApiCallOptions {} * Construct that creates a custom resource that will perform * a query using the AWS SDK */ -export class AwsApiCall extends CoreConstruct implements IAwsApiCall { +export class AwsApiCall extends CoreConstruct { private readonly sdkCallResource: CustomResource; private flattenResponse: string = 'false'; private readonly name: string; @@ -143,16 +69,44 @@ export class AwsApiCall extends CoreConstruct implements IAwsApiCall { this.sdkCallResource.node.addDependency(this.provider); } + /** + * Returns the value of an attribute of the custom resource of an arbitrary + * type. Attributes are returned from the custom resource provider through the + * `Data` map where the key is the attribute name. + * + * @param attributeName the name of the attribute + * @returns a token for `Fn::GetAtt`. Use `Token.asXxx` to encode the returned `Reference` as a specific type or + * use the convenience `getAttString` for string attributes. + */ public getAtt(attributeName: string): Reference { this.flattenResponse = 'true'; return this.sdkCallResource.getAtt(`apiCallResponse.${attributeName}`); } + /** + * Returns the value of an attribute of the custom resource of type string. + * Attributes are returned from the custom resource provider through the + * `Data` map where the key is the attribute name. + * + * @param attributeName the name of the attribute + * @returns a token for `Fn::GetAtt` encoded as a string. + */ public getAttString(attributeName: string): string { this.flattenResponse = 'true'; return this.sdkCallResource.getAttString(`apiCallResponse.${attributeName}`); } + /** + * Assert that the ExpectedResult is equal + * to the result of the AwsApiCall + * + * @example + * declare const assert: DeployAssert; + * const invoke = new LambdaInvokeFunction(assert, 'Invoke', { + * functionName: 'my-func', + * }); + * invoke.assert(ExpectedResult.objectLike({ Payload: 'OK' })); + */ public assert(expected: ExpectedResult): void { new EqualsAssertion(this, `AssertEquals${this.name}`, { expected, @@ -160,6 +114,37 @@ export class AwsApiCall extends CoreConstruct implements IAwsApiCall { }); } + /** + * Assert that the ExpectedResult is equal + * to the result of the AwsApiCall at the given path. + * + * For example the SQS.receiveMessage api response would look + * like: + * + * If you wanted to assert the value of `Body` you could do + * + * @example + * const actual = { + * Messages: [{ + * MessageId: '', + * ReceiptHandle: '', + * MD5OfBody: '', + * Body: 'hello', + * Attributes: {}, + * MD5OfMessageAttributes: {}, + * MessageAttributes: {} + * }] + * }; + * + * + * declare const assert: DeployAssert; + * const message = new AwsApiCall(assert, 'ReceiveMessage', { + * service: 'SQS', + * api: 'receiveMessage' + * }); + * + * message.assertAtPath('Messages.0.Body', ExpectedResult.stringLikeRegexp('hello')); + */ public assertAtPath(path: string, expected: ExpectedResult): void { new EqualsAssertion(this, `AssertEquals${this.name}`, { expected, diff --git a/packages/@aws-cdk/integ-tests/lib/assertions/types.ts b/packages/@aws-cdk/integ-tests/lib/assertions/types.ts deleted file mode 100644 index 65025fb21d2e4..0000000000000 --- a/packages/@aws-cdk/integ-tests/lib/assertions/types.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ExpectedResult, ActualResult } from './common'; -import { IAwsApiCall, LambdaInvokeFunctionProps } from './sdk'; - -/** - * Interface that allows for registering a list of assertions - * that should be performed on a construct. This is only necessary - * when writing integration tests. - */ -export interface IDeployAssert { - /** - * 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; - * declare const integ: IntegTest; - * integ.assert.awsApiCall('SQS', 'sendMessage', { - * QueueUrl: 'url', - * MessageBody: 'hello', - * }); - * const message = integ.assert.awsApiCall('SQS', 'receiveMessage', { - * QueueUrl: 'url', - * }); - * message.assert(ExpectedResult.objectLike({ - * Messages: [{ Body: 'hello' }], - * })); - */ - awsApiCall(service: string, api: string, parameters?: any): IAwsApiCall; - - /** - * Invoke a lambda function and return the response which can be asserted - * - * @example - * declare const app: App; - * declare const integ: IntegTest; - * const invoke = integ.assert.invokeFunction({ - * functionName: 'my-function', - * }); - * invoke.assert(ExpectedResult.objectLike({ - * Payload: '200', - * })); - */ - invokeFunction(props: LambdaInvokeFunctionProps): IAwsApiCall; - - /** - * Assert that the ExpectedResult is equal - * to the ActualResult - * - * @example - * declare const integ: IntegTest; - * declare const apiCall: AwsApiCall; - * integ.assert.assert( - * 'invoke', - * ExpectedResult.objectLike({ Payload: 'OK' }), - * ActualResult.fromAwsApiCall(apiCall, 'Body'), - * ); - */ - assert(id: string, expected: ExpectedResult, actual: ActualResult): void; -} diff --git a/packages/@aws-cdk/integ-tests/lib/test-case.ts b/packages/@aws-cdk/integ-tests/lib/test-case.ts index 8c9d66118ac76..de701bb63d24a 100644 --- a/packages/@aws-cdk/integ-tests/lib/test-case.ts +++ b/packages/@aws-cdk/integ-tests/lib/test-case.ts @@ -1,8 +1,7 @@ import { IntegManifest, Manifest, TestCase, TestOptions } from '@aws-cdk/cloud-assembly-schema'; import { attachCustomSynthesis, Stack, ISynthesisSession, StackProps } from '@aws-cdk/core'; import { Construct } from 'constructs'; -import { IDeployAssert } from './assertions'; -import { DeployAssert } from './assertions/private/deploy-assert'; +import { DeployAssert } from './assertions'; import { IntegManifestSynthesizer } from './manifest-synthesizer'; const TEST_CASE_STACK_SYMBOL = Symbol.for('@aws-cdk/integ-tests.IntegTestCaseStack'); @@ -32,15 +31,12 @@ export class IntegTestCase extends CoreConstruct { /** * Make assertions on resources in this test case */ - public readonly assert: IDeployAssert; - - private readonly _assert: DeployAssert; + public readonly assert: DeployAssert; constructor(scope: Construct, id: string, private readonly props: IntegTestCaseProps) { super(scope, id); - this._assert = new DeployAssert(this); - this.assert = this._assert; + this.assert = new DeployAssert(this); } /** @@ -57,7 +53,7 @@ export class IntegTestCase extends CoreConstruct { private toTestCase(props: IntegTestCaseProps): TestCase { return { ...props, - assertionStack: this._assert.scope.artifactId, + assertionStack: Stack.of(this.assert).artifactId, stacks: props.stacks.map(s => s.artifactId), }; } @@ -87,7 +83,7 @@ export class IntegTestCaseStack extends Stack { /** * Make assertions on resources in this test case */ - public readonly assert: IDeployAssert; + public readonly assert: DeployAssert; /** * The underlying IntegTestCase that is created @@ -128,7 +124,7 @@ export class IntegTest extends CoreConstruct { /** * Make assertions on resources in this test case */ - public readonly assert: IDeployAssert; + public readonly assert: DeployAssert; private readonly testCases: IntegTestCase[]; constructor(scope: Construct, id: string, props: IntegTestProps) { super(scope, id); diff --git a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture index e85bf5884afdc..b9b4f3740b427 100644 --- a/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/integ-tests/rosetta/default.ts-fixture @@ -3,6 +3,7 @@ import { IntegTestCase, IntegTest, IntegTestCaseStack, + DeployAssert, AwsApiCall, EqualsAssertion, ActualResult, 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 c779618d2c1aa..847086ed66f7a 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,13 +1,12 @@ import { Template } from '@aws-cdk/assertions'; import { App, Stack } from '@aws-cdk/core'; -import { LogType, InvocationType, ExpectedResult, ActualResult } from '../../lib/assertions'; -import { DeployAssert } from '../../lib/assertions/private/deploy-assert'; +import { DeployAssert, LogType, InvocationType, ExpectedResult, ActualResult } from '../../lib/assertions'; describe('DeployAssert', () => { test('of', () => { const app = new App(); - const stack = new Stack(app, 'TestStack'); + const stack = new Stack(app); new DeployAssert(app); expect(() => { DeployAssert.of(stack); @@ -16,7 +15,7 @@ describe('DeployAssert', () => { test('throws if no DeployAssert', () => { const app = new App(); - const stack = new Stack(app, 'TestStack'); + const stack = new Stack(app); expect(() => { DeployAssert.of(stack); }).toThrow(/No DeployAssert construct found in scopes/); @@ -44,7 +43,7 @@ describe('DeployAssert', () => { }); // THEN - const template = Template.fromStack(deployAssert.scope); + const template = Template.fromStack(Stack.of(deployAssert)); template.hasResourceProperties('Custom::DeployAssert@SdkCallLambdainvoke', { service: 'Lambda', api: 'invoke', @@ -73,7 +72,7 @@ describe('DeployAssert', () => { ); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $StringLike: 'foo' }), actual: { @@ -99,7 +98,7 @@ describe('DeployAssert', () => { ); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $ObjectLike: { foo: 'bar' } }), actual: { @@ -123,7 +122,7 @@ describe('DeployAssert', () => { // THEN - Template.fromStack(deplossert.scope).hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { + Template.fromStack(Stack.of(deplossert)).hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { api: 'MyApi', service: 'MyService', }); @@ -140,7 +139,7 @@ describe('DeployAssert', () => { // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.resourceCountIs('AWS::Lambda::Function', 1); template.resourceCountIs('Custom::DeployAssert@SdkCallMyServiceMyApi1', 1); template.resourceCountIs('Custom::DeployAssert@SdkCallMyServiceMyApi2', 1); 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 d2f486a6687d9..31f1bd5068a4b 100644 --- a/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts +++ b/packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts @@ -1,7 +1,6 @@ import { Template, Match } from '@aws-cdk/assertions'; -import { App, CfnOutput } from '@aws-cdk/core'; -import { LogType, InvocationType, ExpectedResult } from '../../lib/assertions'; -import { DeployAssert } from '../../lib/assertions/private/deploy-assert'; +import { App, Stack, CfnOutput } from '@aws-cdk/core'; +import { DeployAssert, AwsApiCall, LambdaInvokeFunction, LogType, InvocationType, ExpectedResult } from '../../lib/assertions'; describe('AwsApiCall', () => { test('default', () => { @@ -10,10 +9,13 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - deplossert.awsApiCall('MyService', 'MyApi'); + new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.resourceCountIs('AWS::Lambda::Function', 1); template.hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { service: 'MyService', @@ -28,13 +30,17 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - deplossert.awsApiCall('MyService', 'MyApi', { - param1: 'val1', - param2: 2, + new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + parameters: { + param1: 'val1', + param2: 2, + }, }); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.resourceCountIs('AWS::Lambda::Function', 1); template.hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', { service: 'MyService', @@ -53,18 +59,21 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); - new CfnOutput(deplossert.scope, 'GetAttString', { + new CfnOutput(deplossert, 'GetAttString', { value: query.getAttString('att'), }).overrideLogicalId('GetAtt'); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasOutput('GetAtt', { Value: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse.att', ], }, @@ -76,25 +85,27 @@ describe('AwsApiCall', () => { flattenResponse: 'true', }); }); - test('getAtt', () => { // GIVEN const app = new App(); const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); - new CfnOutput(deplossert.scope, 'GetAttString', { + new CfnOutput(deplossert, 'GetAttString', { value: query.getAtt('att').toString(), }).overrideLogicalId('GetAtt'); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasOutput('GetAtt', { Value: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse.att', ], }, @@ -106,6 +117,7 @@ describe('AwsApiCall', () => { flattenResponse: 'true', }); }); + }); describe('assertEqual', () => { @@ -115,16 +127,19 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); query.assert(ExpectedResult.exact({ foo: 'bar' })); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $Exact: { foo: 'bar' } }), actual: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse', ], }, @@ -137,16 +152,19 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); query.assert(ExpectedResult.objectLike({ foo: 'bar' })); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $ObjectLike: { foo: 'bar' } }), actual: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse', ], }, @@ -159,16 +177,19 @@ describe('AwsApiCall', () => { const deplossert = new DeployAssert(app); // WHEN - const query = deplossert.awsApiCall('MyService', 'MyApi'); + const query = new AwsApiCall(deplossert, 'AwsApiCall', { + service: 'MyService', + api: 'MyApi', + }); query.assert(ExpectedResult.exact('bar')); // THEN - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@AssertEquals', { expected: JSON.stringify({ $Exact: 'bar' }), actual: { 'Fn::GetAtt': [ - 'AwsApiCallMyServiceMyApi', + 'AwsApiCall', 'apiCallResponse', ], }, @@ -182,14 +203,14 @@ describe('AwsApiCall', () => { const app = new App(); const deplossert = new DeployAssert(app); - deplossert.invokeFunction({ + new LambdaInvokeFunction(deplossert, 'Invoke', { functionName: 'my-func', logType: LogType.TAIL, payload: JSON.stringify({ key: 'val' }), invocationType: InvocationType.EVENT, }); - const template = Template.fromStack(deplossert.scope); + const template = Template.fromStack(Stack.of(deplossert)); template.hasResourceProperties('Custom::DeployAssert@SdkCallLambdainvoke', { service: 'Lambda', api: 'invoke', From 85ec59b18a431b08038e76fb9814329670e4714c Mon Sep 17 00:00:00 2001 From: Christopher Rybicki Date: Wed, 18 May 2022 15:09:18 -0700 Subject: [PATCH 2/3] empty commit From a48878b3d1d6cbdb4b231d7d5bec1f4c7e9098ba Mon Sep 17 00:00:00 2001 From: Christopher Rybicki Date: Wed, 18 May 2022 16:26:27 -0700 Subject: [PATCH 3/3] please work (empty commit)