From 0f7573598a3a6b6ef2dd5b2aa04ad416fcb610a2 Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Wed, 21 Apr 2021 18:14:36 -0700 Subject: [PATCH 01/43] chore(stepfunctions-tasks): move test dependency to devdependencies (#14291) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-stepfunctions-tasks/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 8e4c71ef3e10b..63d49eb3fb8d5 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -77,6 +77,7 @@ "@aws-cdk/aws-apigatewayv2": "0.0.0", "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-batch": "0.0.0", + "@aws-cdk/aws-databrew": "0.0.0", "jest": "^26.6.3", "pkglint": "0.0.0", "@aws-cdk/assert-internal": "0.0.0" @@ -85,7 +86,6 @@ "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", - "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecr": "0.0.0", @@ -107,7 +107,6 @@ "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", "@aws-cdk/aws-codebuild": "0.0.0", - "@aws-cdk/aws-databrew": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-ecr": "0.0.0", From 3f1aaa8321faed2e5e96d7ed243c8774f9dac5af Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 22 Apr 2021 05:18:42 +0100 Subject: [PATCH 02/43] chore(cli): backport import statement changes (#14306) A change on v2-main branch - 1972691 - removed the main index file of this library. This required some additional changes to internal import statements. Backport these changes to reduce merge conflicts. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/bin/cdk.ts | 3 ++- packages/aws-cdk/test/api/deploy-stack.test.ts | 2 +- packages/aws-cdk/test/api/sdk-provider.test.ts | 2 +- packages/aws-cdk/test/api/toolkit-info.test.ts | 2 +- packages/aws-cdk/test/assets.test.ts | 2 +- packages/aws-cdk/test/util/mock-sdk.ts | 3 ++- packages/aws-cdk/test/util/mock-toolkitinfo.ts | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 85ebcc00e821c..a9d5c8e0d9f0b 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -4,11 +4,12 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors/safe'; import * as yargs from 'yargs'; -import { ToolkitInfo, BootstrapSource, Bootstrapper } from '../lib'; import { SdkProvider } from '../lib/api/aws-auth'; +import { BootstrapSource, Bootstrapper } from '../lib/api/bootstrap'; import { CloudFormationDeployments } from '../lib/api/cloudformation-deployments'; import { CloudExecutable } from '../lib/api/cxapp/cloud-executable'; import { execProgram } from '../lib/api/cxapp/exec'; +import { ToolkitInfo } from '../lib/api/toolkit-info'; import { StackActivityProgress } from '../lib/api/util/cloudformation/stack-activity-monitor'; import { CdkToolkit } from '../lib/cdk-toolkit'; import { RequireApproval } from '../lib/diff'; diff --git a/packages/aws-cdk/test/api/deploy-stack.test.ts b/packages/aws-cdk/test/api/deploy-stack.test.ts index c89e1095363bf..31645e6c18d4a 100644 --- a/packages/aws-cdk/test/api/deploy-stack.test.ts +++ b/packages/aws-cdk/test/api/deploy-stack.test.ts @@ -1,4 +1,4 @@ -import { deployStack, ToolkitInfo } from '../../lib'; +import { deployStack, ToolkitInfo } from '../../lib/api'; import { DEFAULT_FAKE_TEMPLATE, testStack } from '../util'; import { MockedObject, mockResolvedEnvironment, MockSdk, MockSdkProvider, SyncHandlerSubsetOf } from '../util/mock-sdk'; diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index c09577d740870..c7dfbbcfeb45c 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -4,9 +4,9 @@ import * as AWS from 'aws-sdk'; import type { ConfigurationOptions } from 'aws-sdk/lib/config-base'; import * as promptly from 'promptly'; import * as uuid from 'uuid'; -import { PluginHost } from '../../lib'; import { ISDK, Mode, SDK, SdkProvider } from '../../lib/api/aws-auth'; import * as logging from '../../lib/logging'; +import { PluginHost } from '../../lib/plugin'; import * as bockfs from '../bockfs'; import { withMocked } from '../util'; import { FakeSts, RegisterRoleOptions, RegisterUserOptions } from './fake-sts'; diff --git a/packages/aws-cdk/test/api/toolkit-info.test.ts b/packages/aws-cdk/test/api/toolkit-info.test.ts index 113164404325e..1ef00d113142f 100644 --- a/packages/aws-cdk/test/api/toolkit-info.test.ts +++ b/packages/aws-cdk/test/api/toolkit-info.test.ts @@ -1,4 +1,4 @@ -import { ToolkitInfo } from '../../lib'; +import { ToolkitInfo } from '../../lib/api'; import { errorWithCode, mockBootstrapStack, MockSdk } from '../util/mock-sdk'; diff --git a/packages/aws-cdk/test/assets.test.ts b/packages/aws-cdk/test/assets.test.ts index 2426e20aa0972..c632a0cf620e0 100644 --- a/packages/aws-cdk/test/assets.test.ts +++ b/packages/aws-cdk/test/assets.test.ts @@ -1,5 +1,5 @@ import { AssetMetadataEntry } from '@aws-cdk/cloud-assembly-schema'; -import { ToolkitInfo } from '../lib'; +import { ToolkitInfo } from '../lib/api'; import { addMetadataAssetsToManifest } from '../lib/assets'; import { AssetManifestBuilder } from '../lib/util/asset-manifest-builder'; import { testStack } from './util'; diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index 9d1f1426a77a6..30dbbd682a5a4 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -1,6 +1,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as AWS from 'aws-sdk'; -import { Account, ISDK, SDK, SdkProvider, ToolkitInfo } from '../../lib'; +import { Account, ISDK, SDK, SdkProvider } from '../../lib/api/aws-auth'; +import { ToolkitInfo } from '../../lib/api/toolkit-info'; import { CloudFormationStack } from '../../lib/api/util/cloudformation'; const FAKE_CREDENTIALS = new AWS.Credentials({ accessKeyId: 'ACCESS', secretAccessKey: 'SECRET', sessionToken: 'TOKEN ' }); diff --git a/packages/aws-cdk/test/util/mock-toolkitinfo.ts b/packages/aws-cdk/test/util/mock-toolkitinfo.ts index f52ae4cf78267..326fa9dea8140 100644 --- a/packages/aws-cdk/test/util/mock-toolkitinfo.ts +++ b/packages/aws-cdk/test/util/mock-toolkitinfo.ts @@ -1,4 +1,4 @@ -import { ISDK, ToolkitInfo } from '../../lib'; +import { ISDK, ToolkitInfo } from '../../lib/api'; import { CloudFormationStack } from '../../lib/api/util/cloudformation'; export interface MockToolkitInfoProps { From 66fafe21e9fa6bcd0f72363bf04e6c813b658c1f Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Thu, 22 Apr 2021 11:30:08 +0100 Subject: [PATCH 03/43] chore: add validation for ubergen aws-cdk-lib README import re-writes (#14302) Imports in READMEs are now rewritten from '@aws-cdk/...' to 'aws-cdk-lib'. This change was introduced in #14255, but no validation was included in the original PR. This change introduces a post-build validation step for aws-cdk-lib to ensure that the module rewrites were properly done. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk-lib/package.json | 3 +- .../scripts/verify-readme-import-rewrites.ts | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 packages/aws-cdk-lib/scripts/verify-readme-import-rewrites.ts diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index abc3c2aabaf5e..eb7231dc1fa8a 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -34,7 +34,8 @@ "eslint": { "disable": true }, - "stripDeprecated": true + "stripDeprecated": true, + "post": ["node ./scripts/verify-readme-import-rewrites.js"] }, "cdk-package": { "post": "node ./scripts/verify-stripped-exp.js" diff --git a/packages/aws-cdk-lib/scripts/verify-readme-import-rewrites.ts b/packages/aws-cdk-lib/scripts/verify-readme-import-rewrites.ts new file mode 100644 index 0000000000000..a31f721c05dfc --- /dev/null +++ b/packages/aws-cdk-lib/scripts/verify-readme-import-rewrites.ts @@ -0,0 +1,49 @@ +/** + * This script verifies the behavior of the `rewriteReadmeImports` method in `ubergen`, + * which rewrites '@aws-cdk/...' imports to 'aws-cdk-lib' imports in the package READMEs. + * Any verification based on expected state of the live READMEs is going to be somewhat fragile, + * but this is the most certain way to be notified if either the tool is broken or the READMEs + * have been explicitly changed to no longer reference their import statements. + * + * The script looks at a few modules with known different import formats and verifies that the + * expected rewritten import is present in the generated .jsii manifest. + */ + +import * as path from 'path'; +import * as fs from 'fs-extra'; + +const jsiiManifestPath = path.resolve(process.cwd(), '.jsii'); +if (!fs.existsSync(jsiiManifestPath)) { + throw new Error(`No .jsii manifest file found at: ${jsiiManifestPath}`); +} + +const jsiiManifest = JSON.parse(fs.readFileSync(jsiiManifestPath, { encoding: 'utf-8' })); + +// Expected import statements chosen from the individual module READMEs to have a breadth of styles and syntax. +// If this test fails because one of the below import statements is invalid, +// please update to have a new, comparable example. +// This is admittedly a bit fragile; if this test breaks a lot, we should reconsider validation methodology. +// Count of times this test has been broken by README updates so far (please increment as necessary! :D): 0 +const EXPECTED_SUBMODULE_IMPORTS = { + // import * as origins from '@aws-cdk/aws-cloudfront-origins'; + 'aws-cdk-lib.aws_cloudfront_origins': "import { aws_cloudfront_origins as origins } from 'aws-cdk-lib';", + // import * as cw from "@aws-cdk/aws-cloudwatch"; + 'aws-cdk-lib.aws_cloudwatch_actions': "import { aws_cloudwatch as cw } from 'aws-cdk-lib';", + // import { PhysicalName } from '@aws-cdk/core'; + 'aws-cdk-lib.aws_codepipeline_actions': "import { PhysicalName } from 'aws-cdk-lib';", + // import { Rule, Schedule } from '@aws-cdk/aws-events'; + 'aws-cdk-lib.aws_events': "import { Rule, Schedule } from 'aws-cdk-lib/aws-events';", + // import * as cdk from '@aws-cdk/core'; + 'aws-cdk-lib.aws_stepfunctions': "import * as cdk from 'aws-cdk-lib';", +}; + +Object.entries(EXPECTED_SUBMODULE_IMPORTS).forEach(([submodule, importStatement]) => { + const submoduleReadme = jsiiManifest.submodules[submodule]?.readme?.markdown; + if (!submoduleReadme) { + throw new Error(`jsii manifest for submodule ${submodule} not found`); + } else if (!submoduleReadme.includes(importStatement)) { + const errorMessage = `Expected to find import statement in ${submodule} README: ${importStatement}\n` + + 'This may mean the README has changed and this test needs to be updated, or the uberGen rewriteReadmeImports method is broken.'; + throw new Error(errorMessage); + } +}); From bf513bc55e324d5d0ac23c2ddaa1d570a8d2ea1a Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 22 Apr 2021 15:36:23 +0200 Subject: [PATCH 04/43] feat(iam): add imported user to a group (#13698) Allow adding an imported user to a group by implementing `addToGroup()` with [`AWS::IAM::UserToGroupAddition`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-addusertogroup.html). ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-iam/README.md | 12 ++++++++ packages/@aws-cdk/aws-iam/lib/user.ts | 13 +++++--- packages/@aws-cdk/aws-iam/test/user.test.ts | 33 ++++++++++++++++++++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index 8cf5978aaa7c6..b54a0bff34fc4 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -433,6 +433,18 @@ const user = User.fromUserAttributes(stack, 'MyImportedUserByAttributes', { }); ``` +To add a user to a group (both for a new and imported user/group): + +```ts +const user = new User(this, 'MyUser'); // or User.fromUserName(stack, 'User', 'johnsmith'); +const group = new Group(this, 'MyGroup'); // or Group.fromGroupArn(stack, 'Group', 'arn:aws:iam::account-id:group/group-name'); + +user.addToGroup(group); +// or +group.addUser(user); +``` + + ## Features * Policy name uniqueness is enforced. If two policies by the same name are attached to the same diff --git a/packages/@aws-cdk/aws-iam/lib/user.ts b/packages/@aws-cdk/aws-iam/lib/user.ts index 5c8f6418a9bb8..4874e84f791df 100644 --- a/packages/@aws-cdk/aws-iam/lib/user.ts +++ b/packages/@aws-cdk/aws-iam/lib/user.ts @@ -1,7 +1,7 @@ import { Arn, Aws, Lazy, Resource, SecretValue, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IGroup } from './group'; -import { CfnUser } from './iam.generated'; +import { CfnUser, CfnUserToGroupAddition } from './iam.generated'; import { IIdentity } from './identity-base'; import { IManagedPolicy } from './managed-policy'; import { Policy } from './policy'; @@ -181,6 +181,7 @@ export class User extends Resource implements IIdentity, IUser { public readonly policyFragment: PrincipalPolicyFragment = new ArnPrincipal(attrs.userArn).policyFragment; private readonly attachedPolicies = new AttachedPolicies(); private defaultPolicy?: Policy; + private groupId = 0; public addToPolicy(statement: PolicyStatement): boolean { return this.addToPrincipalPolicy(statement).statementAdded; @@ -195,8 +196,12 @@ export class User extends Resource implements IIdentity, IUser { return { statementAdded: true, policyDependable: this.defaultPolicy }; } - public addToGroup(_group: IGroup): void { - throw new Error('Cannot add imported User to Group'); + public addToGroup(group: IGroup): void { + new CfnUserToGroupAddition(Stack.of(group), `${this.userName}Group${this.groupId}`, { + groupName: group.groupName, + users: [this.userName], + }); + this.groupId += 1; } public attachInlinePolicy(policy: Policy): void { @@ -229,7 +234,7 @@ export class User extends Resource implements IIdentity, IUser { public readonly userArn: string; /** - * Returns the permissions boundary attached to this user + * Returns the permissions boundary attached to this user */ public readonly permissionsBoundary?: IManagedPolicy; diff --git a/packages/@aws-cdk/aws-iam/test/user.test.ts b/packages/@aws-cdk/aws-iam/test/user.test.ts index af30005bec513..5cc42ae015619 100644 --- a/packages/@aws-cdk/aws-iam/test/user.test.ts +++ b/packages/@aws-cdk/aws-iam/test/user.test.ts @@ -1,6 +1,6 @@ import '@aws-cdk/assert-internal/jest'; import { App, SecretValue, Stack } from '@aws-cdk/core'; -import { ManagedPolicy, Policy, PolicyStatement, User } from '../lib'; +import { Group, ManagedPolicy, Policy, PolicyStatement, User } from '../lib'; describe('IAM user', () => { test('default user', () => { @@ -177,4 +177,35 @@ describe('IAM user', () => { }, }); }); + + test('addToGroup for imported user', () => { + // GIVEN + const stack = new Stack(); + const user = User.fromUserName(stack, 'ImportedUser', 'john'); + const group = new Group(stack, 'Group'); + const otherGroup = new Group(stack, 'OtherGroup'); + + // WHEN + user.addToGroup(group); + otherGroup.addUser(user); + + // THEN + expect(stack).toHaveResource('AWS::IAM::UserToGroupAddition', { + GroupName: { + Ref: 'GroupC77FDACD', + }, + Users: [ + 'john', + ], + }); + + expect(stack).toHaveResource('AWS::IAM::UserToGroupAddition', { + GroupName: { + Ref: 'OtherGroup85E5C653', + }, + Users: [ + 'john', + ], + }); + }); }); From f672dc442543f88f3acaec57104d52adc63c78ff Mon Sep 17 00:00:00 2001 From: Neta Nir Date: Thu, 22 Apr 2021 07:19:34 -0700 Subject: [PATCH 05/43] chore(cloudformation-diff): use types provided by the table module (#14321) The `table` module has published its own TS types, sadly the types provided by `table` are different from the one provided by `@types/table`. Changing the code to use the `table` types. This change is required to allow upgrading the `table` module. Executed `yarn install` to upgrade all other eligible dependencies, closing the blocked autocreated PR: https://github.com/aws/aws-cdk/pull/14303 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cloudformation-diff/lib/format-table.ts | 9 ++++---- .../@aws-cdk/cloudformation-diff/package.json | 3 +-- yarn.lock | 23 +++++++++++++++---- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-diff/lib/format-table.ts b/packages/@aws-cdk/cloudformation-diff/lib/format-table.ts index 372ab5d9acd8a..0799243ba954e 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/format-table.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/format-table.ts @@ -27,16 +27,15 @@ function lineBetween(rowA: string[], rowB: string[]) { return rowA[1] !== rowB[1]; } -function buildColumnConfig(widths: number[] | undefined): { [index: number]: table.TableColumns } | undefined { +function buildColumnConfig(widths: number[] | undefined): { [index: number]: table.ColumnUserConfig } | undefined { if (widths === undefined) { return undefined; } - const ret: { [index: number]: table.TableColumns } = {}; + const ret: { [index: number]: table.ColumnUserConfig } = {}; widths.forEach((width, i) => { - ret[i] = { width }; - if (width === undefined) { - delete ret[i].width; + return; } + ret[i] = { width }; }); return ret; diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index 7f76e1847c701..497069a37ab62 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -26,12 +26,11 @@ "diff": "^5.0.0", "fast-deep-equal": "^3.1.3", "string-width": "^4.2.2", - "table": "^6.1.0" + "table": "^6.3.0" }, "devDependencies": { "@types/jest": "^26.0.22", "@types/string-width": "^4.0.1", - "@types/table": "^6.0.0", "cdk-build-tools": "0.0.0", "fast-check": "^2.14.0", "jest": "^26.6.3", diff --git a/yarn.lock b/yarn.lock index 535425bd3644f..77dcb7d110fad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1727,7 +1727,7 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.4.tgz#445251eb00bd9c1e751f82c7c6bf4f714edfd464" integrity sha512-/emrKCfQMQmFCqRqqBJ0JueHBT06jBRM3e8OgnvDUcvuExONujIk2hFA5dNsN9Nt41ljGVDdChvCydATZ+KOZw== -"@typescript-eslint/eslint-plugin@^4.22.0": +"@typescript-eslint/eslint-plugin@^4.21.0", "@typescript-eslint/eslint-plugin@^4.22.0": version "4.22.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.0.tgz#3d5f29bb59e61a9dba1513d491b059e536e16dbc" integrity sha512-U8SP9VOs275iDXaL08Ln1Fa/wLXfj5aTr/1c0t0j6CdbOnxh+TruXu1p4I0NAvdPBQgoPjHsgKn28mOi0FzfoA== @@ -1765,7 +1765,7 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^4.22.0": +"@typescript-eslint/parser@^4.21.0", "@typescript-eslint/parser@^4.22.0": version "4.22.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.22.0.tgz#e1637327fcf796c641fe55f73530e90b16ac8fe8" integrity sha512-z/bGdBJJZJN76nvAY9DkJANYgK3nlRstRRi74WHm3jjgf2I8AglrSY+6l7ogxOmn55YJ6oKZCLLy+6PW70z15Q== @@ -3838,7 +3838,7 @@ eslint-plugin-import@^2.22.1: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jest@^24.3.5: +eslint-plugin-jest@^24.3.4, eslint-plugin-jest@^24.3.5: version "24.3.5" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.3.5.tgz#71f0b580f87915695c286c3f0eb88cf23664d044" integrity sha512-XG4rtxYDuJykuqhsOqokYIR84/C8pRihRtEpVskYLbIIKGwPNW2ySxdctuVzETZE+MbF/e7wmsnbNVpzM0rDug== @@ -3897,7 +3897,7 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint@^7.24.0: +eslint@^7.23.0, eslint@^7.24.0: version "7.24.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.24.0.tgz#2e44fa62d93892bfdb100521f17345ba54b8513a" integrity sha512-k9gaHeHiFmGCDQ2rEfvULlSLruz6tgfA8DEn+rY9/oYPFFTlz55mM/Q/Rij1b2Y42jwZiK3lXvNTw6w6TXzcKQ== @@ -9276,6 +9276,21 @@ table@^6.1.0: slice-ansi "^4.0.0" string-width "^4.2.0" +table@^6.3.0: + version "6.3.2" + resolved "https://registry.yarnpkg.com/table/-/table-6.3.2.tgz#afa86bee5cfe305f9328f89bb3e5454132cdea28" + integrity sha512-I9/Ca6Huf2oxFag7crD0DhA+arIdfLtWunSn0NIXSzjtUlDgIBGVZY7SsMkNPNT3Psd/z4gza0nuEpmra9eRbg== + dependencies: + ajv "^8.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + lodash.clonedeep "^4.5.0" + lodash.flatten "^4.4.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + tap-mocha-reporter@^3.0.9, tap-mocha-reporter@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/tap-mocha-reporter/-/tap-mocha-reporter-5.0.1.tgz#74f00be2ddd2a380adad45e085795137bc39497a" From 2d01c0748426887dc876f9fa59cc3fa2ce0db56b Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 22 Apr 2021 16:45:10 +0200 Subject: [PATCH 06/43] chore(v2): re-enable the assert transform for v2 (#14207) (#14214) We disabled it to make the previous builds work; now the absence of the tranforms is blocking the test because `assert` still depends on `@aws-cdk/core`. This is a backport of #14210. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/assert/.gitignore | 6 +- packages/@aws-cdk/assert/README.md | 227 --------- packages/@aws-cdk/assert/clone.sh | 9 + packages/@aws-cdk/assert/jest.ts | 107 ----- packages/@aws-cdk/assert/lib/assertion.ts | 40 -- .../assert/lib/assertions/and-assertion.ts | 19 - .../assert/lib/assertions/count-resources.ts | 58 --- .../@aws-cdk/assert/lib/assertions/exist.ts | 18 - .../assert/lib/assertions/have-output.ts | 116 ----- .../lib/assertions/have-resource-matchers.ts | 430 ------------------ .../assert/lib/assertions/have-resource.ts | 163 ------- .../assert/lib/assertions/have-type.ts | 21 - .../assert/lib/assertions/match-template.ts | 96 ---- .../lib/assertions/negated-assertion.ts | 16 - .../assert/lib/canonicalize-assets.ts | 71 --- packages/@aws-cdk/assert/lib/expect.ts | 12 - packages/@aws-cdk/assert/lib/index.ts | 15 - packages/@aws-cdk/assert/lib/inspector.ts | 74 --- packages/@aws-cdk/assert/lib/synth-utils.ts | 87 ---- .../@aws-cdk/assert/test/assertions.test.ts | 349 -------------- .../assert/test/canonicalize-assets.test.ts | 135 ------ .../@aws-cdk/assert/test/have-output.test.ts | 202 -------- .../assert/test/have-resource.test.ts | 279 ------------ .../@aws-cdk/assert/test/synth-utils.test.ts | 14 - .../aws-cdk-migration/bin/rewrite-imports-v2 | 2 +- 25 files changed, 15 insertions(+), 2551 deletions(-) delete mode 100644 packages/@aws-cdk/assert/README.md delete mode 100644 packages/@aws-cdk/assert/jest.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertion.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/and-assertion.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/count-resources.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/exist.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/have-output.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/have-resource-matchers.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/have-resource.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/have-type.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/match-template.ts delete mode 100644 packages/@aws-cdk/assert/lib/assertions/negated-assertion.ts delete mode 100644 packages/@aws-cdk/assert/lib/canonicalize-assets.ts delete mode 100644 packages/@aws-cdk/assert/lib/expect.ts delete mode 100644 packages/@aws-cdk/assert/lib/index.ts delete mode 100644 packages/@aws-cdk/assert/lib/inspector.ts delete mode 100644 packages/@aws-cdk/assert/lib/synth-utils.ts delete mode 100644 packages/@aws-cdk/assert/test/assertions.test.ts delete mode 100644 packages/@aws-cdk/assert/test/canonicalize-assets.test.ts delete mode 100644 packages/@aws-cdk/assert/test/have-output.test.ts delete mode 100644 packages/@aws-cdk/assert/test/have-resource.test.ts delete mode 100644 packages/@aws-cdk/assert/test/synth-utils.test.ts diff --git a/packages/@aws-cdk/assert/.gitignore b/packages/@aws-cdk/assert/.gitignore index 6a8dcb15f4bcf..f18f865e5fb57 100644 --- a/packages/@aws-cdk/assert/.gitignore +++ b/packages/@aws-cdk/assert/.gitignore @@ -20,4 +20,8 @@ nyc.config.js !.eslintrc.js !jest.config.js -junit.xml \ No newline at end of file +junit.xml +./lib +./test +README.md +jest.ts diff --git a/packages/@aws-cdk/assert/README.md b/packages/@aws-cdk/assert/README.md deleted file mode 100644 index 9256b46d2b154..0000000000000 --- a/packages/@aws-cdk/assert/README.md +++ /dev/null @@ -1,227 +0,0 @@ -# Testing utilities and assertions for CDK libraries - - ---- - -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) - -> The APIs of higher level constructs in this module are experimental and under active development. -> They are subject to non-backward compatible changes or removal in any future version. These are -> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be -> announced in the release notes. This means that while you may use them, you may need to update -> your source code when upgrading to a newer version of this package. - ---- - - - -This library contains helpers for writing unit tests and integration tests for CDK libraries - -## Unit tests - -Write your unit tests like this: - -```ts -const stack = new Stack(); - -new MyConstruct(stack, 'MyConstruct', { - ... -}); - -expect(stack).to(someExpectation(...)); -``` - -Here are the expectations you can use: - -## Verify (parts of) a template - -Check that the synthesized stack template looks like the given template, or is a superset of it. These functions match logical IDs and all properties of a resource. - -```ts -matchTemplate(template, matchStyle) -exactlyMatchTemplate(template) -beASupersetOfTemplate(template) -``` - -Example: - -```ts -expect(stack).to(beASupersetOfTemplate({ - Resources: { - HostedZone674DD2B7: { - Type: "AWS::Route53::HostedZone", - Properties: { - Name: "test.private.", - VPCs: [{ - VPCId: { Ref: 'VPC06C5F037' }, - VPCRegion: { Ref: 'AWS::Region' } - }] - } - } - } -})); -``` - - -## Check existence of a resource - -If you only care that a resource of a particular type exists (regardless of its logical identifier), and that *some* of its properties are set to specific values: - -```ts -haveResource(type, subsetOfProperties) -haveResourceLike(type, subsetOfProperties) -``` - -Example: - -```ts -expect(stack).to(haveResource('AWS::CertificateManager::Certificate', { - DomainName: 'test.example.com', - // Note: some properties omitted here - - ShouldNotExist: ABSENT -})); -``` - -The object you give to `haveResource`/`haveResourceLike` like can contain the -following values: - -- **Literal values**: the given property in the resource must match the given value *exactly*. -- `ABSENT`: a magic value to assert that a particular key in an object is *not* set (or set to `undefined`). -- special matchers for inexact matching. You can use these to match values based on more lenient conditions - than the default (such as an array containing at least one element, ignoring the rest, or an inexact string - match). - -The following matchers exist: - -- `objectLike(O)` - the value has to be an object matching at least the keys in `O` (but may contain - more). The nested values must match exactly. -- `deepObjectLike(O)` - as `objectLike`, but nested objects are also treated as partial specifications. -- `exactValue(X)` - must match exactly the given value. Use this to escape from `deepObjectLike`'s leniency - back to exact value matching. -- `arrayWith(E, [F, ...])` - value must be an array containing the given elements (or matchers) in any order. -- `stringLike(S)` - value must be a string matching `S`. `S` may contain `*` as wildcard to match any number - of characters. -- `anything()` - matches any value. -- `notMatching(M)` - any value that does NOT match the given matcher (or exact value) given. -- `encodedJson(M)` - value must be a string which, when decoded as JSON, matches the given matcher or - exact value. - -Slightly more complex example with array matchers: - -```ts -expect(stack).to(haveResourceLike('AWS::IAM::Policy', { - PolicyDocument: { - Statement: arrayWith(objectLike({ - Action: ['s3:GetObject'], - Resource: ['arn:my:arn'], - }}) - } -})); -``` - -## Capturing values from a match - -Special `Capture` matchers exist to capture values encountered during a match. These can be -used for two typical purposes: - -- Apply additional assertions to the values found during a matching operation. -- Use the value found during a matching operation in a new matching operation. - -`Capture` matchers take an inner matcher as an argument, and will only capture the value -if the inner matcher succeeds in matching the given value. - -Here's an example which asserts that a policy for `RoleA` contains two statements -with *different* ARNs (without caring what those ARNs might be), and that -a policy for `RoleB` *also* has a statement for one of those ARNs (again, without -caring what the ARN might be): - -```ts -const arn1 = Capture.aString(); -const arn2 = Capture.aString(); - -expect(stack).to(haveResourceLike('AWS::IAM::Policy', { - Roles: ['RoleA'], - PolicyDocument: { - Statement: [ - objectLike({ - Resource: [arn1.capture()], - }), - objectLike({ - Resource: [arn2.capture()], - }), - ], - }, -})); - -// Don't care about the values as long as they are not the same -expect(arn1.capturedValue).not.toEqual(arn2.capturedValue); - -expect(stack).to(haveResourceLike('AWS::IAM::Policy', { - Roles: ['RoleB'], - PolicyDocument: { - Statement: [ - objectLike({ - // This ARN must be the same as ARN1 above. - Resource: [arn1.capturedValue] - }), - ], - }, -})); -``` - -NOTE: `Capture` look somewhat like *bindings* in other pattern matching -libraries you might be used to, but they are far simpler and very -deterministic. In particular, they don't do unification: if the same Capture -is either used multiple times in the same structure expression or matches -multiple times, no restarting of the match is done to make them all match the -same value: the last value encountered by the `Capture` (as determined by the -behavior of the matchers around it) is stored into it and will be the one -available after the match has completed. - -## Check number of resources - -If you want to assert that `n` number of resources of a particular type exist, with or without specific properties: - -```ts -countResources(type, count) -countResourcesLike(type, count, props) -``` - -Example: - -```ts -expect(stack).to(countResources('AWS::ApiGateway::Method', 3)); -expect(stack).to(countResourcesLike('AWS::ApiGateway::Method', 1, { - HttpMethod: 'GET', - ResourceId: { - "Ref": "MyResource01234" - } -})); -``` - -## Check existence of an output - -`haveOutput` assertion can be used to check that a stack contains specific output. -Parameters to check against can be: - -- `outputName` -- `outputValue` -- `exportName` - -If `outputValue` is provided, at least one of `outputName`, `exportName` should be provided as well - -Example - -```ts -expect(synthStack).to(haveOutput({ - outputName: 'TestOutputName', - exportName: 'TestOutputExportName', - outputValue: { - 'Fn::GetAtt': [ - 'TestResource', - 'Arn' - ] - } -})); -``` diff --git a/packages/@aws-cdk/assert/clone.sh b/packages/@aws-cdk/assert/clone.sh index a4e441512f9a6..c8d6c6e82af9f 100755 --- a/packages/@aws-cdk/assert/clone.sh +++ b/packages/@aws-cdk/assert/clone.sh @@ -4,6 +4,9 @@ scriptdir=$(cd $(dirname $0) && pwd) cd $scriptdir set -euo pipefail src="../assert-internal" + +# Don't copy .d.ts and .js files -- otherwise tsc might not recreate +# those files after we have rewritten the .ts files (probably due to timestamps) rsync -av $src/lib/ lib/ rsync -av $src/test/ test/ @@ -16,5 +19,11 @@ for file in ${files}; do done if [[ "$majorversion" = "2" ]]; then + echo "Rewriting TS files..." npx rewrite-imports-v2 "**/*.ts" + + # This forces a recompile even if this file already exists + rm -f tsconfig.tsbuildinfo + + echo "Done." fi diff --git a/packages/@aws-cdk/assert/jest.ts b/packages/@aws-cdk/assert/jest.ts deleted file mode 100644 index 5c6db5727ed8d..0000000000000 --- a/packages/@aws-cdk/assert/jest.ts +++ /dev/null @@ -1,107 +0,0 @@ -import * as core from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; -import { countResources } from './lib'; -import { JestFriendlyAssertion } from './lib/assertion'; -import { haveOutput, HaveOutputProperties } from './lib/assertions/have-output'; -import { HaveResourceAssertion, ResourcePart } from './lib/assertions/have-resource'; -import { MatchStyle, matchTemplate } from './lib/assertions/match-template'; -import { expect as ourExpect } from './lib/expect'; -import { StackInspector } from './lib/inspector'; - -declare global { - namespace jest { - interface Matchers { - toMatchTemplate( - template: any, - matchStyle?: MatchStyle): R; - - toHaveResource( - resourceType: string, - properties?: any, - comparison?: ResourcePart): R; - - toHaveResourceLike( - resourceType: string, - properties?: any, - comparison?: ResourcePart): R; - - toHaveOutput(props: HaveOutputProperties): R; - - toCountResources(resourceType: string, count: number): R; - } - } -} - -expect.extend({ - toMatchTemplate( - actual: cxapi.CloudFormationStackArtifact | core.Stack, - template: any, - matchStyle?: MatchStyle) { - - const assertion = matchTemplate(template, matchStyle); - const inspector = ourExpect(actual); - const pass = assertion.assertUsing(inspector); - if (pass) { - return { - pass, - message: () => 'Not ' + assertion.description, - }; - } else { - return { - pass, - message: () => assertion.description, - }; - } - }, - - toHaveResource( - actual: cxapi.CloudFormationStackArtifact | core.Stack, - resourceType: string, - properties?: any, - comparison?: ResourcePart) { - - const assertion = new HaveResourceAssertion(resourceType, properties, comparison, false); - return applyAssertion(assertion, actual); - }, - - toHaveResourceLike( - actual: cxapi.CloudFormationStackArtifact | core.Stack, - resourceType: string, - properties?: any, - comparison?: ResourcePart) { - - const assertion = new HaveResourceAssertion(resourceType, properties, comparison, true); - return applyAssertion(assertion, actual); - }, - - toHaveOutput( - actual: cxapi.CloudFormationStackArtifact | core.Stack, - props: HaveOutputProperties) { - - return applyAssertion(haveOutput(props), actual); - }, - - toCountResources( - actual: cxapi.CloudFormationStackArtifact | core.Stack, - resourceType: string, - count = 1) { - - return applyAssertion(countResources(resourceType, count), actual); - }, -}); - -function applyAssertion(assertion: JestFriendlyAssertion, actual: cxapi.CloudFormationStackArtifact | core.Stack) { - const inspector = ourExpect(actual); - const pass = assertion.assertUsing(inspector); - if (pass) { - return { - pass, - message: () => 'Not ' + assertion.generateErrorMessage(), - }; - } else { - return { - pass, - message: () => assertion.generateErrorMessage(), - }; - } -} diff --git a/packages/@aws-cdk/assert/lib/assertion.ts b/packages/@aws-cdk/assert/lib/assertion.ts deleted file mode 100644 index 376b099f8433f..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertion.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Inspector } from './inspector'; - -export abstract class Assertion { - public abstract readonly description: string; - - public abstract assertUsing(inspector: InspectorClass): boolean; - - /** - * Assert this thing and another thing - */ - public and(assertion: Assertion): Assertion { - // Needs to delegate to a function so that we can import mutually dependent classes in the right order - return and(this, assertion); - } - - public assertOrThrow(inspector: InspectorClass) { - if (!this.assertUsing(inspector)) { - throw new Error(`${JSON.stringify(inspector.value, null, 2)} does not match ${this.description}`); - } - } -} - -export abstract class JestFriendlyAssertion extends Assertion { - /** - * Generates an error message that can be used by Jest. - */ - public abstract generateErrorMessage(): string; -} - -import { AndAssertion } from './assertions/and-assertion'; - -function and(left: Assertion, right: Assertion): Assertion { - return new AndAssertion(left, right); -} - -import { NegatedAssertion } from './assertions/negated-assertion'; - -export function not(assertion: Assertion): Assertion { - return new NegatedAssertion(assertion); -} diff --git a/packages/@aws-cdk/assert/lib/assertions/and-assertion.ts b/packages/@aws-cdk/assert/lib/assertions/and-assertion.ts deleted file mode 100644 index 737dbaca67e5e..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/and-assertion.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Assertion } from '../assertion'; -import { Inspector } from '../inspector'; - -export class AndAssertion extends Assertion { - public description: string = 'Combined assertion'; - - constructor(private readonly first: Assertion, private readonly second: Assertion) { - super(); - } - - public assertUsing(_inspector: InspectorClass): boolean { - throw new Error('This is never called'); - } - - public assertOrThrow(inspector: InspectorClass) { - this.first.assertOrThrow(inspector); - this.second.assertOrThrow(inspector); - } -} diff --git a/packages/@aws-cdk/assert/lib/assertions/count-resources.ts b/packages/@aws-cdk/assert/lib/assertions/count-resources.ts deleted file mode 100644 index 0827ba1f18306..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/count-resources.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Assertion, JestFriendlyAssertion } from '../assertion'; -import { StackInspector } from '../inspector'; -import { isSuperObject } from './have-resource'; - -/** - * An assertion to check whether a resource of a given type and with the given properties exists, disregarding properties - */ -export function countResources(resourceType: string, count = 1): JestFriendlyAssertion { - return new CountResourcesAssertion(resourceType, count); -} - -/** - * An assertion to check whether a resource of a given type and with the given properties exists, considering properties - */ -export function countResourcesLike(resourceType: string, count = 1, props: any): Assertion { - return new CountResourcesAssertion(resourceType, count, props); -} - -class CountResourcesAssertion extends JestFriendlyAssertion { - private inspected: number = 0; - private readonly props: any; - - constructor( - private readonly resourceType: string, - private readonly count: number, - props: any = null) { - super(); - this.props = props; - } - - public assertUsing(inspector: StackInspector): boolean { - let counted = 0; - for (const logicalId of Object.keys(inspector.value.Resources || {})) { - const resource = inspector.value.Resources[logicalId]; - if (resource.Type === this.resourceType) { - if (this.props) { - if (isSuperObject(resource.Properties, this.props, [], true)) { - counted++; - this.inspected += 1; - } - } else { - counted++; - this.inspected += 1; - } - } - } - - return counted === this.count; - } - - public generateErrorMessage(): string { - return this.description; - } - - public get description(): string { - return `stack only has ${this.inspected} resource of type ${this.resourceType}${this.props ? ' with specified properties' : ''} but we expected to find ${this.count}`; - } -} diff --git a/packages/@aws-cdk/assert/lib/assertions/exist.ts b/packages/@aws-cdk/assert/lib/assertions/exist.ts deleted file mode 100644 index 3cc62f0444de4..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/exist.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Assertion } from '../assertion'; -import { StackPathInspector } from '../inspector'; - -class ExistingResourceAssertion extends Assertion { - public description: string = 'an existing resource'; - - constructor() { - super(); - } - - public assertUsing(inspector: StackPathInspector): boolean { - return inspector.value !== undefined; - } -} - -export function exist(): Assertion { - return new ExistingResourceAssertion(); -} diff --git a/packages/@aws-cdk/assert/lib/assertions/have-output.ts b/packages/@aws-cdk/assert/lib/assertions/have-output.ts deleted file mode 100644 index 36f76b3e573a0..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/have-output.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { JestFriendlyAssertion } from '../assertion'; -import { StackInspector } from '../inspector'; - -class HaveOutputAssertion extends JestFriendlyAssertion { - private readonly inspected: InspectionFailure[] = []; - - constructor(private readonly outputName?: string, private readonly exportName?: any, private outputValue?: any) { - super(); - if (!this.outputName && !this.exportName) { - throw new Error('At least one of [outputName, exportName] should be provided'); - } - } - - public get description(): string { - const descriptionPartsArray = new Array(); - - if (this.outputName) { - descriptionPartsArray.push(`name '${this.outputName}'`); - } - if (this.exportName) { - descriptionPartsArray.push(`export name ${JSON.stringify(this.exportName)}`); - } - if (this.outputValue) { - descriptionPartsArray.push(`value ${JSON.stringify(this.outputValue)}`); - } - - return 'output with ' + descriptionPartsArray.join(', '); - } - - public assertUsing(inspector: StackInspector): boolean { - if (!('Outputs' in inspector.value)) { - return false; - } - - for (const [name, props] of Object.entries(inspector.value.Outputs as Record)) { - const mismatchedFields = new Array(); - - if (this.outputName && name !== this.outputName) { - mismatchedFields.push('name'); - } - - if (this.exportName && JSON.stringify(this.exportName) !== JSON.stringify(props.Export?.Name)) { - mismatchedFields.push('export name'); - } - - if (this.outputValue && JSON.stringify(this.outputValue) !== JSON.stringify(props.Value)) { - mismatchedFields.push('value'); - } - - if (mismatchedFields.length === 0) { - return true; - } - - this.inspected.push({ - output: { [name]: props }, - failureReason: `mismatched ${mismatchedFields.join(', ')}`, - }); - } - - return false; - } - - public generateErrorMessage() { - const lines = new Array(); - - lines.push(`None of ${this.inspected.length} outputs matches ${this.description}.`); - - for (const inspected of this.inspected) { - lines.push(`- ${inspected.failureReason} in:`); - lines.push(indent(4, JSON.stringify(inspected.output, null, 2))); - } - - return lines.join('\n'); - } -} - -/** - * Interface for haveOutput function properties - * NOTE that at least one of [outputName, exportName] should be provided - */ -export interface HaveOutputProperties { - /** - * Logical ID of the output - * @default - the logical ID of the output will not be checked - */ - outputName?: string; - /** - * Export name of the output, when it's exported for cross-stack referencing - * @default - the export name is not required and will not be checked - */ - exportName?: any; - /** - * Value of the output; - * @default - the value will not be checked - */ - outputValue?: any; -} - -interface InspectionFailure { - output: any; - failureReason: string; -} - -/** - * An assertion to check whether Output with particular properties is present in a stack - * @param props properties of the Output that is being asserted against. - * Check ``HaveOutputProperties`` interface to get full list of available parameters - */ -export function haveOutput(props: HaveOutputProperties): JestFriendlyAssertion { - return new HaveOutputAssertion(props.outputName, props.exportName, props.outputValue); -} - -function indent(n: number, s: string) { - const prefix = ' '.repeat(n); - return prefix + s.replace(/\n/g, '\n' + prefix); -} diff --git a/packages/@aws-cdk/assert/lib/assertions/have-resource-matchers.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource-matchers.ts deleted file mode 100644 index deb64b769ff16..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/have-resource-matchers.ts +++ /dev/null @@ -1,430 +0,0 @@ -import { ABSENT, InspectionFailure, PropertyMatcher } from './have-resource'; - -/** - * A matcher for an object that contains at least the given fields with the given matchers (or literals) - * - * Only does lenient matching one level deep, at the next level all objects must declare the - * exact expected keys again. - */ -export function objectLike(pattern: A): PropertyMatcher { - return _objectContaining(pattern, false); -} - -/** - * A matcher for an object that contains at least the given fields with the given matchers (or literals) - * - * Switches to "deep" lenient matching. Nested objects also only need to contain declared keys. - */ -export function deepObjectLike(pattern: A): PropertyMatcher { - return _objectContaining(pattern, true); -} - -function _objectContaining(pattern: A, deep: boolean): PropertyMatcher { - const anno = { [deep ? '$deepObjectLike' : '$objectLike']: pattern }; - - return annotateMatcher(anno, (value: any, inspection: InspectionFailure): boolean => { - if (typeof value !== 'object' || !value) { - return failMatcher(inspection, `Expect an object but got '${typeof value}'`); - } - - const errors = new Array(); - - for (const [patternKey, patternValue] of Object.entries(pattern)) { - if (patternValue === ABSENT) { - if (value[patternKey] !== undefined) { errors.push(`Field ${patternKey} present, but shouldn't be`); } - continue; - } - - if (!(patternKey in value)) { - errors.push(`Field ${patternKey} missing`); - continue; - } - - // If we are doing DEEP objectLike, translate object literals in the pattern into - // more `deepObjectLike` matchers, even if they occur in lists. - const matchValue = deep ? deepMatcherFromObjectLiteral(patternValue) : patternValue; - - const innerInspection = { ...inspection, failureReason: '' }; - const valueMatches = match(value[patternKey], matchValue, innerInspection); - if (!valueMatches) { - errors.push(`Field ${patternKey} mismatch: ${innerInspection.failureReason}`); - } - } - - /** - * Transform nested object literals into more deep object matchers, if applicable - * - * Object literals in lists are also transformed. - */ - function deepMatcherFromObjectLiteral(nestedPattern: any): any { - if (isObject(nestedPattern)) { - return deepObjectLike(nestedPattern); - } - if (Array.isArray(nestedPattern)) { - return nestedPattern.map(deepMatcherFromObjectLiteral); - } - return nestedPattern; - } - - if (errors.length > 0) { - return failMatcher(inspection, errors.join(', ')); - } - return true; - }); -} - -/** - * Match exactly the given value - * - * This is the default, you only need this to escape from the deep lenient matching - * of `deepObjectLike`. - */ -export function exactValue(expected: any): PropertyMatcher { - const anno = { $exactValue: expected }; - return annotateMatcher(anno, (value: any, inspection: InspectionFailure): boolean => { - return matchLiteral(value, expected, inspection); - }); -} - -/** - * A matcher for a list that contains all of the given elements in any order - */ -export function arrayWith(...elements: any[]): PropertyMatcher { - if (elements.length === 0) { return anything(); } - - const anno = { $arrayContaining: elements.length === 1 ? elements[0] : elements }; - return annotateMatcher(anno, (value: any, inspection: InspectionFailure): boolean => { - if (!Array.isArray(value)) { - return failMatcher(inspection, `Expect an array but got '${typeof value}'`); - } - - for (const element of elements) { - const failure = longestFailure(value, element); - if (failure) { - return failMatcher(inspection, `Array did not contain expected element, closest match at index ${failure[0]}: ${failure[1]}`); - } - } - - return true; - - /** - * Return 'null' if the matcher matches anywhere in the array, otherwise the longest error and its index - */ - function longestFailure(array: any[], matcher: any): [number, string] | null { - let fail: [number, string] | null = null; - for (let i = 0; i < array.length; i++) { - const innerInspection = { ...inspection, failureReason: '' }; - if (match(array[i], matcher, innerInspection)) { - return null; - } - - if (fail === null || innerInspection.failureReason.length > fail[1].length) { - fail = [i, innerInspection.failureReason]; - } - } - return fail; - } - }); -} - -/** - * Whether a value is an object - */ -function isObject(x: any): x is object { - // Because `typeof null === 'object'`. - return x && typeof x === 'object'; -} - -/** - * Helper function to make matcher failure reporting a little easier - * - * Our protocol is weird (change a string on a passed-in object and return 'false'), - * but I don't want to change that right now. - */ -export function failMatcher(inspection: InspectionFailure, error: string): boolean { - inspection.failureReason = error; - return false; -} - -/** - * Match a given literal value against a matcher - * - * If the matcher is a callable, use that to evaluate the value. Otherwise, the values - * must be literally the same. - */ -export function match(value: any, matcher: any, inspection: InspectionFailure) { - if (isCallable(matcher)) { - // Custom matcher (this mostly looks very weird because our `InspectionFailure` signature is weird) - const innerInspection: InspectionFailure = { ...inspection, failureReason: '' }; - const result = matcher(value, innerInspection); - if (typeof result !== 'boolean') { - return failMatcher(inspection, `Predicate returned non-boolean return value: ${result}`); - } - if (!result && !innerInspection.failureReason) { - // Custom matcher neglected to return an error - return failMatcher(inspection, 'Predicate returned false'); - } - // Propagate inner error in case of failure - if (!result) { inspection.failureReason = innerInspection.failureReason; } - return result; - } - - return matchLiteral(value, matcher, inspection); -} - -/** - * Match a literal value at the top level. - * - * When recursing into arrays or objects, the nested values can be either matchers - * or literals. - */ -function matchLiteral(value: any, pattern: any, inspection: InspectionFailure) { - if (pattern == null) { return true; } - - const errors = new Array(); - - if (Array.isArray(value) !== Array.isArray(pattern)) { - return failMatcher(inspection, 'Array type mismatch'); - } - if (Array.isArray(value)) { - if (pattern.length !== value.length) { - return failMatcher(inspection, 'Array length mismatch'); - } - - // Recurse comparison for individual objects - for (let i = 0; i < pattern.length; i++) { - if (!match(value[i], pattern[i], { ...inspection })) { - errors.push(`Array element ${i} mismatch`); - } - } - - if (errors.length > 0) { - return failMatcher(inspection, errors.join(', ')); - } - return true; - } - if ((typeof value === 'object') !== (typeof pattern === 'object')) { - return failMatcher(inspection, 'Object type mismatch'); - } - if (typeof pattern === 'object') { - // Check that all fields in the pattern have the right value - const innerInspection = { ...inspection, failureReason: '' }; - const matcher = objectLike(pattern)(value, innerInspection); - if (!matcher) { - inspection.failureReason = innerInspection.failureReason; - return false; - } - - // Check no fields uncovered - const realFields = new Set(Object.keys(value)); - for (const key of Object.keys(pattern)) { realFields.delete(key); } - if (realFields.size > 0) { - return failMatcher(inspection, `Unexpected keys present in object: ${Array.from(realFields).join(', ')}`); - } - return true; - } - - if (value !== pattern) { - return failMatcher(inspection, 'Different values'); - } - - return true; -} - -/** - * Whether a value is a callable - */ -function isCallable(x: any): x is ((...args: any[]) => any) { - return x && {}.toString.call(x) === '[object Function]'; -} - -/** - * Do a glob-like pattern match (which only supports *s) - */ -export function stringLike(pattern: string): PropertyMatcher { - // Replace * with .* in the string, escape the rest and brace with ^...$ - const regex = new RegExp(`^${pattern.split('*').map(escapeRegex).join('.*')}$`); - - return annotateMatcher({ $stringContaining: pattern }, (value: any, failure: InspectionFailure) => { - if (typeof value !== 'string') { - failure.failureReason = `Expected a string, but got '${typeof value}'`; - return false; - } - - if (!regex.test(value)) { - failure.failureReason = 'String did not match pattern'; - return false; - } - - return true; - }); -} - -/** - * Matches any value - */ -export function anything(): PropertyMatcher { - return annotateMatcher({ $anything: true }, () => true); -} - -/** - * Negate an inner matcher - */ -export function notMatching(matcher: any): PropertyMatcher { - return annotateMatcher({ $notMatching: matcher }, (value: any, failure: InspectionFailure) => { - const result = matcherFrom(matcher)(value, failure); - if (result) { - failure.failureReason = 'Should not have matched, but did'; - return false; - } - return true; - }); -} - -export type TypeValidator = (x: any) => x is T; - -/** - * Captures a value onto an object if it matches a given inner matcher - * - * @example - * - * const someValue = Capture.aString(); - * expect(stack).toHaveResource({ - * // ... - * Value: someValue.capture(stringMatching('*a*')), - * }); - * console.log(someValue.capturedValue); - */ -export class Capture { - /** - * A Capture object that captures any type - */ - public static anyType(): Capture { - return new Capture(); - } - - /** - * A Capture object that captures a string type - */ - public static aString(): Capture { - return new Capture((x: any): x is string => { - if (typeof x !== 'string') { - throw new Error(`Expected to capture a string, got '${x}'`); - } - return true; - }); - } - - /** - * A Capture object that captures a custom type - */ - // eslint-disable-next-line @typescript-eslint/no-shadow - public static a(validator: TypeValidator): Capture { - return new Capture(validator); - } - - private _value?: T; - private _didCapture = false; - private _wasInvoked = false; - - protected constructor(private readonly typeValidator?: TypeValidator) { - } - - /** - * Capture the value if the inner matcher successfully matches it - * - * If no matcher is given, `anything()` is assumed. - * - * And exception will be thrown if the inner matcher returns `true` and - * the value turns out to be of a different type than the `Capture` object - * is expecting. - */ - public capture(matcher?: any): PropertyMatcher { - if (matcher === undefined) { - matcher = anything(); - } - - return annotateMatcher({ $capture: matcher }, (value: any, failure: InspectionFailure) => { - this._wasInvoked = true; - const result = matcherFrom(matcher)(value, failure); - if (result) { - if (this.typeValidator && !this.typeValidator(value)) { - throw new Error(`Value not of the expected type: ${value}`); - } - this._didCapture = true; - this._value = value; - } - return result; - }); - } - - /** - * Whether a value was successfully captured - */ - public get didCapture() { - return this._didCapture; - } - - /** - * Return the value that was captured - * - * Throws an exception if now value was captured - */ - public get capturedValue(): T { - // When this module is ported to jsii, the type parameter will obviously - // have to be dropped and this will have to turn into an `any`. - if (!this.didCapture) { - throw new Error(`Did not capture a value: ${this._wasInvoked ? 'inner matcher failed' : 'never invoked'}`); - } - return this._value!; - } -} - -/** - * Match on the innards of a JSON string, instead of the complete string - */ -export function encodedJson(matcher: any): PropertyMatcher { - return annotateMatcher({ $encodedJson: matcher }, (value: any, failure: InspectionFailure) => { - if (typeof value !== 'string') { - failure.failureReason = `Expected a string, but got '${typeof value}'`; - return false; - } - - let decoded; - try { - decoded = JSON.parse(value); - } catch (e) { - failure.failureReason = `String is not JSON: ${e}`; - return false; - } - - return matcherFrom(matcher)(decoded, failure); - }); -} - -function escapeRegex(s: string) { - return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string -} - -/** - * Make a matcher out of the given argument if it's not a matcher already - * - * If it's not a matcher, it will be treated as a literal. - */ -export function matcherFrom(matcher: any): PropertyMatcher { - return isCallable(matcher) ? matcher : exactValue(matcher); -} - -/** - * Annotate a matcher with toJSON - * - * We will JSON.stringify() values if we have a match failure, but for matchers this - * would show (in traditional JS fashion) something like '[function Function]', or more - * accurately nothing at all since functions cannot be JSONified. - * - * We override to JSON() in order to produce a readadable version of the matcher. - */ -export function annotateMatcher(how: A, matcher: PropertyMatcher): PropertyMatcher { - (matcher as any).toJSON = () => how; - return matcher; -} diff --git a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts deleted file mode 100644 index 5a977d8252fd4..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { Assertion, JestFriendlyAssertion } from '../assertion'; -import { StackInspector } from '../inspector'; -import { anything, deepObjectLike, match, objectLike } from './have-resource-matchers'; - -/** - * Magic value to signify that a certain key should be absent from the property bag. - * - * The property is either not present or set to `undefined. - * - * NOTE: `ABSENT` only works with the `haveResource()` and `haveResourceLike()` - * assertions. - */ -export const ABSENT = '{{ABSENT}}'; - -/** - * An assertion to check whether a resource of a given type and with the given properties exists, disregarding properties - * - * @param resourceType the type of the resource that is expected to be present. - * @param properties the properties that the resource is expected to have. A function may be provided, in which case - * it will be called with the properties of candidate resources and an ``InspectionFailure`` - * instance on which errors should be appended, and should return a truthy value to denote a match. - * @param comparison the entity that is being asserted against. - * @param allowValueExtension if properties is an object, tells whether values must match exactly, or if they are - * allowed to be supersets of the reference values. Meaningless if properties is a function. - */ -export function haveResource( - resourceType: string, - properties?: any, - comparison?: ResourcePart, - allowValueExtension: boolean = false): Assertion { - return new HaveResourceAssertion(resourceType, properties, comparison, allowValueExtension); -} - -/** - * Sugar for calling ``haveResource`` with ``allowValueExtension`` set to ``true``. - */ -export function haveResourceLike( - resourceType: string, - properties?: any, - comparison?: ResourcePart) { - return haveResource(resourceType, properties, comparison, true); -} - -export type PropertyMatcher = (props: any, inspection: InspectionFailure) => boolean; - -export class HaveResourceAssertion extends JestFriendlyAssertion { - private readonly inspected: InspectionFailure[] = []; - private readonly part: ResourcePart; - private readonly matcher: any; - - constructor( - private readonly resourceType: string, - properties?: any, - part?: ResourcePart, - allowValueExtension: boolean = false) { - super(); - - this.matcher = isCallable(properties) ? properties : - properties === undefined ? anything() : - allowValueExtension ? deepObjectLike(properties) : - objectLike(properties); - this.part = part ?? ResourcePart.Properties; - } - - public assertUsing(inspector: StackInspector): boolean { - for (const logicalId of Object.keys(inspector.value.Resources || {})) { - const resource = inspector.value.Resources[logicalId]; - if (resource.Type === this.resourceType) { - const propsToCheck = this.part === ResourcePart.Properties ? (resource.Properties ?? {}) : resource; - - // Pass inspection object as 2nd argument, initialize failure with default string, - // to maintain backwards compatibility with old predicate API. - const inspection = { resource, failureReason: 'Object did not match predicate' }; - - if (match(propsToCheck, this.matcher, inspection)) { - return true; - } - - this.inspected.push(inspection); - } - } - - return false; - } - - public generateErrorMessage() { - const lines: string[] = []; - lines.push(`None of ${this.inspected.length} resources matches ${this.description}.`); - - for (const inspected of this.inspected) { - lines.push(`- ${inspected.failureReason} in:`); - lines.push(indent(4, JSON.stringify(inspected.resource, null, 2))); - } - - return lines.join('\n'); - } - - public assertOrThrow(inspector: StackInspector) { - if (!this.assertUsing(inspector)) { - throw new Error(this.generateErrorMessage()); - } - } - - public get description(): string { - // eslint-disable-next-line max-len - return `resource '${this.resourceType}' with ${JSON.stringify(this.matcher, undefined, 2)}`; - } -} - -function indent(n: number, s: string) { - const prefix = ' '.repeat(n); - return prefix + s.replace(/\n/g, '\n' + prefix); -} - -export interface InspectionFailure { - resource: any; - failureReason: string; -} - -/** - * What part of the resource to compare - */ -export enum ResourcePart { - /** - * Only compare the resource's properties - */ - Properties, - - /** - * Check the entire CloudFormation config - * - * (including UpdateConfig, DependsOn, etc.) - */ - CompleteDefinition -} - -/** - * Whether a value is a callable - */ -function isCallable(x: any): x is ((...args: any[]) => any) { - return x && {}.toString.call(x) === '[object Function]'; -} - -/** - * Return whether `superObj` is a super-object of `obj`. - * - * A super-object has the same or more property values, recursing into sub properties if ``allowValueExtension`` is true. - * - * At any point in the object, a value may be replaced with a function which will be used to check that particular field. - * The type of a matcher function is expected to be of type PropertyMatcher. - * - * @deprecated - Use `objectLike` or a literal object instead. - */ -export function isSuperObject(superObj: any, pattern: any, errors: string[] = [], allowValueExtension: boolean = false): boolean { - const matcher = allowValueExtension ? deepObjectLike(pattern) : objectLike(pattern); - - const inspection: InspectionFailure = { resource: superObj, failureReason: '' }; - const ret = match(superObj, matcher, inspection); - if (!ret) { - errors.push(inspection.failureReason); - } - return ret; -} diff --git a/packages/@aws-cdk/assert/lib/assertions/have-type.ts b/packages/@aws-cdk/assert/lib/assertions/have-type.ts deleted file mode 100644 index a04d8a450a338..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/have-type.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Assertion } from '../assertion'; -import { StackPathInspector } from '../inspector'; - -export function haveType(type: string): Assertion { - return new StackPathHasTypeAssertion(type); -} - -class StackPathHasTypeAssertion extends Assertion { - constructor(private readonly type: string) { - super(); - } - - public assertUsing(inspector: StackPathInspector): boolean { - const resource = inspector.value; - return resource !== undefined && resource.Type === this.type; - } - - public get description(): string { - return `resource of type ${this.type}`; - } -} diff --git a/packages/@aws-cdk/assert/lib/assertions/match-template.ts b/packages/@aws-cdk/assert/lib/assertions/match-template.ts deleted file mode 100644 index e668466d12416..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/match-template.ts +++ /dev/null @@ -1,96 +0,0 @@ -import * as cfnDiff from '@aws-cdk/cloudformation-diff'; -import { Assertion } from '../assertion'; -import { StackInspector } from '../inspector'; - -export enum MatchStyle { - /** Requires an exact match */ - EXACT = 'exactly', - /** Allows any change that does not cause a resource replacement */ - NO_REPLACES = 'no replaces', - /** Allows additions, but no updates */ - SUPERSET = 'superset' -} - -export function exactlyMatchTemplate(template: { [key: string]: any }) { - return matchTemplate(template, MatchStyle.EXACT); -} - -export function beASupersetOfTemplate(template: { [key: string]: any }) { - return matchTemplate(template, MatchStyle.SUPERSET); -} - -export function matchTemplate( - template: { [key: string]: any }, - matchStyle: MatchStyle = MatchStyle.EXACT): Assertion { - return new StackMatchesTemplateAssertion(template, matchStyle); -} - -class StackMatchesTemplateAssertion extends Assertion { - constructor( - private readonly template: { [key: string]: any }, - private readonly matchStyle: MatchStyle) { - super(); - } - - public assertOrThrow(inspector: StackInspector) { - if (!this.assertUsing(inspector)) { - // The details have already been printed, so don't generate a huge error message - throw new Error('Template comparison produced unacceptable match'); - } - } - - public assertUsing(inspector: StackInspector): boolean { - const diff = cfnDiff.diffTemplate(this.template, inspector.value); - const acceptable = this.isDiffAcceptable(diff); - if (!acceptable) { - // Print the diff - cfnDiff.formatDifferences(process.stderr, diff); - - // Print the actual template - process.stdout.write('--------------------------------------------------------------------------------------\n'); - process.stdout.write(JSON.stringify(inspector.value, undefined, 2) + '\n'); - } - - return acceptable; - } - - private isDiffAcceptable(diff: cfnDiff.TemplateDiff): boolean { - switch (this.matchStyle) { - case MatchStyle.EXACT: - return diff.differenceCount === 0; - case MatchStyle.NO_REPLACES: - for (const change of Object.values(diff.resources.changes)) { - if (change.changeImpact === cfnDiff.ResourceImpact.MAY_REPLACE) { return false; } - if (change.changeImpact === cfnDiff.ResourceImpact.WILL_REPLACE) { return false; } - } - - for (const change of Object.values(diff.parameters.changes)) { - if (change.isUpdate) { return false; } - } - - for (const change of Object.values(diff.outputs.changes)) { - if (change.isUpdate) { return false; } - } - return true; - case MatchStyle.SUPERSET: - for (const change of Object.values(diff.resources.changes)) { - if (change.changeImpact !== cfnDiff.ResourceImpact.WILL_CREATE) { return false; } - } - - for (const change of Object.values(diff.parameters.changes)) { - if (change.isAddition) { return false; } - } - - for (const change of Object.values(diff.outputs.changes)) { - if (change.isAddition || change.isUpdate) { return false; } - } - - return true; - } - throw new Error(`Unsupported match style: ${this.matchStyle}`); - } - - public get description(): string { - return `template (${this.matchStyle}): ${JSON.stringify(this.template, null, 2)}`; - } -} diff --git a/packages/@aws-cdk/assert/lib/assertions/negated-assertion.ts b/packages/@aws-cdk/assert/lib/assertions/negated-assertion.ts deleted file mode 100644 index 4c62225ee48a9..0000000000000 --- a/packages/@aws-cdk/assert/lib/assertions/negated-assertion.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Assertion } from '../assertion'; -import { Inspector } from '../inspector'; - -export class NegatedAssertion extends Assertion { - constructor(private readonly negated: Assertion) { - super(); - } - - public assertUsing(inspector: I): boolean { - return !this.negated.assertUsing(inspector); - } - - public get description(): string { - return `not ${this.negated.description}`; - } -} diff --git a/packages/@aws-cdk/assert/lib/canonicalize-assets.ts b/packages/@aws-cdk/assert/lib/canonicalize-assets.ts deleted file mode 100644 index 9cee3d4742b3c..0000000000000 --- a/packages/@aws-cdk/assert/lib/canonicalize-assets.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Reduce template to a normal form where asset references have been normalized - * - * This makes it possible to compare templates if all that's different between - * them is the hashes of the asset values. - * - * Currently only handles parameterized assets, but can (and should) - * be adapted to handle convention-mode assets as well when we start using - * more of those. - */ -export function canonicalizeTemplate(template: any): any { - // For the weird case where we have an array of templates... - if (Array.isArray(template)) { - return template.map(canonicalizeTemplate); - } - - // Find assets via parameters - const stringSubstitutions = new Array<[RegExp, string]>(); - const paramRe = /^AssetParameters([a-zA-Z0-9]{64})(S3Bucket|S3VersionKey|ArtifactHash)([a-zA-Z0-9]{8})$/; - - const assetsSeen = new Set(); - for (const paramName of Object.keys(template?.Parameters || {})) { - const m = paramRe.exec(paramName); - if (!m) { continue; } - if (assetsSeen.has(m[1])) { continue; } - - assetsSeen.add(m[1]); - const ix = assetsSeen.size; - - // Full parameter reference - stringSubstitutions.push([ - new RegExp(`AssetParameters${m[1]}(S3Bucket|S3VersionKey|ArtifactHash)([a-zA-Z0-9]{8})`), - `Asset${ix}$1`, - ]); - // Substring asset hash reference - stringSubstitutions.push([ - new RegExp(`${m[1]}`), - `Asset${ix}Hash`, - ]); - } - - // Substitute them out - return substitute(template); - - function substitute(what: any): any { - if (Array.isArray(what)) { - return what.map(substitute); - } - - if (typeof what === 'object' && what !== null) { - const ret: any = {}; - for (const [k, v] of Object.entries(what)) { - ret[stringSub(k)] = substitute(v); - } - return ret; - } - - if (typeof what === 'string') { - return stringSub(what); - } - - return what; - } - - function stringSub(x: string) { - for (const [re, replacement] of stringSubstitutions) { - x = x.replace(re, replacement); - } - return x; - } -} diff --git a/packages/@aws-cdk/assert/lib/expect.ts b/packages/@aws-cdk/assert/lib/expect.ts deleted file mode 100644 index 21dd7e011c826..0000000000000 --- a/packages/@aws-cdk/assert/lib/expect.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as cdk from '@aws-cdk/core'; -import * as api from '@aws-cdk/cx-api'; -import { StackInspector } from './inspector'; -import { SynthUtils } from './synth-utils'; - -export function expect(stack: api.CloudFormationStackArtifact | cdk.Stack | Record, skipValidation = false): StackInspector { - // if this is already a synthesized stack, then just inspect it. - const artifact = stack instanceof api.CloudFormationStackArtifact ? stack - : cdk.Stack.isStack(stack) ? SynthUtils._synthesizeWithNested(stack, { skipValidation }) - : stack; // This is a template already - return new StackInspector(artifact); -} diff --git a/packages/@aws-cdk/assert/lib/index.ts b/packages/@aws-cdk/assert/lib/index.ts deleted file mode 100644 index 902a5c222f003..0000000000000 --- a/packages/@aws-cdk/assert/lib/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export * from './assertion'; -export * from './canonicalize-assets'; -export * from './expect'; -export * from './inspector'; -export * from './synth-utils'; - -export * from './assertions/exist'; -export * from './assertions/have-output'; -export * from './assertions/have-resource'; -export * from './assertions/have-resource-matchers'; -export * from './assertions/have-type'; -export * from './assertions/match-template'; -export * from './assertions/and-assertion'; -export * from './assertions/negated-assertion'; -export * from './assertions/count-resources'; diff --git a/packages/@aws-cdk/assert/lib/inspector.ts b/packages/@aws-cdk/assert/lib/inspector.ts deleted file mode 100644 index f633de428f4f2..0000000000000 --- a/packages/@aws-cdk/assert/lib/inspector.ts +++ /dev/null @@ -1,74 +0,0 @@ -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as api from '@aws-cdk/cx-api'; -import { Assertion, not } from './assertion'; -import { MatchStyle, matchTemplate } from './assertions/match-template'; - -export abstract class Inspector { - public aroundAssert?: (cb: () => void) => any; - - constructor() { - this.aroundAssert = undefined; - } - - public to(assertion: Assertion): any { - return this.aroundAssert ? this.aroundAssert(() => this._to(assertion)) - : this._to(assertion); - } - - public notTo(assertion: Assertion): any { - return this.to(not(assertion)); - } - - abstract get value(): any; - - private _to(assertion: Assertion): any { - assertion.assertOrThrow(this); - } -} - -export class StackInspector extends Inspector { - - private readonly template: { [key: string]: any }; - - constructor(public readonly stack: api.CloudFormationStackArtifact | object) { - super(); - - this.template = stack instanceof api.CloudFormationStackArtifact ? stack.template : stack; - } - - public at(path: string | string[]): StackPathInspector { - if (!(this.stack instanceof api.CloudFormationStackArtifact)) { - throw new Error('Cannot use "expect(stack).at(path)" for a raw template, only CloudFormationStackArtifact'); - } - - const strPath = typeof path === 'string' ? path : path.join('/'); - return new StackPathInspector(this.stack, strPath); - } - - public toMatch(template: { [key: string]: any }, matchStyle = MatchStyle.EXACT) { - return this.to(matchTemplate(template, matchStyle)); - } - - public get value(): { [key: string]: any } { - return this.template; - } -} - -export class StackPathInspector extends Inspector { - constructor(public readonly stack: api.CloudFormationStackArtifact, public readonly path: string) { - super(); - } - - public get value(): { [key: string]: any } | undefined { - // The names of paths in metadata in tests are very ill-defined. Try with the full path first, - // then try with the stack name preprended for backwards compat with most tests that happen to give - // their stack an ID that's the same as the stack name. - const metadata = this.stack.manifest.metadata || {}; - const md = metadata[this.path] || metadata[`/${this.stack.id}${this.path}`]; - if (md === undefined) { return undefined; } - const resourceMd = md.find(entry => entry.type === cxschema.ArtifactMetadataEntryType.LOGICAL_ID); - if (resourceMd === undefined) { return undefined; } - const logicalId = resourceMd.data as cxschema.LogMessageMetadataEntry; - return this.stack.template.Resources[logicalId]; - } -} diff --git a/packages/@aws-cdk/assert/lib/synth-utils.ts b/packages/@aws-cdk/assert/lib/synth-utils.ts deleted file mode 100644 index bb8d9a437afd9..0000000000000 --- a/packages/@aws-cdk/assert/lib/synth-utils.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as core from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; - -export class SynthUtils { - /** - * Returns the cloud assembly template artifact for a stack. - */ - public static synthesize(stack: core.Stack, options: core.SynthesisOptions = { }): cxapi.CloudFormationStackArtifact { - // always synthesize against the root (be it an App or whatever) so all artifacts will be included - const assembly = synthesizeApp(stack, options); - return assembly.getStackArtifact(stack.artifactId); - } - - /** - * Synthesizes the stack and returns the resulting CloudFormation template. - */ - public static toCloudFormation(stack: core.Stack, options: core.SynthesisOptions = { }): any { - const synth = this._synthesizeWithNested(stack, options); - if (synth instanceof cxapi.CloudFormationStackArtifact) { - return synth.template; - } else { - return synth; - } - } - - /** - * @returns Returns a subset of the synthesized CloudFormation template (only specific resource types). - */ - public static subset(stack: core.Stack, options: SubsetOptions): any { - const template = this.toCloudFormation(stack); - if (template.Resources) { - for (const [key, resource] of Object.entries(template.Resources)) { - if (options.resourceTypes && !options.resourceTypes.includes((resource as any).Type)) { - delete template.Resources[key]; - } - } - } - - return template; - } - - /** - * Synthesizes the stack and returns a `CloudFormationStackArtifact` which can be inspected. - * Supports nested stacks as well as normal stacks. - * - * @return CloudFormationStackArtifact for normal stacks or the actual template for nested stacks - * @internal - */ - public static _synthesizeWithNested(stack: core.Stack, options: core.SynthesisOptions = { }): cxapi.CloudFormationStackArtifact | object { - // always synthesize against the root (be it an App or whatever) so all artifacts will be included - const assembly = synthesizeApp(stack, options); - - // if this is a nested stack (it has a parent), then just read the template as a string - if (stack.nestedStackParent) { - return JSON.parse(fs.readFileSync(path.join(assembly.directory, stack.templateFile)).toString('utf-8')); - } - - return assembly.getStackArtifact(stack.artifactId); - } -} - -/** - * Synthesizes the app in which a stack resides and returns the cloud assembly object. - */ -function synthesizeApp(stack: core.Stack, options: core.SynthesisOptions) { - const root = stack.node.root; - if (!core.Stage.isStage(root)) { - throw new Error('unexpected: all stacks must be part of a Stage or an App'); - } - - // to support incremental assertions (i.e. "expect(stack).toNotContainSomething(); doSomething(); expect(stack).toContainSomthing()") - const force = true; - - return root.synth({ - force, - ...options, - }); -} - -export interface SubsetOptions { - /** - * Match all resources of the given type - */ - resourceTypes?: string[]; -} diff --git a/packages/@aws-cdk/assert/test/assertions.test.ts b/packages/@aws-cdk/assert/test/assertions.test.ts deleted file mode 100644 index bd20d60032d76..0000000000000 --- a/packages/@aws-cdk/assert/test/assertions.test.ts +++ /dev/null @@ -1,349 +0,0 @@ -import * as cdk from '@aws-cdk/core'; -import * as cx from '@aws-cdk/cx-api'; -import * as constructs from 'constructs'; - -import { countResources, countResourcesLike, exist, expect as cdkExpect, haveType, MatchStyle, matchTemplate } from '../lib/index'; - -passingExample('expect at to have ', () => { - const resourceType = 'Test::Resource'; - const synthStack = synthesizedStack(stack => { - new TestResource(stack, 'TestResource', { type: resourceType }); - }); - cdkExpect(synthStack).at('/TestResource').to(haveType(resourceType)); -}); -passingExample('expect non-synthesized stack at to have ', () => { - const resourceType = 'Test::Resource'; - const stack = new cdk.Stack(); - new TestResource(stack, 'TestResource', { type: resourceType }); - cdkExpect(stack).at('/TestResource').to(haveType(resourceType)); -}); -passingExample('expect at *not* to have ', () => { - const resourceType = 'Test::Resource'; - const synthStack = synthesizedStack(stack => { - new TestResource(stack, 'TestResource', { type: resourceType }); - }); - cdkExpect(synthStack).at('/TestResource').notTo(haveType('Foo::Bar')); -}); -passingExample('expect at to exist', () => { - const resourceType = 'Test::Resource'; - const synthStack = synthesizedStack(stack => { - new TestResource(stack, 'TestResource', { type: resourceType }); - }); - cdkExpect(synthStack).at('/TestResource').to(exist()); -}); -passingExample('expect to match (exactly)