diff --git a/packages/@aws-cdk/integ-runner/.eslintrc.js b/packages/@aws-cdk/integ-runner/.eslintrc.js index 2658ee8727166..934540549e665 100644 --- a/packages/@aws-cdk/integ-runner/.eslintrc.js +++ b/packages/@aws-cdk/integ-runner/.eslintrc.js @@ -1,3 +1,4 @@ const baseConfig = require('@aws-cdk/cdk-build-tools/config/eslintrc'); baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +baseConfig.ignorePatterns = [...baseConfig.ignorePatterns, "test/language-tests/**/integ.*.ts"]; module.exports = baseConfig; diff --git a/packages/@aws-cdk/integ-runner/README.md b/packages/@aws-cdk/integ-runner/README.md index bfe58e2ff4926..350087e45bc8b 100644 --- a/packages/@aws-cdk/integ-runner/README.md +++ b/packages/@aws-cdk/integ-runner/README.md @@ -70,16 +70,35 @@ to be a self contained CDK app. The runner will execute the following for each f If this is set to `true` then the [update workflow](#update-workflow) will be disabled - `--app` The custom CLI command that will be used to run the test files. You can include {filePath} to specify where in the command the test file path should be inserted. Example: --app="python3.8 {filePath}". + + Use together with `--test-regex` to fully customize how tests are run, or use with a single `--language` preset to change the command used for this language. - `--test-regex` Detect integration test files matching this JavaScript regex pattern. If used multiple times, all files matching any one of the patterns are detected. - + + Use together with `--app` to fully customize how tests are run, or use with a single `--language` preset to change which files are detected for this language. +- `--language` + The language presets to use. You can discover and run tests written in multiple languages by passing this flag multiple times (`--language typescript --language python`). Defaults to all supported languages. Currently supported language presets are: + - `javascript`: + - File RegExp: `^integ\..*\.js$` + - App run command: `node {filePath}` + - `typescript`:\ + Note that for TypeScript files compiled to JavaScript, the JS tests will take precedence and the TS ones won't be evaluated. + - File RegExp: `^integ\..*(? { + if (!fileName) { + return {}; + } + + try { + return JSON.parse(fs.readFileSync(fileName, { encoding: 'utf-8' })); + } catch { + return {}; + } +} diff --git a/packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts b/packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts index fa4d64a4d3876..9aa48874428b1 100644 --- a/packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts +++ b/packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts @@ -159,42 +159,112 @@ export interface IntegrationTestsDiscoveryOptions { readonly tests?: string[]; /** - * Detect integration test files matching any of these JavaScript regex patterns. - * - * @default - */ - readonly testRegex?: string[]; - - /** - * The CLI command used to run this test. - * If it contains {filePath}, the test file names will be substituted at that place in the command for each run. + * A map of of the app commands to run integration tests with, + * and the regex patterns matching the integration test files each app command. * - * @default - test run command will be `node {filePath}` + * If the app command contains {filePath}, the test file names will be substituted at that place in the command for each run. */ - readonly app?: string; + readonly testCases: { + [app: string]: string[] + } } +/** + * Returns the name of the Python executable for the current OS + */ +function pythonExecutable() { + let python = 'python3'; + if (process.platform === 'win32') { + python = 'python'; + } + return python; +} /** * Discover integration tests */ export class IntegrationTests { + constructor(private readonly directory: string) {} + /** - * Return configuration options from a file + * Get integration tests discovery options from CLI options */ - public static configFromFile(fileName?: string): Record { - if (!fileName) { - return {}; + public async fromCliOptions(options: { + app?: string; + exclude?: boolean, + language?: string[], + testRegex?: string[], + tests?: string[], + }): Promise { + const baseOptions = { + tests: options.tests, + exclude: options.exclude, + }; + + // Explicitly set both, app and test-regex + if (options.app && options.testRegex) { + return this.discover({ + testCases: { + [options.app]: options.testRegex, + }, + ...baseOptions, + }); + } + + // Use the selected presets + if (!options.app && !options.testRegex) { + // Only case with multiple languages, i.e. the only time we need to check the special case + const ignoreUncompiledTypeScript = options.language?.includes('javascript') && options.language?.includes('typescript'); + + return this.discover({ + testCases: this.getLanguagePresets(options.language), + ...baseOptions, + }, ignoreUncompiledTypeScript); } - try { - return JSON.parse(fs.readFileSync(fileName, { encoding: 'utf-8' })); - } catch { - return {}; + // Only one of app or test-regex is set, with a single preset selected + // => override either app or test-regex + if (options.language?.length === 1) { + const [presetApp, presetTestRegex] = this.getLanguagePreset(options.language[0]); + return this.discover({ + testCases: { + [options.app ?? presetApp]: options.testRegex ?? presetTestRegex, + }, + ...baseOptions, + }); } + + // Only one of app or test-regex is set, with multiple presets + // => impossible to resolve + const option = options.app ? '--app' : '--test-regex'; + throw new Error(`Only a single "--language" can be used with "${option}". Alternatively provide both "--app" and "--test-regex" to fully customize the configuration.`); + } + + /** + * Get the default configuration for a language + */ + private getLanguagePreset(language: string) { + const languagePresets: { + [language: string]: [string, string[]] + } = { + javascript: ['node {filePath}', ['^integ\\..*\\.js$']], + typescript: ['node -r ts-node/register {filePath}', ['^integ\\.(?!.*\\.d\\.ts$).*\\.ts$']], + python: [`${pythonExecutable()} {filePath}`, ['^integ_.*\\.py$']], + go: ['go run {filePath}', ['^integ_.*\\.go$']], + }; + + return languagePresets[language]; } - constructor(private readonly directory: string) { + /** + * Get the config for all selected languages + */ + private getLanguagePresets(languages: string[] = []) { + return Object.fromEntries( + languages + .map(language => this.getLanguagePreset(language)) + .filter(Boolean), + ); } /** @@ -209,7 +279,6 @@ export class IntegrationTests { return discoveredTests; } - const allTests = discoveredTests.filter(t => { const matches = requestedTests.some(pattern => t.matches(pattern)); return matches !== !!exclude; // Looks weird but is equal to (matches && !exclude) || (!matches && exclude) @@ -237,33 +306,41 @@ export class IntegrationTests { * @param tests Tests to include or exclude, undefined means include all tests. * @param exclude Whether the 'tests' list is inclusive or exclusive (inclusive by default). */ - public async fromCliArgs(options: IntegrationTestsDiscoveryOptions = {}): Promise { - return this.discover(options); - } - - private async discover(options: IntegrationTestsDiscoveryOptions): Promise { - const patterns = options.testRegex ?? ['^integ\\..*\\.js$']; - + private async discover(options: IntegrationTestsDiscoveryOptions, ignoreUncompiledTypeScript: boolean = false): Promise { const files = await this.readTree(); - const integs = files.filter(fileName => patterns.some((p) => { - const regex = new RegExp(p); - return regex.test(fileName) || regex.test(path.basename(fileName)); - })); - - return this.request(integs, options); - } - - private request(files: string[], options: IntegrationTestsDiscoveryOptions): IntegTest[] { - const discoveredTests = files.map(fileName => new IntegTest({ - discoveryRoot: this.directory, - fileName, - appCommand: options.app, - })); + const testCases = Object.entries(options.testCases) + .flatMap(([appCommand, patterns]) => files + .filter(fileName => patterns.some((pattern) => { + const regex = new RegExp(pattern); + return regex.test(fileName) || regex.test(path.basename(fileName)); + })) + .map(fileName => new IntegTest({ + discoveryRoot: this.directory, + fileName, + appCommand, + })), + ); + + const discoveredTests = ignoreUncompiledTypeScript ? this.filterUncompiledTypeScript(testCases) : testCases; return this.filterTests(discoveredTests, options.tests, options.exclude); } + private filterUncompiledTypeScript(testCases: IntegTest[]): IntegTest[] { + const jsTestCases = testCases.filter(t => t.fileName.endsWith('.js')); + + return testCases + // Remove all TypeScript test cases (ending in .ts) + // for which a compiled version is present (same name, ending in .js) + .filter((tsCandidate) => { + if (!tsCandidate.fileName.endsWith('.ts')) { + return true; + } + return jsTestCases.findIndex(jsTest => jsTest.testName === tsCandidate.testName) === -1; + }); + } + private async readTree(): Promise { const ret = new Array(); diff --git a/packages/@aws-cdk/integ-runner/package.json b/packages/@aws-cdk/integ-runner/package.json index a68ebd429ee39..6aabd8a47d166 100644 --- a/packages/@aws-cdk/integ-runner/package.json +++ b/packages/@aws-cdk/integ-runner/package.json @@ -14,6 +14,7 @@ "awslint": "cdk-awslint", "pkglint": "pkglint -f", "test": "cdk-test", + "integ": "integ-runner", "watch": "cdk-watch", "build+test": "yarn build && yarn test", "build+test+package": "yarn build+test && yarn package", @@ -52,15 +53,19 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", - "@types/mock-fs": "^4.13.1", - "mock-fs": "^4.14.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/fs-extra": "^8.1.2", "@types/jest": "^27.5.2", + "@types/mock-fs": "^4.13.1", "@types/node": "^14.18.34", "@types/workerpool": "^6.1.0", "@types/yargs": "^15.0.14", - "jest": "^27.5.1" + "constructs": "^10.0.0", + "mock-fs": "^4.14.0", + "jest": "^27.5.1", + "ts-node": "^10.9.1" }, "dependencies": { "@aws-cdk/cloud-assembly-schema": "0.0.0", diff --git a/packages/@aws-cdk/integ-runner/test/cli.test.ts b/packages/@aws-cdk/integ-runner/test/cli.test.ts index 87bfabd0e7e78..8edc4b69d663a 100644 --- a/packages/@aws-cdk/integ-runner/test/cli.test.ts +++ b/packages/@aws-cdk/integ-runner/test/cli.test.ts @@ -4,14 +4,21 @@ import * as path from 'path'; import { main, parseCliArgs } from '../lib/cli'; let stdoutMock: jest.SpyInstance; +let stderrMock: jest.SpyInstance; beforeEach(() => { stdoutMock = jest.spyOn(process.stdout, 'write').mockImplementation(() => { return true; }); + stderrMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => { return true; }); }); afterEach(() => { + stdoutMock.mockReset(); + stderrMock.mockReset(); +}); +afterAll(() => { stdoutMock.mockRestore(); + stderrMock.mockRestore(); }); -describe('CLI', () => { +describe('Test discovery', () => { const currentCwd = process.cwd(); beforeAll(() => { process.chdir(path.join(__dirname, '..')); @@ -28,7 +35,7 @@ describe('CLI', () => { }); test('find by custom pattern', async () => { - await main(['--list', '--directory=test/test-data', '--test-regex="^xxxxx\.integ-test[12]\.js$"']); + await main(['--list', '--directory=test/test-data', '--language=javascript', '--test-regex="^xxxxx\.integ-test[12]\.js$"']); expect(stdoutMock.mock.calls).toEqual([[ [ @@ -40,7 +47,14 @@ describe('CLI', () => { }); test('list only shows explicitly provided tests', async () => { - await main(['xxxxx.integ-test1.js', 'xxxxx.integ-test2.js', '--list', '--directory=test/test-data', '--test-regex="^xxxxx\..*\.js$"']); + await main([ + 'xxxxx.integ-test1.js', + 'xxxxx.integ-test2.js', + '--list', + '--directory=test/test-data', + '--language=javascript', + '--test-regex="^xxxxx\..*\.js$"', + ]); expect(stdoutMock.mock.calls).toEqual([[ [ @@ -51,11 +65,57 @@ describe('CLI', () => { ]]); }); + test('find only TypeScript files', async () => { + await main(['--list', '--language', 'typescript', '--directory=test']); + + expect(stdoutMock.mock.calls).toEqual([[ + 'language-tests/integ.typescript-test.ts\n', + ]]); + }); + test('can run with no tests detected', async () => { await main(['whatever.js', '--directory=test/test-data']); expect(stdoutMock.mock.calls).toEqual([]); }); + + test('app and test-regex override default presets', async () => { + await main([ + '--list', + '--directory=test/test-data', + '--app="node {filePath}"', + '--test-regex="^xxxxx\.integ-test[12]\.js$"', + ]); + + expect(stdoutMock.mock.calls).toEqual([[ + [ + 'xxxxx.integ-test1.js', + 'xxxxx.integ-test2.js', + '', + ].join('\n'), + ]]); + }); + + + test('cannot use --test-regex by itself with more than one language preset', async () => { + await expect(() => main([ + '--list', + '--directory=test/test-data', + '--language=javascript', + '--language=typescript', + '--test-regex="^xxxxx\.integ-test[12]\.js$"', + ])).rejects.toThrowError('Only a single "--language" can be used with "--test-regex". Alternatively provide both "--app" and "--test-regex" to fully customize the configuration.'); + }); + + test('cannot use --app by itself with more than one language preset', async () => { + await expect(() => main([ + '--list', + '--directory=test/test-data', + '--language=javascript', + '--language=typescript', + '--app="node --prof {filePath}"', + ])).rejects.toThrowError('Only a single "--language" can be used with "--app". Alternatively provide both "--app" and "--test-regex" to fully customize the configuration.'); + }); }); describe('CLI config file', () => { diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/go.mod b/packages/@aws-cdk/integ-runner/test/language-tests/go.mod new file mode 100644 index 0000000000000..74af83f274eda --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/go.mod @@ -0,0 +1,23 @@ +module go-app + +go 1.18 + +require ( + github.com/aws/aws-cdk-go/awscdk/v2 v2.59.0 + github.com/aws/aws-cdk-go/awscdkintegtestsalpha/v2 v2.59.0-alpha.0 + github.com/aws/jsii-runtime-go v1.72.0 +) + +require ( + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/aws/constructs-go/constructs/v10 v10.1.189 // indirect + github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.30 // indirect + github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.1 // indirect + github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv5/v2 v2.0.38 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/yuin/goldmark v1.4.13 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/sys v0.2.0 // indirect + golang.org/x/tools v0.3.0 // indirect +) diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/go.sum b/packages/@aws-cdk/integ-runner/test/language-tests/go.sum new file mode 100644 index 0000000000000..3e2b98baaf7a1 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/go.sum @@ -0,0 +1,45 @@ +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/aws/aws-cdk-go/awscdk/v2 v2.59.0 h1:3s5RSXLarE7wgDYDidQ3IkCYg2XQ+15JahNEHeYEmQM= +github.com/aws/aws-cdk-go/awscdk/v2 v2.59.0/go.mod h1:C6aU5w7MABoOb+uFDW4DoQuL4bwkE56+AW4G6BYHO4k= +github.com/aws/aws-cdk-go/awscdkintegtestsalpha/v2 v2.59.0-alpha.0 h1:QEjZEQn5BbHmMsQLsS/BM4B7HsbvGP2HZofOxcWhc3o= +github.com/aws/aws-cdk-go/awscdkintegtestsalpha/v2 v2.59.0-alpha.0/go.mod h1:g/y63iF97+agyTfBwwMwnAm3KECkeXJPf+ogOjoF8C0= +github.com/aws/constructs-go/constructs/v10 v10.1.189 h1:PmI42w6gXXcXBMZgx6KO0ndHb5xGyUcndV39/nPzxMU= +github.com/aws/constructs-go/constructs/v10 v10.1.189/go.mod h1:Jx57tX1dGJwvmOiqKPIZEUDPY9xKRCd1zag5cg1u5aw= +github.com/aws/jsii-runtime-go v1.72.0 h1:5jMBCUu/qINj0hSt08gkyVyKRbrRBb9RV8ETGxTb+lo= +github.com/aws/jsii-runtime-go v1.72.0/go.mod h1:lExVNqTnEcbS/NMnuovDrHohgmPK3IsGdqvaYLA+N1s= +github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.30 h1:ryT4dP31QvXF7SO3mij6ir+8JBRaHyv1NSd2seUgpH4= +github.com/cdklabs/awscdk-asset-awscli-go/awscliv1/v2 v2.2.30/go.mod h1:tlNrUd0+vEvsVTA0C1lE0p2txRPrnK53kb44hCuxdDI= +github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.1 h1:l5N27aCCjAB5cgW5pI4/ujnasPL8hUcJ9KBxrKk6UiQ= +github.com/cdklabs/awscdk-asset-kubectl-go/kubectlv20/v2 v2.1.1/go.mod h1:CvFHBo0qcg8LUkJqIxQtP1rD/sNGv9bX3L2vHT2FUAo= +github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv5/v2 v2.0.38 h1:IgRq7F3odfHx5cnb+ejlMV+jGTmNOH7pJnSt8ownfvc= +github.com/cdklabs/awscdk-asset-node-proxy-agent-go/nodeproxyagentv5/v2 v2.0.38/go.mod h1:nh+vhRCLVkw4XOCpller0iKwUxQSs7PbRZnaQ4WoqSw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts new file mode 100644 index 0000000000000..76ab6f458b8c8 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts @@ -0,0 +1,36 @@ +/** + * This test cannot use service modules since it would introduce a circular dependency + * Using CfnResource etc. is a workaround to still test something useful + */ +import * as cdk from "@aws-cdk/core"; +import { ExpectedResult, IntegTest } from "@aws-cdk/integ-tests"; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, "TypeScriptStack"); +const queue = new cdk.CfnResource(stack, "Queue", { + type: "AWS::SQS::Queue", + properties: { + FifoQueue: true + } +}); + +const assertionStack = new cdk.Stack(app, "TypeScriptAssertions"); +assertionStack.addDependency(stack); + +const integ = new IntegTest(app, "TypeScript", { + testCases: [stack], + assertionStack, +}); + +integ.assertions + .awsApiCall("SQS", "getQueueAttributes", { + QueueUrl: stack.exportValue(queue.getAtt("QueueUrl", cdk.ResolutionTypeHint.STRING)), + AttributeNames: ["QueueArn"], + }) + .assertAtPath( + "Attributes.QueueArn", + ExpectedResult.stringLikeRegexp(".*\\.fifo$") + ); + +app.synth(); diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptAssertions.assets.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptAssertions.assets.json new file mode 100644 index 0000000000000..c28870f817d5a --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptAssertions.assets.json @@ -0,0 +1,32 @@ +{ + "version": "22.0.0", + "files": { + "278d42fa865f60954d898636503d0ee86a6689d73dc50eb912fac62def0ef6a4": { + "source": { + "path": "asset.278d42fa865f60954d898636503d0ee86a6689d73dc50eb912fac62def0ef6a4.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "278d42fa865f60954d898636503d0ee86a6689d73dc50eb912fac62def0ef6a4.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "bb131995f97d3bf2ccde6c8fd8d88a6b780ae97a9348985f50b9207d5e341b5c": { + "source": { + "path": "TypeScriptAssertions.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "bb131995f97d3bf2ccde6c8fd8d88a6b780ae97a9348985f50b9207d5e341b5c.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptAssertions.template.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptAssertions.template.json new file mode 100644 index 0000000000000..b0014a9825e61 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptAssertions.template.json @@ -0,0 +1,139 @@ +{ + "Resources": { + "AwsApiCallSQSgetQueueAttributes": { + "Type": "Custom::DeployAssert@SdkCallSQSgetQueueAttributes", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "SQS", + "api": "getQueueAttributes", + "expected": "{\"$StringLike\":\".*\\\\.fifo$\"}", + "actualPath": "Attributes.QueueArn", + "parameters": { + "QueueUrl": { + "Fn::ImportValue": "TypeScriptStack:ExportsOutputFnGetAttQueueQueueUrlA8D516BA" + }, + "AttributeNames": [ + "QueueArn" + ] + }, + "flattenResponse": "true", + "outputPaths": [ + "Attributes.QueueArn" + ], + "salt": "1672941547549" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "278d42fa865f60954d898636503d0ee86a6689d73dc50eb912fac62def0ef6a4.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallSQSgetQueueAttributes": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallSQSgetQueueAttributes", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptStack.assets.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptStack.assets.json new file mode 100644 index 0000000000000..f01cf3ee1f79f --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptStack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "22.0.0", + "files": { + "a28a0c4ed7dfaee5dec40a71393fffa16401094a480e0340d4713a9bb0677c36": { + "source": { + "path": "TypeScriptStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "a28a0c4ed7dfaee5dec40a71393fffa16401094a480e0340d4713a9bb0677c36.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptStack.template.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptStack.template.json new file mode 100644 index 0000000000000..c356249963a10 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/TypeScriptStack.template.json @@ -0,0 +1,57 @@ +{ + "Resources": { + "Queue": { + "Type": "AWS::SQS::Queue", + "Properties": { + "FifoQueue": true + } + } + }, + "Outputs": { + "ExportsOutputFnGetAttQueueQueueUrlA8D516BA": { + "Value": { + "Fn::GetAtt": [ + "Queue", + "QueueUrl" + ] + }, + "Export": { + "Name": "TypeScriptStack:ExportsOutputFnGetAttQueueQueueUrlA8D516BA" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/asset.278d42fa865f60954d898636503d0ee86a6689d73dc50eb912fac62def0ef6a4.bundle/index.js b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/asset.278d42fa865f60954d898636503d0ee86a6689d73dc50eb912fac62def0ef6a4.bundle/index.js new file mode 100644 index 0000000000000..2bf09d6726a42 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/asset.278d42fa865f60954d898636503d0ee86a6689d73dc50eb912fac62def0ef6a4.bundle/index.js @@ -0,0 +1,1052 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler, + isComplete: () => isComplete, + onTimeout: () => onTimeout +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// ../assertions/lib/matcher.ts +var Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } +}; +var MatchResult = class { + constructor(target) { + this.failuresHere = /* @__PURE__ */ new Map(); + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.innerMatchFailures = /* @__PURE__ */ new Map(); + this._hasFailed = false; + this._failCount = 0; + this._cost = 0; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + const failKey = failure.path.join("."); + let list = this.failuresHere.get(failKey); + if (!list) { + list = []; + this.failuresHere.set(failKey, list); + } + this._failCount += 1; + this._cost += failure.cost ?? 1; + list.push(failure); + this._hasFailed = true; + return this; + } + get isSuccess() { + return !this._hasFailed; + } + hasFailed() { + return this._hasFailed; + } + get failCount() { + return this._failCount; + } + get failCost() { + return this._cost; + } + compose(id, inner) { + if (inner.hasFailed()) { + this._hasFailed = true; + this._failCount += inner.failCount; + this._cost += inner._cost; + this.innerMatchFailures.set(id, inner); + } + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + const failures = new Array(); + debugger; + recurse(this, []); + return failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at /${r.path.join("/")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + function recurse(x, prefix) { + for (const fail of Array.from(x.failuresHere.values()).flat()) { + failures.push({ + matcher: fail.matcher, + message: fail.message, + path: [...prefix, ...fail.path] + }); + } + for (const [key, inner] of x.innerMatchFailures.entries()) { + recurse(inner, [...prefix, key]); + } + } + } + renderMismatch() { + if (!this.hasFailed()) { + return ""; + } + const parts = new Array(); + const indents = new Array(); + emitFailures(this, ""); + recurse(this); + return moveMarkersToFront(parts.join("").trimEnd()); + function emit(x) { + if (x === void 0) { + debugger; + } + parts.push(x.replace(/\n/g, ` +${indents.join("")}`)); + } + function emitFailures(r, path, scrapSet) { + for (const fail of r.failuresHere.get(path) ?? []) { + emit(`!! ${fail.message} +`); + } + scrapSet == null ? void 0 : scrapSet.delete(path); + } + function recurse(r) { + const remainingFailures = new Set(Array.from(r.failuresHere.keys()).filter((x) => x !== "")); + if (Array.isArray(r.target)) { + indents.push(" "); + emit("[\n"); + for (const [first, i] of enumFirst(range(r.target.length))) { + if (!first) { + emit(",\n"); + } + emitFailures(r, `${i}`, remainingFailures); + const innerMatcher = r.innerMatchFailures.get(`${i}`); + if (innerMatcher) { + emitFailures(innerMatcher, ""); + recurseComparingValues(innerMatcher, r.target[i]); + } else { + emit(renderAbridged(r.target[i])); + } + } + emitRemaining(); + indents.pop(); + emit("\n]"); + return; + } + if (r.target && typeof r.target === "object") { + indents.push(" "); + emit("{\n"); + const keys = Array.from(/* @__PURE__ */ new Set([ + ...Object.keys(r.target), + ...Array.from(remainingFailures) + ])).sort(); + for (const [first, key] of enumFirst(keys)) { + if (!first) { + emit(",\n"); + } + emitFailures(r, key, remainingFailures); + const innerMatcher = r.innerMatchFailures.get(key); + if (innerMatcher) { + emitFailures(innerMatcher, ""); + emit(`${jsonify(key)}: `); + recurseComparingValues(innerMatcher, r.target[key]); + } else { + emit(`${jsonify(key)}: `); + emit(renderAbridged(r.target[key])); + } + } + emitRemaining(); + indents.pop(); + emit("\n}"); + return; + } + emitRemaining(); + emit(jsonify(r.target)); + function emitRemaining() { + if (remainingFailures.size > 0) { + emit("\n"); + } + for (const key of remainingFailures) { + emitFailures(r, key); + } + } + } + function recurseComparingValues(inner, actualValue) { + if (inner.target === actualValue) { + return recurse(inner); + } + emit(renderAbridged(actualValue)); + emit(" <*> "); + recurse(inner); + } + function renderAbridged(x) { + if (Array.isArray(x)) { + switch (x.length) { + case 0: + return "[]"; + case 1: + return `[ ${renderAbridged(x[0])} ]`; + case 2: + if (x.every((e) => ["number", "boolean", "string"].includes(typeof e))) { + return `[ ${x.map(renderAbridged).join(", ")} ]`; + } + return "[ ... ]"; + default: + return "[ ... ]"; + } + } + if (x && typeof x === "object") { + const keys = Object.keys(x); + switch (keys.length) { + case 0: + return "{}"; + case 1: + return `{ ${JSON.stringify(keys[0])}: ${renderAbridged(x[keys[0]])} }`; + default: + return "{ ... }"; + } + } + return jsonify(x); + } + function jsonify(x) { + return JSON.stringify(x) ?? "undefined"; + } + function moveMarkersToFront(x) { + const re = /^(\s+)!!/gm; + return x.replace(re, (_, spaces) => `!!${spaces.substring(0, spaces.length - 2)}`); + } + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } +}; +function* range(n) { + for (let i = 0; i < n; i++) { + yield i; + } +} +function* enumFirst(xs) { + let first = true; + for (const x of xs) { + yield [first, x]; + first = false; + } +} + +// ../assertions/lib/private/matchers/absent.ts +var AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } +}; + +// ../assertions/lib/private/sorting.ts +function sortKeyComparator(keyFn) { + return (a, b) => { + const ak = keyFn(a); + const bk = keyFn(b); + for (let i = 0; i < ak.length && i < bk.length; i++) { + const av = ak[i]; + const bv = bk[i]; + let diff = 0; + if (typeof av === "number" && typeof bv === "number") { + diff = av - bv; + } else if (typeof av === "string" && typeof bv === "string") { + diff = av.localeCompare(bv); + } + if (diff !== 0) { + return diff; + } + } + return bk.length - ak.length; + }; +} + +// ../assertions/lib/private/sparse-matrix.ts +var SparseMatrix = class { + constructor() { + this.matrix = /* @__PURE__ */ new Map(); + } + get(row, col) { + var _a; + return (_a = this.matrix.get(row)) == null ? void 0 : _a.get(col); + } + row(row) { + var _a; + return Array.from(((_a = this.matrix.get(row)) == null ? void 0 : _a.entries()) ?? []); + } + set(row, col, value) { + let r = this.matrix.get(row); + if (!r) { + r = /* @__PURE__ */ new Map(); + this.matrix.set(row, r); + } + r.set(col, value); + } +}; + +// ../assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} + +// ../assertions/lib/match.ts +var Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } +}; +var LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } +}; +var ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + return this.subsequence ? this.testSubsequence(actual) : this.testFullArray(actual); + } + testFullArray(actual) { + const result = new MatchResult(actual); + let i = 0; + for (; i < this.pattern.length && i < actual.length; i++) { + const patternElement = this.pattern[i]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const innerResult = matcher.test(actual[i]); + result.compose(`${i}`, innerResult); + } + if (i < this.pattern.length) { + result.recordFailure({ + matcher: this, + message: `Not enough elements in array (expecting ${this.pattern.length}, got ${actual.length})`, + path: [`${i}`] + }); + } + if (i < actual.length) { + result.recordFailure({ + matcher: this, + message: `Too many elements in array (expecting ${this.pattern.length}, got ${actual.length})`, + path: [`${i}`] + }); + } + return result; + } + testSubsequence(actual) { + const result = new MatchResult(actual); + let patternIdx = 0; + let actualIdx = 0; + const matches = new SparseMatrix(); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (matcherName == "absent" || matcherName == "anyValue") { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + matches.set(patternIdx, actualIdx, innerResult); + actualIdx++; + if (innerResult.isSuccess) { + result.compose(`${actualIdx}`, innerResult); + patternIdx++; + } + } + if (patternIdx < this.pattern.length) { + for (let spi = 0; spi < patternIdx; spi++) { + const foundMatch = matches.row(spi).find(([, r]) => r.isSuccess); + if (!foundMatch) { + continue; + } + const [index] = foundMatch; + result.compose(`${index}`, new MatchResult(actual[index]).recordFailure({ + matcher: this, + message: `arrayWith pattern ${spi} matched here`, + path: [], + cost: 0 + })); + } + const failedMatches = matches.row(patternIdx); + failedMatches.sort(sortKeyComparator(([i, r]) => [r.failCost, i])); + if (failedMatches.length > 0) { + const [index, innerResult] = failedMatches[0]; + result.recordFailure({ + matcher: this, + message: `Could not match arrayWith pattern ${patternIdx}. This is the closest match`, + path: [`${index}`], + cost: 0 + }); + result.compose(`${index}`, innerResult); + } else { + result.recordFailure({ + matcher: this, + message: `Could not match arrayWith pattern ${patternIdx}. No more elements to try`, + path: [`${actual.length}`] + }); + } + } + return result; + } +}; +var ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [a], + message: `Unexpected key ${a}` + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [patternKey], + message: `Missing key '${patternKey}'` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(patternKey, inner); + } + return result; + } +}; +var SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + if (getType(actual) !== "string") { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + if (innerResult.hasFailed()) { + innerResult.recordFailure({ + matcher: this, + path: [], + message: "Encoded JSON value does not match" + }); + } + return innerResult; + } +}; +var NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } +}; +var AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } +}; +var StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } +}; + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var AWS = __toESM(require("aws-sdk")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + if ("stateMachineArn" in this.event.ResourceProperties) { + const req = { + stateMachineArn: this.event.ResourceProperties.stateMachineArn, + name: this.event.RequestId, + input: JSON.stringify(this.event) + }; + await this.startExecution(req); + return; + } else { + const response = await this.processEvent(this.event.ResourceProperties); + return response; + } + } catch (e) { + console.log(e); + throw e; + } finally { + clearTimeout(this.timeout); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + async startExecution(req) { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } finally { + clearTimeout(this.timeout); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + failed: true, + assertion: JSON.stringify({ + status: "fail", + message: matchResult.renderMismatch() + }) + }; + if (request2.failDeployment) { + throw new Error(result.assertion); + } + } else { + result = { + assertion: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return Match.arrayWith(v[nested]); + case "$ObjectLike": + return Match.objectLike(v[nested]); + case "$StringLike": + return Match.stringLikeRegexp(v[nested]); + case "$SerializedJson": + return Match.serializedJson(v[nested]); + default: + return v; + } + }); + if (Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return Match.exact(final.matcher); + } catch { + return Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + if (typeof childKey === "string") { + childKey = isJsonString(childKey); + } + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS2 = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS2.VERSION}`); + if (!Object.prototype.hasOwnProperty.call(AWS2, request2.service)) { + throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS2.VERSION}.`); + } + const service = new AWS2[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + let resp = respond; + if (request2.outputPaths) { + resp = filterKeys(flatData, request2.outputPaths); + } else if (request2.flattenResponse === "true") { + resp = flatData; + } + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +function filterKeys(object, searchStrings) { + return Object.entries(object).reduce((filteredObject, [key, value]) => { + for (const searchString of searchStrings) { + if (key.startsWith(`apiCallResponse.${searchString}`)) { + filteredObject[key] = value; + } + } + return filteredObject; + }, {}); +} +function isJsonString(value) { + try { + return JSON.parse(value); + } catch { + return value; + } +} + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + if (event.RequestType === "Delete") { + await provider.respond({ + status: "SUCCESS", + reason: "OK" + }); + return; + } + const result = await provider.handle(); + if ("stateMachineArn" in event.ResourceProperties) { + console.info('Found "stateMachineArn", waiter statemachine started'); + return; + } else if ("expected" in event.ResourceProperties) { + console.info('Found "expected", testing assertions'); + const actualPath = event.ResourceProperties.actualPath; + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + try { + const assertionResult = await assertion.handle(); + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + return; +} +async function onTimeout(timeoutEvent) { + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + const provider = createResourceHandler(isCompleteRequest, standardContext); + await provider.respond({ + status: "FAILED", + reason: "Operation timed out: " + JSON.stringify(isCompleteRequest) + }); +} +async function isComplete(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + const result = await provider.handleIsComplete(); + const actualPath = event.ResourceProperties.actualPath; + if (result) { + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + if ("expected" in event.ResourceProperties) { + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + const assertionResult = await assertion.handleIsComplete(); + if (!(assertionResult == null ? void 0 : assertionResult.failed)) { + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } else { + console.log(`Assertion Failed: ${JSON.stringify(assertionResult)}`); + throw new Error(JSON.stringify(event)); + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } else { + console.log("No result"); + throw new Error(JSON.stringify(event)); + } + return; + } catch (e) { + console.log(e); + throw new Error(JSON.stringify(event)); + } +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) { + return new AssertionHandler(event, context); + } else { + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +var standardContext = { + getRemainingTimeInMillis: () => 9e4 +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler, + isComplete, + onTimeout +}); diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/cdk.out b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/cdk.out new file mode 100644 index 0000000000000..145739f539580 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"22.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/integ.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/integ.json new file mode 100644 index 0000000000000..eb715a69af65d --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "22.0.0", + "testCases": { + "TypeScript/DefaultTest": { + "stacks": [ + "TypeScriptStack" + ], + "assertionStack": "TypeScriptAssertions", + "assertionStackName": "TypeScriptAssertions" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/manifest.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/manifest.json new file mode 100644 index 0000000000000..79dace8e5747c --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/manifest.json @@ -0,0 +1,142 @@ +{ + "version": "22.0.0", + "artifacts": { + "TypeScriptStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "TypeScriptStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "TypeScriptStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "TypeScriptStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/a28a0c4ed7dfaee5dec40a71393fffa16401094a480e0340d4713a9bb0677c36.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "TypeScriptStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "TypeScriptStack.assets" + ], + "metadata": { + "/TypeScriptStack/Queue": [ + { + "type": "aws:cdk:logicalId", + "data": "Queue" + } + ], + "/TypeScriptStack/Exports/Output{\"Fn::GetAtt\":[\"Queue\",\"QueueUrl\"]}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputFnGetAttQueueQueueUrlA8D516BA" + } + ], + "/TypeScriptStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/TypeScriptStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "TypeScriptStack" + }, + "TypeScriptAssertions.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "TypeScriptAssertions.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "TypeScriptAssertions": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "TypeScriptAssertions.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/bb131995f97d3bf2ccde6c8fd8d88a6b780ae97a9348985f50b9207d5e341b5c.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "TypeScriptAssertions.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "TypeScriptStack", + "TypeScriptAssertions.assets" + ], + "metadata": { + "/TypeScriptAssertions/AwsApiCallSQSgetQueueAttributes/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSgetQueueAttributes" + } + ], + "/TypeScriptAssertions/AwsApiCallSQSgetQueueAttributes/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallSQSgetQueueAttributes" + } + ], + "/TypeScriptAssertions/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/TypeScriptAssertions/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/TypeScriptAssertions/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/TypeScriptAssertions/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "TypeScriptAssertions" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/tree.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/tree.json new file mode 100644 index 0000000000000..4006b7c316ce1 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ.typescript-test.ts.snapshot/tree.json @@ -0,0 +1,215 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "TypeScriptStack": { + "id": "TypeScriptStack", + "path": "TypeScriptStack", + "children": { + "Queue": { + "id": "Queue", + "path": "TypeScriptStack/Queue", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "TypeScriptStack/Exports", + "children": { + "Output{\"Fn::GetAtt\":[\"Queue\",\"QueueUrl\"]}": { + "id": "Output{\"Fn::GetAtt\":[\"Queue\",\"QueueUrl\"]}", + "path": "TypeScriptStack/Exports/Output{\"Fn::GetAtt\":[\"Queue\",\"QueueUrl\"]}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "TypeScriptStack/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "TypeScriptStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "TypeScriptAssertions": { + "id": "TypeScriptAssertions", + "path": "TypeScriptAssertions", + "children": { + "AwsApiCallSQSgetQueueAttributes": { + "id": "AwsApiCallSQSgetQueueAttributes", + "path": "TypeScriptAssertions/AwsApiCallSQSgetQueueAttributes", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "TypeScriptAssertions/AwsApiCallSQSgetQueueAttributes/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "TypeScriptAssertions/AwsApiCallSQSgetQueueAttributes/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AssertionsProvider", + "version": "0.0.0" + } + }, + "Default": { + "id": "Default", + "path": "TypeScriptAssertions/AwsApiCallSQSgetQueueAttributes/Default", + "children": { + "Default": { + "id": "Default", + "path": "TypeScriptAssertions/AwsApiCallSQSgetQueueAttributes/Default/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "TypeScriptAssertions/AwsApiCallSQSgetQueueAttributes/AssertionResults", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.AwsApiCall", + "version": "0.0.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "TypeScriptAssertions/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "TypeScriptAssertions/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "TypeScriptAssertions/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "TypeScriptAssertions/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "TypeScriptAssertions/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "TypeScriptAssertions/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "TypeScript": { + "id": "TypeScript", + "path": "TypeScript", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "TypeScript/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "TypeScript/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go new file mode 100644 index 0000000000000..3bf5e5313957c --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go @@ -0,0 +1,43 @@ +package main + +import ( + "github.com/aws/aws-cdk-go/awscdk/v2" + "github.com/aws/aws-cdk-go/awscdk/v2/awssqs" + "github.com/aws/aws-cdk-go/awscdkintegtestsalpha/v2" + "github.com/aws/jsii-runtime-go" +) + +func main() { + defer jsii.Close() + + app := awscdk.NewApp(nil) + stack := awscdk.NewStack(app, jsii.String("GoLangStack"), nil) + + queue := awssqs.NewQueue(stack, jsii.String("Queue"), &awssqs.QueueProps{ + Fifo: jsii.Bool(true), + }) + + integ := awscdkintegtestsalpha.NewIntegTest(app, jsii.String("GoLang"), &awscdkintegtestsalpha.IntegTestProps{ + TestCases: &[]awscdk.Stack{ + stack, + }, + }) + + integ.Assertions().AwsApiCall( + jsii.String("SQS"), + jsii.String("getQueueAttributes"), + struct { + QueueUrl *string + AttributeNames []string + }{ + QueueUrl: queue.QueueUrl(), + AttributeNames: []string{"QueueArn"}, + }, + &[]*string{}, + ).AssertAtPath( + jsii.String("Attributes.QueueArn"), + awscdkintegtestsalpha.ExpectedResult_StringLikeRegexp(jsii.String(".*\\.fifo$")), + ) + + app.Synth(nil) +} diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangDefaultTestDeployAssertA629E612.assets.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangDefaultTestDeployAssertA629E612.assets.json new file mode 100644 index 0000000000000..45b4d060e0695 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangDefaultTestDeployAssertA629E612.assets.json @@ -0,0 +1,32 @@ +{ + "version": "22.0.0", + "files": { + "8f0c571812657f9654505faac7869bb410c192d7feba2ed7246fcb86f64e36e0": { + "source": { + "path": "asset.8f0c571812657f9654505faac7869bb410c192d7feba2ed7246fcb86f64e36e0.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "8f0c571812657f9654505faac7869bb410c192d7feba2ed7246fcb86f64e36e0.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "f97830f7d4c4e6fff4f1baaeb87c6b6aa3e08f6c008c99b764e519e4d4cd309a": { + "source": { + "path": "GoLangDefaultTestDeployAssertA629E612.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "f97830f7d4c4e6fff4f1baaeb87c6b6aa3e08f6c008c99b764e519e4d4cd309a.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangDefaultTestDeployAssertA629E612.template.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangDefaultTestDeployAssertA629E612.template.json new file mode 100644 index 0000000000000..16a6514b4694b --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangDefaultTestDeployAssertA629E612.template.json @@ -0,0 +1,139 @@ +{ + "Resources": { + "AwsApiCallSQSgetQueueAttributes": { + "Type": "Custom::DeployAssert@SdkCallSQSgetQueueAttributes", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "SQS", + "api": "getQueueAttributes", + "expected": "{\"$StringLike\":\".*\\\\.fifo$\"}", + "actualPath": "Attributes.QueueArn", + "parameters": { + "QueueUrl": { + "Fn::ImportValue": "GoLangStack:ExportsOutputRefQueue4A7E3555425E8BD3" + }, + "AttributeNames": [ + "QueueArn" + ] + }, + "flattenResponse": "true", + "outputPaths": [ + "Attributes.QueueArn" + ], + "salt": "1672934999538" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "8f0c571812657f9654505faac7869bb410c192d7feba2ed7246fcb86f64e36e0.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallSQSgetQueueAttributes": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallSQSgetQueueAttributes", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangStack.assets.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangStack.assets.json new file mode 100644 index 0000000000000..77b5067009441 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangStack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "22.0.0", + "files": { + "cdf94bfd1a3da2b2708c1e970f501fd2aec8dffea45b036ee1f42e20f5081015": { + "source": { + "path": "GoLangStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "cdf94bfd1a3da2b2708c1e970f501fd2aec8dffea45b036ee1f42e20f5081015.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangStack.template.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangStack.template.json new file mode 100644 index 0000000000000..c16a95c2748fa --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/GoLangStack.template.json @@ -0,0 +1,56 @@ +{ + "Resources": { + "Queue4A7E3555": { + "Type": "AWS::SQS::Queue", + "Properties": { + "FifoQueue": true + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "ExportsOutputRefQueue4A7E3555425E8BD3": { + "Value": { + "Ref": "Queue4A7E3555" + }, + "Export": { + "Name": "GoLangStack:ExportsOutputRefQueue4A7E3555425E8BD3" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/asset.8f0c571812657f9654505faac7869bb410c192d7feba2ed7246fcb86f64e36e0.bundle/index.js b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/asset.8f0c571812657f9654505faac7869bb410c192d7feba2ed7246fcb86f64e36e0.bundle/index.js new file mode 100644 index 0000000000000..555b7b3a5057a --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/asset.8f0c571812657f9654505faac7869bb410c192d7feba2ed7246fcb86f64e36e0.bundle/index.js @@ -0,0 +1,1128 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// ../../aws-cdk-lib/assertions/lib/matcher.ts +var matcher_exports = {}; +__export(matcher_exports, { + MatchResult: () => MatchResult, + Matcher: () => Matcher +}); +function* range(n) { + for (let i = 0; i < n; i++) { + yield i; + } +} +function* enumFirst(xs) { + let first = true; + for (const x of xs) { + yield [first, x]; + first = false; + } +} +var Matcher, MatchResult; +var init_matcher = __esm({ + "../../aws-cdk-lib/assertions/lib/matcher.ts"() { + "use strict"; + Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } + }; + MatchResult = class { + constructor(target) { + this.failuresHere = /* @__PURE__ */ new Map(); + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.innerMatchFailures = /* @__PURE__ */ new Map(); + this._hasFailed = false; + this._failCount = 0; + this._cost = 0; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + const failKey = failure.path.join("."); + let list = this.failuresHere.get(failKey); + if (!list) { + list = []; + this.failuresHere.set(failKey, list); + } + this._failCount += 1; + this._cost += failure.cost ?? 1; + list.push(failure); + this._hasFailed = true; + return this; + } + get isSuccess() { + return !this._hasFailed; + } + hasFailed() { + return this._hasFailed; + } + get failCount() { + return this._failCount; + } + get failCost() { + return this._cost; + } + compose(id, inner) { + if (inner.hasFailed()) { + this._hasFailed = true; + this._failCount += inner.failCount; + this._cost += inner._cost; + this.innerMatchFailures.set(id, inner); + } + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + const failures = new Array(); + debugger; + recurse(this, []); + return failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at /${r.path.join("/")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + function recurse(x, prefix) { + for (const fail of Array.from(x.failuresHere.values()).flat()) { + failures.push({ + matcher: fail.matcher, + message: fail.message, + path: [...prefix, ...fail.path] + }); + } + for (const [key, inner] of x.innerMatchFailures.entries()) { + recurse(inner, [...prefix, key]); + } + } + } + renderMismatch() { + if (!this.hasFailed()) { + return ""; + } + const parts = new Array(); + const indents = new Array(); + emitFailures(this, ""); + recurse(this); + return moveMarkersToFront(parts.join("").trimEnd()); + function emit(x) { + if (x === void 0) { + debugger; + } + parts.push(x.replace(/\n/g, ` +${indents.join("")}`)); + } + function emitFailures(r, path, scrapSet) { + for (const fail of r.failuresHere.get(path) ?? []) { + emit(`!! ${fail.message} +`); + } + scrapSet == null ? void 0 : scrapSet.delete(path); + } + function recurse(r) { + const remainingFailures = new Set(Array.from(r.failuresHere.keys()).filter((x) => x !== "")); + if (Array.isArray(r.target)) { + indents.push(" "); + emit("[\n"); + for (const [first, i] of enumFirst(range(r.target.length))) { + if (!first) { + emit(",\n"); + } + emitFailures(r, `${i}`, remainingFailures); + const innerMatcher = r.innerMatchFailures.get(`${i}`); + if (innerMatcher) { + emitFailures(innerMatcher, ""); + recurseComparingValues(innerMatcher, r.target[i]); + } else { + emit(renderAbridged(r.target[i])); + } + } + emitRemaining(); + indents.pop(); + emit("\n]"); + return; + } + if (r.target && typeof r.target === "object") { + indents.push(" "); + emit("{\n"); + const keys = Array.from(/* @__PURE__ */ new Set([ + ...Object.keys(r.target), + ...Array.from(remainingFailures) + ])).sort(); + for (const [first, key] of enumFirst(keys)) { + if (!first) { + emit(",\n"); + } + emitFailures(r, key, remainingFailures); + const innerMatcher = r.innerMatchFailures.get(key); + if (innerMatcher) { + emitFailures(innerMatcher, ""); + emit(`${jsonify(key)}: `); + recurseComparingValues(innerMatcher, r.target[key]); + } else { + emit(`${jsonify(key)}: `); + emit(renderAbridged(r.target[key])); + } + } + emitRemaining(); + indents.pop(); + emit("\n}"); + return; + } + emitRemaining(); + emit(jsonify(r.target)); + function emitRemaining() { + if (remainingFailures.size > 0) { + emit("\n"); + } + for (const key of remainingFailures) { + emitFailures(r, key); + } + } + } + function recurseComparingValues(inner, actualValue) { + if (inner.target === actualValue) { + return recurse(inner); + } + emit(renderAbridged(actualValue)); + emit(" <*> "); + recurse(inner); + } + function renderAbridged(x) { + if (Array.isArray(x)) { + switch (x.length) { + case 0: + return "[]"; + case 1: + return `[ ${renderAbridged(x[0])} ]`; + case 2: + if (x.every((e) => ["number", "boolean", "string"].includes(typeof e))) { + return `[ ${x.map(renderAbridged).join(", ")} ]`; + } + return "[ ... ]"; + default: + return "[ ... ]"; + } + } + if (x && typeof x === "object") { + const keys = Object.keys(x); + switch (keys.length) { + case 0: + return "{}"; + case 1: + return `{ ${JSON.stringify(keys[0])}: ${renderAbridged(x[keys[0]])} }`; + default: + return "{ ... }"; + } + } + return jsonify(x); + } + function jsonify(x) { + return JSON.stringify(x) ?? "undefined"; + } + function moveMarkersToFront(x) { + const re = /^(\s+)!!/gm; + return x.replace(re, (_, spaces) => `!!${spaces.substring(0, spaces.length - 2)}`); + } + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/matchers/absent.ts +var AbsentMatch; +var init_absent = __esm({ + "../../aws-cdk-lib/assertions/lib/private/matchers/absent.ts"() { + "use strict"; + init_matcher(); + AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/sorting.ts +function sortKeyComparator(keyFn) { + return (a, b) => { + const ak = keyFn(a); + const bk = keyFn(b); + for (let i = 0; i < ak.length && i < bk.length; i++) { + const av = ak[i]; + const bv = bk[i]; + let diff = 0; + if (typeof av === "number" && typeof bv === "number") { + diff = av - bv; + } else if (typeof av === "string" && typeof bv === "string") { + diff = av.localeCompare(bv); + } + if (diff !== 0) { + return diff; + } + } + return bk.length - ak.length; + }; +} +var init_sorting = __esm({ + "../../aws-cdk-lib/assertions/lib/private/sorting.ts"() { + "use strict"; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/sparse-matrix.ts +var SparseMatrix; +var init_sparse_matrix = __esm({ + "../../aws-cdk-lib/assertions/lib/private/sparse-matrix.ts"() { + "use strict"; + SparseMatrix = class { + constructor() { + this.matrix = /* @__PURE__ */ new Map(); + } + get(row, col) { + var _a; + return (_a = this.matrix.get(row)) == null ? void 0 : _a.get(col); + } + row(row) { + var _a; + return Array.from(((_a = this.matrix.get(row)) == null ? void 0 : _a.entries()) ?? []); + } + set(row, col, value) { + let r = this.matrix.get(row); + if (!r) { + r = /* @__PURE__ */ new Map(); + this.matrix.set(row, r); + } + r.set(col, value); + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} +var init_type = __esm({ + "../../aws-cdk-lib/assertions/lib/private/type.ts"() { + "use strict"; + } +}); + +// ../../aws-cdk-lib/assertions/lib/match.ts +var match_exports = {}; +__export(match_exports, { + Match: () => Match +}); +var Match, LiteralMatch, ArrayMatch, ObjectMatch, SerializedJson, NotMatch, AnyMatch, StringLikeRegexpMatch; +var init_match = __esm({ + "../../aws-cdk-lib/assertions/lib/match.ts"() { + "use strict"; + init_matcher(); + init_absent(); + init_sorting(); + init_sparse_matrix(); + init_type(); + Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } + }; + LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } + }; + ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + return this.subsequence ? this.testSubsequence(actual) : this.testFullArray(actual); + } + testFullArray(actual) { + const result = new MatchResult(actual); + let i = 0; + for (; i < this.pattern.length && i < actual.length; i++) { + const patternElement = this.pattern[i]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const innerResult = matcher.test(actual[i]); + result.compose(`${i}`, innerResult); + } + if (i < this.pattern.length) { + result.recordFailure({ + matcher: this, + message: `Not enough elements in array (expecting ${this.pattern.length}, got ${actual.length})`, + path: [`${i}`] + }); + } + if (i < actual.length) { + result.recordFailure({ + matcher: this, + message: `Too many elements in array (expecting ${this.pattern.length}, got ${actual.length})`, + path: [`${i}`] + }); + } + return result; + } + testSubsequence(actual) { + const result = new MatchResult(actual); + let patternIdx = 0; + let actualIdx = 0; + const matches = new SparseMatrix(); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (matcherName == "absent" || matcherName == "anyValue") { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + matches.set(patternIdx, actualIdx, innerResult); + actualIdx++; + if (innerResult.isSuccess) { + result.compose(`${actualIdx}`, innerResult); + patternIdx++; + } + } + if (patternIdx < this.pattern.length) { + for (let spi = 0; spi < patternIdx; spi++) { + const foundMatch = matches.row(spi).find(([, r]) => r.isSuccess); + if (!foundMatch) { + continue; + } + const [index] = foundMatch; + result.compose(`${index}`, new MatchResult(actual[index]).recordFailure({ + matcher: this, + message: `arrayWith pattern ${spi} matched here`, + path: [], + cost: 0 + })); + } + const failedMatches = matches.row(patternIdx); + failedMatches.sort(sortKeyComparator(([i, r]) => [r.failCost, i])); + if (failedMatches.length > 0) { + const [index, innerResult] = failedMatches[0]; + result.recordFailure({ + matcher: this, + message: `Could not match arrayWith pattern ${patternIdx}. This is the closest match`, + path: [`${index}`], + cost: 0 + }); + result.compose(`${index}`, innerResult); + } else { + result.recordFailure({ + matcher: this, + message: `Could not match arrayWith pattern ${patternIdx}. No more elements to try`, + path: [`${actual.length}`] + }); + } + } + return result; + } + }; + ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [a], + message: `Unexpected key ${a}` + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [patternKey], + message: `Missing key '${patternKey}'` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(patternKey, inner); + } + return result; + } + }; + SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + if (getType(actual) !== "string") { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + if (innerResult.hasFailed()) { + innerResult.recordFailure({ + matcher: this, + path: [], + message: "Encoded JSON value does not match" + }); + } + return innerResult; + } + }; + NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } + }; + AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } + }; + StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/helpers-internal/index.js +var require_helpers_internal = __commonJS({ + "../../aws-cdk-lib/assertions/lib/helpers-internal/index.js"(exports) { + "use strict"; + var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) { + k2 === void 0 && (k2 = k), Object.defineProperty(o, k2, { enumerable: true, get: function() { + return m[k]; + } }); + } : function(o, m, k, k2) { + k2 === void 0 && (k2 = k), o[k2] = m[k]; + }); + var __exportStar = exports && exports.__exportStar || function(m, exports2) { + for (var p in m) + p !== "default" && !exports2.hasOwnProperty(p) && __createBinding(exports2, m, p); + }; + Object.defineProperty(exports, "__esModule", { value: true }), __exportStar((init_match(), __toCommonJS(match_exports)), exports), __exportStar((init_matcher(), __toCommonJS(matcher_exports)), exports); + } +}); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler, + isComplete: () => isComplete, + onTimeout: () => onTimeout +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// lib/assertions/providers/lambda-handler/assertion.ts +var import_helpers_internal = __toESM(require_helpers_internal()); + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var AWS = __toESM(require("aws-sdk")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + if ("stateMachineArn" in this.event.ResourceProperties) { + const req = { + stateMachineArn: this.event.ResourceProperties.stateMachineArn, + name: this.event.RequestId, + input: JSON.stringify(this.event) + }; + await this.startExecution(req); + return; + } else { + const response = await this.processEvent(this.event.ResourceProperties); + return response; + } + } catch (e) { + console.log(e); + throw e; + } finally { + clearTimeout(this.timeout); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + async startExecution(req) { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } finally { + clearTimeout(this.timeout); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + failed: true, + assertion: JSON.stringify({ + status: "fail", + message: matchResult.renderMismatch() + }) + }; + if (request2.failDeployment) { + throw new Error(result.assertion); + } + } else { + result = { + assertion: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return import_helpers_internal.Match.arrayWith(v[nested]); + case "$ObjectLike": + return import_helpers_internal.Match.objectLike(v[nested]); + case "$StringLike": + return import_helpers_internal.Match.stringLikeRegexp(v[nested]); + case "$SerializedJson": + return import_helpers_internal.Match.serializedJson(v[nested]); + default: + return v; + } + }); + if (import_helpers_internal.Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return import_helpers_internal.Match.exact(final.matcher); + } catch { + return import_helpers_internal.Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + if (typeof childKey === "string") { + childKey = isJsonString(childKey); + } + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS2 = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS2.VERSION}`); + if (!Object.prototype.hasOwnProperty.call(AWS2, request2.service)) { + throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS2.VERSION}.`); + } + const service = new AWS2[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + let resp = respond; + if (request2.outputPaths) { + resp = filterKeys(flatData, request2.outputPaths); + } else if (request2.flattenResponse === "true") { + resp = flatData; + } + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +function filterKeys(object, searchStrings) { + return Object.entries(object).reduce((filteredObject, [key, value]) => { + for (const searchString of searchStrings) { + if (key.startsWith(`apiCallResponse.${searchString}`)) { + filteredObject[key] = value; + } + } + return filteredObject; + }, {}); +} +function isJsonString(value) { + try { + return JSON.parse(value); + } catch { + return value; + } +} + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + if (event.RequestType === "Delete") { + await provider.respond({ + status: "SUCCESS", + reason: "OK" + }); + return; + } + const result = await provider.handle(); + if ("stateMachineArn" in event.ResourceProperties) { + console.info('Found "stateMachineArn", waiter statemachine started'); + return; + } else if ("expected" in event.ResourceProperties) { + console.info('Found "expected", testing assertions'); + const actualPath = event.ResourceProperties.actualPath; + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + try { + const assertionResult = await assertion.handle(); + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + return; +} +async function onTimeout(timeoutEvent) { + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + const provider = createResourceHandler(isCompleteRequest, standardContext); + await provider.respond({ + status: "FAILED", + reason: "Operation timed out: " + JSON.stringify(isCompleteRequest) + }); +} +async function isComplete(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + const result = await provider.handleIsComplete(); + const actualPath = event.ResourceProperties.actualPath; + if (result) { + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + if ("expected" in event.ResourceProperties) { + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + const assertionResult = await assertion.handleIsComplete(); + if (!(assertionResult == null ? void 0 : assertionResult.failed)) { + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } else { + console.log(`Assertion Failed: ${JSON.stringify(assertionResult)}`); + throw new Error(JSON.stringify(event)); + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } else { + console.log("No result"); + throw new Error(JSON.stringify(event)); + } + return; + } catch (e) { + console.log(e); + throw new Error(JSON.stringify(event)); + } +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) { + return new AssertionHandler(event, context); + } else { + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +var standardContext = { + getRemainingTimeInMillis: () => 9e4 +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler, + isComplete, + onTimeout +}); diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/cdk.out b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/cdk.out new file mode 100644 index 0000000000000..145739f539580 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"22.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/integ.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/integ.json new file mode 100644 index 0000000000000..300810713fbde --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "22.0.0", + "testCases": { + "GoLang/DefaultTest": { + "stacks": [ + "GoLangStack" + ], + "assertionStack": "GoLang/DefaultTest/DeployAssert", + "assertionStackName": "GoLangDefaultTestDeployAssertA629E612" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/manifest.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/manifest.json new file mode 100644 index 0000000000000..04cfcc4959c15 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/manifest.json @@ -0,0 +1,142 @@ +{ + "version": "22.0.0", + "artifacts": { + "GoLangStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "GoLangStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "GoLangStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "GoLangStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/cdf94bfd1a3da2b2708c1e970f501fd2aec8dffea45b036ee1f42e20f5081015.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "GoLangStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "GoLangStack.assets" + ], + "metadata": { + "/GoLangStack/Queue/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Queue4A7E3555" + } + ], + "/GoLangStack/Exports/Output{\"Ref\":\"Queue4A7E3555\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefQueue4A7E3555425E8BD3" + } + ], + "/GoLangStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/GoLangStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "GoLangStack" + }, + "GoLangDefaultTestDeployAssertA629E612.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "GoLangDefaultTestDeployAssertA629E612.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "GoLangDefaultTestDeployAssertA629E612": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "GoLangDefaultTestDeployAssertA629E612.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/f97830f7d4c4e6fff4f1baaeb87c6b6aa3e08f6c008c99b764e519e4d4cd309a.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "GoLangDefaultTestDeployAssertA629E612.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "GoLangStack", + "GoLangDefaultTestDeployAssertA629E612.assets" + ], + "metadata": { + "/GoLang/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSgetQueueAttributes" + } + ], + "/GoLang/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallSQSgetQueueAttributes" + } + ], + "/GoLang/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/GoLang/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/GoLang/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/GoLang/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "GoLang/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/tree.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/tree.json new file mode 100644 index 0000000000000..c8f29007af4ad --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_go-test.go.snapshot/tree.json @@ -0,0 +1,231 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "GoLangStack": { + "id": "GoLangStack", + "path": "GoLangStack", + "children": { + "Queue": { + "id": "Queue", + "path": "GoLangStack/Queue", + "children": { + "Resource": { + "id": "Resource", + "path": "GoLangStack/Queue/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": { + "fifoQueue": true + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.CfnQueue", + "version": "2.59.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.Queue", + "version": "2.59.0" + } + }, + "Exports": { + "id": "Exports", + "path": "GoLangStack/Exports", + "children": { + "Output{\"Ref\":\"Queue4A7E3555\"}": { + "id": "Output{\"Ref\":\"Queue4A7E3555\"}", + "path": "GoLangStack/Exports/Output{\"Ref\":\"Queue4A7E3555\"}", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "2.59.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "GoLangStack/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "2.59.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "GoLangStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "2.59.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "2.59.0" + } + }, + "GoLang": { + "id": "GoLang", + "path": "GoLang", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "GoLang/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "GoLang/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "GoLang/DefaultTest/DeployAssert", + "children": { + "AwsApiCallSQSgetQueueAttributes": { + "id": "AwsApiCallSQSgetQueueAttributes", + "path": "GoLang/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "GoLang/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "GoLang/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.AssertionsProvider", + "version": "2.59.0-alpha.0" + } + }, + "Default": { + "id": "Default", + "path": "GoLang/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/Default", + "children": { + "Default": { + "id": "Default", + "path": "GoLang/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/Default/Default", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "2.59.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.CustomResource", + "version": "2.59.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "GoLang/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/AssertionResults", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "2.59.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.AwsApiCall", + "version": "2.59.0-alpha.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "GoLang/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "GoLang/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "2.59.0" + } + }, + "Role": { + "id": "Role", + "path": "GoLang/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "2.59.0" + } + }, + "Handler": { + "id": "Handler", + "path": "GoLang/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "2.59.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "GoLang/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "2.59.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "GoLang/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "2.59.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "2.59.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "2.59.0-alpha.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "2.59.0-alpha.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.189" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "2.59.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py new file mode 100644 index 0000000000000..4e3365cf7f578 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py @@ -0,0 +1,17 @@ +import aws_cdk as cdk +import aws_cdk.aws_sqs as sqs +from aws_cdk.integ_tests_alpha import IntegTest, ExpectedResult + +app = cdk.App() +stack = cdk.Stack(app, 'PythonStack') + +queue = sqs.Queue(stack, 'Queue', fifo=True) + +integ = IntegTest(app, "Python", test_cases=[stack]) + +integ.assertions.aws_api_call('SQS', 'getQueueAttributes', { + "QueueUrl": queue.queue_url, + "AttributeNames": ["QueueArn"] +}).assert_at_path('Attributes.QueueArn', ExpectedResult.string_like_regexp(".*\\.fifo$")) + +app.synth() diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonDefaultTestDeployAssert82E20B88.assets.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonDefaultTestDeployAssert82E20B88.assets.json new file mode 100644 index 0000000000000..16240eb9fd3ed --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonDefaultTestDeployAssert82E20B88.assets.json @@ -0,0 +1,32 @@ +{ + "version": "22.0.0", + "files": { + "c519532b68b7cbc9ca6fcad1d1b608452f0cba5889291ec0aecc40c5b48ec1b3": { + "source": { + "path": "asset.c519532b68b7cbc9ca6fcad1d1b608452f0cba5889291ec0aecc40c5b48ec1b3.bundle", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "c519532b68b7cbc9ca6fcad1d1b608452f0cba5889291ec0aecc40c5b48ec1b3.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "4ba4a227fd62b83817c85595e9fcb22091b0c088a429a8eea7bd06560b18e2d2": { + "source": { + "path": "PythonDefaultTestDeployAssert82E20B88.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "4ba4a227fd62b83817c85595e9fcb22091b0c088a429a8eea7bd06560b18e2d2.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonDefaultTestDeployAssert82E20B88.template.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonDefaultTestDeployAssert82E20B88.template.json new file mode 100644 index 0000000000000..01e247643cb21 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonDefaultTestDeployAssert82E20B88.template.json @@ -0,0 +1,139 @@ +{ + "Resources": { + "AwsApiCallSQSgetQueueAttributes": { + "Type": "Custom::DeployAssert@SdkCallSQSgetQueueAttributes", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F", + "Arn" + ] + }, + "service": "SQS", + "api": "getQueueAttributes", + "expected": "{\"$StringLike\":\".*\\\\.fifo$\"}", + "actualPath": "Attributes.QueueArn", + "parameters": { + "QueueUrl": { + "Fn::ImportValue": "PythonStack:ExportsOutputRefQueue4A7E3555425E8BD3" + }, + "AttributeNames": [ + "QueueArn" + ] + }, + "flattenResponse": "true", + "outputPaths": [ + "Attributes.QueueArn" + ], + "salt": "1672925608158" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sqs:GetQueueAttributes" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ] + } + } + ] + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Runtime": "nodejs14.x", + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "c519532b68b7cbc9ca6fcad1d1b608452f0cba5889291ec0aecc40c5b48ec1b3.zip" + }, + "Timeout": 120, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73", + "Arn" + ] + } + } + } + }, + "Outputs": { + "AssertionResultsAwsApiCallSQSgetQueueAttributes": { + "Value": { + "Fn::GetAtt": [ + "AwsApiCallSQSgetQueueAttributes", + "assertion" + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonStack.assets.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonStack.assets.json new file mode 100644 index 0000000000000..3a7478edb22ca --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonStack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "22.0.0", + "files": { + "f73d32c184c3aacfc0baa0a075665e95c9a8065be2690b7c556d2bd78b5d796b": { + "source": { + "path": "PythonStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "f73d32c184c3aacfc0baa0a075665e95c9a8065be2690b7c556d2bd78b5d796b.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonStack.template.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonStack.template.json new file mode 100644 index 0000000000000..2fbdbd5bd61b3 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/PythonStack.template.json @@ -0,0 +1,56 @@ +{ + "Resources": { + "Queue4A7E3555": { + "Type": "AWS::SQS::Queue", + "Properties": { + "FifoQueue": true + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "ExportsOutputRefQueue4A7E3555425E8BD3": { + "Value": { + "Ref": "Queue4A7E3555" + }, + "Export": { + "Name": "PythonStack:ExportsOutputRefQueue4A7E3555425E8BD3" + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/asset.c519532b68b7cbc9ca6fcad1d1b608452f0cba5889291ec0aecc40c5b48ec1b3.bundle/index.js b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/asset.c519532b68b7cbc9ca6fcad1d1b608452f0cba5889291ec0aecc40c5b48ec1b3.bundle/index.js new file mode 100644 index 0000000000000..2a8ccdbc6cc8c --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/asset.c519532b68b7cbc9ca6fcad1d1b608452f0cba5889291ec0aecc40c5b48ec1b3.bundle/index.js @@ -0,0 +1,846 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// ../../aws-cdk-lib/assertions/lib/matcher.ts +var matcher_exports = {}; +__export(matcher_exports, { + MatchResult: () => MatchResult, + Matcher: () => Matcher +}); +var Matcher, MatchResult; +var init_matcher = __esm({ + "../../aws-cdk-lib/assertions/lib/matcher.ts"() { + "use strict"; + Matcher = class { + static isMatcher(x) { + return x && x instanceof Matcher; + } + }; + MatchResult = class { + constructor(target) { + this.failures = []; + this.captures = /* @__PURE__ */ new Map(); + this.finalized = false; + this.target = target; + } + push(matcher, path, message) { + return this.recordFailure({ matcher, path, message }); + } + recordFailure(failure) { + this.failures.push(failure); + return this; + } + hasFailed() { + return this.failures.length !== 0; + } + get failCount() { + return this.failures.length; + } + compose(id, inner) { + const innerF = inner.failures; + this.failures.push(...innerF.map((f) => { + return { path: [id, ...f.path], message: f.message, matcher: f.matcher }; + })); + inner.captures.forEach((vals, capture) => { + vals.forEach((value) => this.recordCapture({ capture, value })); + }); + return this; + } + finished() { + if (this.finalized) { + return this; + } + if (this.failCount === 0) { + this.captures.forEach((vals, cap) => cap._captured.push(...vals)); + } + this.finalized = true; + return this; + } + toHumanStrings() { + return this.failures.map((r) => { + const loc = r.path.length === 0 ? "" : ` at ${r.path.join("")}`; + return "" + r.message + loc + ` (using ${r.matcher.name} matcher)`; + }); + } + recordCapture(options) { + let values = this.captures.get(options.capture); + if (values === void 0) { + values = []; + } + values.push(options.value); + this.captures.set(options.capture, values); + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/matchers/absent.ts +var AbsentMatch; +var init_absent = __esm({ + "../../aws-cdk-lib/assertions/lib/private/matchers/absent.ts"() { + "use strict"; + init_matcher(); + AbsentMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual !== void 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Received ${actual}, but key should be absent` + }); + } + return result; + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/private/type.ts +function getType(obj) { + return Array.isArray(obj) ? "array" : typeof obj; +} +var init_type = __esm({ + "../../aws-cdk-lib/assertions/lib/private/type.ts"() { + "use strict"; + } +}); + +// ../../aws-cdk-lib/assertions/lib/match.ts +var match_exports = {}; +__export(match_exports, { + Match: () => Match +}); +var Match, LiteralMatch, ArrayMatch, ObjectMatch, SerializedJson, NotMatch, AnyMatch, StringLikeRegexpMatch; +var init_match = __esm({ + "../../aws-cdk-lib/assertions/lib/match.ts"() { + "use strict"; + init_matcher(); + init_absent(); + init_type(); + Match = class { + static absent() { + return new AbsentMatch("absent"); + } + static arrayWith(pattern) { + return new ArrayMatch("arrayWith", pattern); + } + static arrayEquals(pattern) { + return new ArrayMatch("arrayEquals", pattern, { subsequence: false }); + } + static exact(pattern) { + return new LiteralMatch("exact", pattern, { partialObjects: false }); + } + static objectLike(pattern) { + return new ObjectMatch("objectLike", pattern); + } + static objectEquals(pattern) { + return new ObjectMatch("objectEquals", pattern, { partial: false }); + } + static not(pattern) { + return new NotMatch("not", pattern); + } + static serializedJson(pattern) { + return new SerializedJson("serializedJson", pattern); + } + static anyValue() { + return new AnyMatch("anyValue"); + } + static stringLikeRegexp(pattern) { + return new StringLikeRegexpMatch("stringLikeRegexp", pattern); + } + }; + LiteralMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partialObjects = options.partialObjects ?? false; + if (Matcher.isMatcher(this.pattern)) { + throw new Error("LiteralMatch cannot directly contain another matcher. Remove the top-level matcher or nest it more deeply."); + } + } + test(actual) { + if (Array.isArray(this.pattern)) { + return new ArrayMatch(this.name, this.pattern, { subsequence: false, partialObjects: this.partialObjects }).test(actual); + } + if (typeof this.pattern === "object") { + return new ObjectMatch(this.name, this.pattern, { partial: this.partialObjects }).test(actual); + } + const result = new MatchResult(actual); + if (typeof this.pattern !== typeof actual) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected type ${typeof this.pattern} but received ${getType(actual)}` + }); + return result; + } + if (actual !== this.pattern) { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected ${this.pattern} but received ${actual}` + }); + } + return result; + } + }; + ArrayMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.subsequence = options.subsequence ?? true; + this.partialObjects = options.partialObjects ?? false; + } + test(actual) { + if (!Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type array but received ${getType(actual)}` + }); + } + if (!this.subsequence && this.pattern.length !== actual.length) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected array of length ${this.pattern.length} but received ${actual.length}` + }); + } + let patternIdx = 0; + let actualIdx = 0; + const result = new MatchResult(actual); + while (patternIdx < this.pattern.length && actualIdx < actual.length) { + const patternElement = this.pattern[patternIdx]; + const matcher = Matcher.isMatcher(patternElement) ? patternElement : new LiteralMatch(this.name, patternElement, { partialObjects: this.partialObjects }); + const matcherName = matcher.name; + if (this.subsequence && (matcherName == "absent" || matcherName == "anyValue")) { + throw new Error(`The Matcher ${matcherName}() cannot be nested within arrayWith()`); + } + const innerResult = matcher.test(actual[actualIdx]); + if (!this.subsequence || !innerResult.hasFailed()) { + result.compose(`[${actualIdx}]`, innerResult); + patternIdx++; + actualIdx++; + } else { + actualIdx++; + } + } + for (; patternIdx < this.pattern.length; patternIdx++) { + const pattern = this.pattern[patternIdx]; + const element = Matcher.isMatcher(pattern) || typeof pattern === "object" ? " " : ` [${pattern}] `; + result.recordFailure({ + matcher: this, + path: [], + message: `Missing element${element}at pattern index ${patternIdx}` + }); + } + return result; + } + }; + ObjectMatch = class extends Matcher { + constructor(name, pattern, options = {}) { + super(); + this.name = name; + this.pattern = pattern; + this.partial = options.partial ?? true; + } + test(actual) { + if (typeof actual !== "object" || Array.isArray(actual)) { + return new MatchResult(actual).recordFailure({ + matcher: this, + path: [], + message: `Expected type object but received ${getType(actual)}` + }); + } + const result = new MatchResult(actual); + if (!this.partial) { + for (const a of Object.keys(actual)) { + if (!(a in this.pattern)) { + result.recordFailure({ + matcher: this, + path: [`/${a}`], + message: "Unexpected key" + }); + } + } + } + for (const [patternKey, patternVal] of Object.entries(this.pattern)) { + if (!(patternKey in actual) && !(patternVal instanceof AbsentMatch)) { + result.recordFailure({ + matcher: this, + path: [`/${patternKey}`], + message: `Missing key '${patternKey}' among {${Object.keys(actual).join(",")}}` + }); + continue; + } + const matcher = Matcher.isMatcher(patternVal) ? patternVal : new LiteralMatch(this.name, patternVal, { partialObjects: this.partial }); + const inner = matcher.test(actual[patternKey]); + result.compose(`/${patternKey}`, inner); + } + return result; + } + }; + SerializedJson = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + if (getType(actual) !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected JSON as a string but found ${getType(actual)}` + }); + return result; + } + let parsed; + try { + parsed = JSON.parse(actual); + } catch (err) { + if (err instanceof SyntaxError) { + result.recordFailure({ + matcher: this, + path: [], + message: `Invalid JSON string: ${actual}` + }); + return result; + } else { + throw err; + } + } + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(parsed); + result.compose(`(${this.name})`, innerResult); + return result; + } + }; + NotMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const matcher = Matcher.isMatcher(this.pattern) ? this.pattern : new LiteralMatch(this.name, this.pattern); + const innerResult = matcher.test(actual); + const result = new MatchResult(actual); + if (innerResult.failCount === 0) { + result.recordFailure({ + matcher: this, + path: [], + message: `Found unexpected match: ${JSON.stringify(actual, void 0, 2)}` + }); + } + return result; + } + }; + AnyMatch = class extends Matcher { + constructor(name) { + super(); + this.name = name; + } + test(actual) { + const result = new MatchResult(actual); + if (actual == null) { + result.recordFailure({ + matcher: this, + path: [], + message: "Expected a value but found none" + }); + } + return result; + } + }; + StringLikeRegexpMatch = class extends Matcher { + constructor(name, pattern) { + super(); + this.name = name; + this.pattern = pattern; + } + test(actual) { + const result = new MatchResult(actual); + const regex = new RegExp(this.pattern, "gm"); + if (typeof actual !== "string") { + result.recordFailure({ + matcher: this, + path: [], + message: `Expected a string, but got '${typeof actual}'` + }); + } + if (!regex.test(actual)) { + result.recordFailure({ + matcher: this, + path: [], + message: `String '${actual}' did not match pattern '${this.pattern}'` + }); + } + return result; + } + }; + } +}); + +// ../../aws-cdk-lib/assertions/lib/helpers-internal/index.js +var require_helpers_internal = __commonJS({ + "../../aws-cdk-lib/assertions/lib/helpers-internal/index.js"(exports) { + "use strict"; + var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) { + k2 === void 0 && (k2 = k), Object.defineProperty(o, k2, { enumerable: true, get: function() { + return m[k]; + } }); + } : function(o, m, k, k2) { + k2 === void 0 && (k2 = k), o[k2] = m[k]; + }); + var __exportStar = exports && exports.__exportStar || function(m, exports2) { + for (var p in m) + p !== "default" && !exports2.hasOwnProperty(p) && __createBinding(exports2, m, p); + }; + Object.defineProperty(exports, "__esModule", { value: true }), __exportStar((init_match(), __toCommonJS(match_exports)), exports), __exportStar((init_matcher(), __toCommonJS(matcher_exports)), exports); + } +}); + +// lib/assertions/providers/lambda-handler/index.ts +var lambda_handler_exports = {}; +__export(lambda_handler_exports, { + handler: () => handler, + isComplete: () => isComplete, + onTimeout: () => onTimeout +}); +module.exports = __toCommonJS(lambda_handler_exports); + +// lib/assertions/providers/lambda-handler/assertion.ts +var import_helpers_internal = __toESM(require_helpers_internal()); + +// lib/assertions/providers/lambda-handler/base.ts +var https = __toESM(require("https")); +var url = __toESM(require("url")); +var AWS = __toESM(require("aws-sdk")); +var CustomResourceHandler = class { + constructor(event, context) { + this.event = event; + this.context = context; + this.timedOut = false; + this.timeout = setTimeout(async () => { + await this.respond({ + status: "FAILED", + reason: "Lambda Function Timeout", + data: this.context.logStreamName + }); + this.timedOut = true; + }, context.getRemainingTimeInMillis() - 1200); + this.event = event; + this.physicalResourceId = extractPhysicalResourceId(event); + } + async handle() { + try { + if ("stateMachineArn" in this.event.ResourceProperties) { + const req = { + stateMachineArn: this.event.ResourceProperties.stateMachineArn, + name: this.event.RequestId, + input: JSON.stringify(this.event) + }; + await this.startExecution(req); + return; + } else { + const response = await this.processEvent(this.event.ResourceProperties); + return response; + } + } catch (e) { + console.log(e); + throw e; + } finally { + clearTimeout(this.timeout); + } + } + async handleIsComplete() { + try { + const result = await this.processEvent(this.event.ResourceProperties); + return result; + } catch (e) { + console.log(e); + return; + } finally { + clearTimeout(this.timeout); + } + } + async startExecution(req) { + try { + const sfn = new AWS.StepFunctions(); + await sfn.startExecution(req).promise(); + } finally { + clearTimeout(this.timeout); + } + } + respond(response) { + if (this.timedOut) { + return; + } + const cfResponse = { + Status: response.status, + Reason: response.reason, + PhysicalResourceId: this.physicalResourceId, + StackId: this.event.StackId, + RequestId: this.event.RequestId, + LogicalResourceId: this.event.LogicalResourceId, + NoEcho: false, + Data: response.data + }; + const responseBody = JSON.stringify(cfResponse); + console.log("Responding to CloudFormation", responseBody); + const parsedUrl = url.parse(this.event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: "PUT", + headers: { "content-type": "", "content-length": responseBody.length } + }; + return new Promise((resolve, reject) => { + try { + const request2 = https.request(requestOptions, resolve); + request2.on("error", reject); + request2.write(responseBody); + request2.end(); + } catch (e) { + reject(e); + } finally { + clearTimeout(this.timeout); + } + }); + } +}; +function extractPhysicalResourceId(event) { + switch (event.RequestType) { + case "Create": + return event.LogicalResourceId; + case "Update": + case "Delete": + return event.PhysicalResourceId; + } +} + +// lib/assertions/providers/lambda-handler/assertion.ts +var AssertionHandler = class extends CustomResourceHandler { + async processEvent(request2) { + let actual = decodeCall(request2.actual); + const expected = decodeCall(request2.expected); + let result; + const matcher = new MatchCreator(expected).getMatcher(); + console.log(`Testing equality between ${JSON.stringify(request2.actual)} and ${JSON.stringify(request2.expected)}`); + const matchResult = matcher.test(actual); + matchResult.finished(); + if (matchResult.hasFailed()) { + result = { + failed: true, + assertion: JSON.stringify({ + status: "fail", + message: [ + ...matchResult.toHumanStrings(), + JSON.stringify(matchResult.target, void 0, 2) + ].join("\n") + }) + }; + if (request2.failDeployment) { + throw new Error(result.assertion); + } + } else { + result = { + assertion: JSON.stringify({ + status: "success" + }) + }; + } + return result; + } +}; +var MatchCreator = class { + constructor(obj) { + this.parsedObj = { + matcher: obj + }; + } + getMatcher() { + try { + const final = JSON.parse(JSON.stringify(this.parsedObj), function(_k, v) { + const nested = Object.keys(v)[0]; + switch (nested) { + case "$ArrayWith": + return import_helpers_internal.Match.arrayWith(v[nested]); + case "$ObjectLike": + return import_helpers_internal.Match.objectLike(v[nested]); + case "$StringLike": + return import_helpers_internal.Match.stringLikeRegexp(v[nested]); + default: + return v; + } + }); + if (import_helpers_internal.Matcher.isMatcher(final.matcher)) { + return final.matcher; + } + return import_helpers_internal.Match.exact(final.matcher); + } catch { + return import_helpers_internal.Match.exact(this.parsedObj.matcher); + } + } +}; +function decodeCall(call) { + if (!call) { + return void 0; + } + try { + const parsed = JSON.parse(call); + return parsed; + } catch (e) { + return call; + } +} + +// lib/assertions/providers/lambda-handler/utils.ts +function decode(object) { + return JSON.parse(JSON.stringify(object), (_k, v) => { + switch (v) { + case "TRUE:BOOLEAN": + return true; + case "FALSE:BOOLEAN": + return false; + default: + return v; + } + }); +} + +// lib/assertions/providers/lambda-handler/sdk.ts +function flatten(object) { + return Object.assign( + {}, + ...function _flatten(child, path = []) { + return [].concat(...Object.keys(child).map((key) => { + let childKey = Buffer.isBuffer(child[key]) ? child[key].toString("utf8") : child[key]; + if (typeof childKey === "string") { + childKey = isJsonString(childKey); + } + return typeof childKey === "object" && childKey !== null ? _flatten(childKey, path.concat([key])) : { [path.concat([key]).join(".")]: childKey }; + })); + }(object) + ); +} +var AwsApiCallHandler = class extends CustomResourceHandler { + async processEvent(request2) { + const AWS2 = require("aws-sdk"); + console.log(`AWS SDK VERSION: ${AWS2.VERSION}`); + if (!Object.prototype.hasOwnProperty.call(AWS2, request2.service)) { + throw Error(`Service ${request2.service} does not exist in AWS SDK version ${AWS2.VERSION}.`); + } + const service = new AWS2[request2.service](); + const response = await service[request2.api](request2.parameters && decode(request2.parameters)).promise(); + console.log(`SDK response received ${JSON.stringify(response)}`); + delete response.ResponseMetadata; + const respond = { + apiCallResponse: response + }; + const flatData = { + ...flatten(respond) + }; + let resp = respond; + if (request2.outputPaths) { + resp = filterKeys(flatData, request2.outputPaths); + } else if (request2.flattenResponse === "true") { + resp = flatData; + } + console.log(`Returning result ${JSON.stringify(resp)}`); + return resp; + } +}; +function filterKeys(object, searchStrings) { + return Object.entries(object).reduce((filteredObject, [key, value]) => { + for (const searchString of searchStrings) { + if (key.startsWith(`apiCallResponse.${searchString}`)) { + filteredObject[key] = value; + } + } + return filteredObject; + }, {}); +} +function isJsonString(value) { + try { + return JSON.parse(value); + } catch { + return value; + } +} + +// lib/assertions/providers/lambda-handler/types.ts +var ASSERT_RESOURCE_TYPE = "Custom::DeployAssert@AssertEquals"; +var SDK_RESOURCE_TYPE_PREFIX = "Custom::DeployAssert@SdkCall"; + +// lib/assertions/providers/lambda-handler/index.ts +async function handler(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + if (event.RequestType === "Delete") { + await provider.respond({ + status: "SUCCESS", + reason: "OK" + }); + return; + } + const result = await provider.handle(); + if ("stateMachineArn" in event.ResourceProperties) { + console.info('Found "stateMachineArn", waiter statemachine started'); + return; + } else if ("expected" in event.ResourceProperties) { + console.info('Found "expected", testing assertions'); + const actualPath = event.ResourceProperties.actualPath; + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + try { + const assertionResult = await assertion.handle(); + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } catch (e) { + await provider.respond({ + status: "FAILED", + reason: e.message ?? "Internal Error" + }); + return; + } + return; +} +async function onTimeout(timeoutEvent) { + const isCompleteRequest = JSON.parse(JSON.parse(timeoutEvent.Cause).errorMessage); + const provider = createResourceHandler(isCompleteRequest, standardContext); + await provider.respond({ + status: "FAILED", + reason: "Operation timed out: " + JSON.stringify(isCompleteRequest) + }); +} +async function isComplete(event, context) { + console.log(`Event: ${JSON.stringify({ ...event, ResponseURL: "..." })}`); + const provider = createResourceHandler(event, context); + try { + const result = await provider.handleIsComplete(); + const actualPath = event.ResourceProperties.actualPath; + if (result) { + const actual = actualPath ? result[`apiCallResponse.${actualPath}`] : result.apiCallResponse; + if ("expected" in event.ResourceProperties) { + const assertion = new AssertionHandler({ + ...event, + ResourceProperties: { + ServiceToken: event.ServiceToken, + actual, + expected: event.ResourceProperties.expected + } + }, context); + const assertionResult = await assertion.handleIsComplete(); + if (!(assertionResult == null ? void 0 : assertionResult.failed)) { + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: { + ...assertionResult, + ...result + } + }); + return; + } else { + console.log(`Assertion Failed: ${JSON.stringify(assertionResult)}`); + throw new Error(JSON.stringify(event)); + } + } + await provider.respond({ + status: "SUCCESS", + reason: "OK", + data: result + }); + } else { + console.log("No result"); + throw new Error(JSON.stringify(event)); + } + return; + } catch (e) { + console.log(e); + throw new Error(JSON.stringify(event)); + } +} +function createResourceHandler(event, context) { + if (event.ResourceType.startsWith(SDK_RESOURCE_TYPE_PREFIX)) { + return new AwsApiCallHandler(event, context); + } else if (event.ResourceType.startsWith(ASSERT_RESOURCE_TYPE)) { + return new AssertionHandler(event, context); + } else { + throw new Error(`Unsupported resource type "${event.ResourceType}`); + } +} +var standardContext = { + getRemainingTimeInMillis: () => 9e4 +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + handler, + isComplete, + onTimeout +}); diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/cdk.out b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/cdk.out new file mode 100644 index 0000000000000..145739f539580 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"22.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/integ.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/integ.json new file mode 100644 index 0000000000000..a58a27dd566d4 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "22.0.0", + "testCases": { + "Python/DefaultTest": { + "stacks": [ + "PythonStack" + ], + "assertionStack": "Python/DefaultTest/DeployAssert", + "assertionStackName": "PythonDefaultTestDeployAssert82E20B88" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/manifest.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/manifest.json new file mode 100644 index 0000000000000..8d2793db76723 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/manifest.json @@ -0,0 +1,142 @@ +{ + "version": "22.0.0", + "artifacts": { + "PythonStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "PythonStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "PythonStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "PythonStack.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/f73d32c184c3aacfc0baa0a075665e95c9a8065be2690b7c556d2bd78b5d796b.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "PythonStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "PythonStack.assets" + ], + "metadata": { + "/PythonStack/Queue/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Queue4A7E3555" + } + ], + "/PythonStack/Exports/Output{\"Ref\":\"Queue4A7E3555\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefQueue4A7E3555425E8BD3" + } + ], + "/PythonStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/PythonStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "PythonStack" + }, + "PythonDefaultTestDeployAssert82E20B88.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "PythonDefaultTestDeployAssert82E20B88.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "PythonDefaultTestDeployAssert82E20B88": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "PythonDefaultTestDeployAssert82E20B88.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4ba4a227fd62b83817c85595e9fcb22091b0c088a429a8eea7bd06560b18e2d2.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "PythonDefaultTestDeployAssert82E20B88.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "PythonStack", + "PythonDefaultTestDeployAssert82E20B88.assets" + ], + "metadata": { + "/Python/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/Default/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AwsApiCallSQSgetQueueAttributes" + } + ], + "/Python/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/AssertionResults": [ + { + "type": "aws:cdk:logicalId", + "data": "AssertionResultsAwsApiCallSQSgetQueueAttributes" + } + ], + "/Python/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81Role37ABCE73" + } + ], + "/Python/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "SingletonFunction1488541a7b23466481b69b4408076b81HandlerCD40AE9F" + } + ], + "/Python/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/Python/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "Python/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/tree.json b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/tree.json new file mode 100644 index 0000000000000..fded17177c5b9 --- /dev/null +++ b/packages/@aws-cdk/integ-runner/test/language-tests/integ_python-test.py.snapshot/tree.json @@ -0,0 +1,231 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "PythonStack": { + "id": "PythonStack", + "path": "PythonStack", + "children": { + "Queue": { + "id": "Queue", + "path": "PythonStack/Queue", + "children": { + "Resource": { + "id": "Resource", + "path": "PythonStack/Queue/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::SQS::Queue", + "aws:cdk:cloudformation:props": { + "fifoQueue": true + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.CfnQueue", + "version": "2.55.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_sqs.Queue", + "version": "2.55.0" + } + }, + "Exports": { + "id": "Exports", + "path": "PythonStack/Exports", + "children": { + "Output{\"Ref\":\"Queue4A7E3555\"}": { + "id": "Output{\"Ref\":\"Queue4A7E3555\"}", + "path": "PythonStack/Exports/Output{\"Ref\":\"Queue4A7E3555\"}", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "2.55.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.190" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "PythonStack/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "2.55.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "PythonStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "2.55.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "2.55.0" + } + }, + "Python": { + "id": "Python", + "path": "Python", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "Python/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "Python/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.190" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "Python/DefaultTest/DeployAssert", + "children": { + "AwsApiCallSQSgetQueueAttributes": { + "id": "AwsApiCallSQSgetQueueAttributes", + "path": "Python/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes", + "children": { + "SdkProvider": { + "id": "SdkProvider", + "path": "Python/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/SdkProvider", + "children": { + "AssertionsProvider": { + "id": "AssertionsProvider", + "path": "Python/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/SdkProvider/AssertionsProvider", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.190" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.AssertionsProvider", + "version": "2.55.0-alpha.0" + } + }, + "Default": { + "id": "Default", + "path": "Python/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/Default", + "children": { + "Default": { + "id": "Default", + "path": "Python/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/Default/Default", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "2.55.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.CustomResource", + "version": "2.55.0" + } + }, + "AssertionResults": { + "id": "AssertionResults", + "path": "Python/DefaultTest/DeployAssert/AwsApiCallSQSgetQueueAttributes/AssertionResults", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "2.55.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.AwsApiCall", + "version": "2.55.0-alpha.0" + } + }, + "SingletonFunction1488541a7b23466481b69b4408076b81": { + "id": "SingletonFunction1488541a7b23466481b69b4408076b81", + "path": "Python/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81", + "children": { + "Staging": { + "id": "Staging", + "path": "Python/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Staging", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "2.55.0" + } + }, + "Role": { + "id": "Role", + "path": "Python/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Role", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "2.55.0" + } + }, + "Handler": { + "id": "Handler", + "path": "Python/DefaultTest/DeployAssert/SingletonFunction1488541a7b23466481b69b4408076b81/Handler", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnResource", + "version": "2.55.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.190" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "Python/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "2.55.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "Python/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "2.55.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "2.55.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "2.55.0-alpha.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "2.55.0-alpha.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.190" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "2.55.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/integ-runner/test/runner/integ-test-runner.test.ts b/packages/@aws-cdk/integ-runner/test/runner/integ-test-runner.test.ts index d4055eaad76fc..0e5abd08738f5 100644 --- a/packages/@aws-cdk/integ-runner/test/runner/integ-test-runner.test.ts +++ b/packages/@aws-cdk/integ-runner/test/runner/integ-test-runner.test.ts @@ -548,7 +548,7 @@ describe('IntegTest runIntegTests', () => { })); }); - test('with custom app run command for JavaScript', () => { + test('with custom app run command', () => { // WHEN const integTest = new IntegTestRunner({ cdk: cdkMock.cdk, diff --git a/packages/@aws-cdk/integ-runner/test/runner/integration-tests.test.ts b/packages/@aws-cdk/integ-runner/test/runner/integration-tests.test.ts index 010377436b8f0..11c5af79756e5 100644 --- a/packages/@aws-cdk/integ-runner/test/runner/integration-tests.test.ts +++ b/packages/@aws-cdk/integ-runner/test/runner/integration-tests.test.ts @@ -1,7 +1,7 @@ import * as mockfs from 'mock-fs'; import { IntegrationTests } from '../../lib/runner/integration-tests'; -describe('IntegrationTests', () => { +describe('IntegrationTests Discovery', () => { const tests = new IntegrationTests('test'); let stderrMock: jest.SpyInstance; stderrMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => { return true; }); @@ -9,84 +9,124 @@ describe('IntegrationTests', () => { beforeEach(() => { mockfs({ 'test/test-data': { - 'integ.integ-test1.js': 'content', - 'integ.integ-test2.js': 'content', - 'integ.integ-test3.js': 'content', - 'integration.test.js': 'should not match', + 'integ.test1.js': 'javascript', + 'integ.test2.js': 'javascript', + 'integ.test3.js': 'javascript', + 'integration.test.js': 'javascript-no-match', + + 'integ.test1.ts': 'typescript', + 'integ.test2.ts': 'typescript', + 'integ.test3.ts': 'typescript', + 'integ.test3.d.ts': 'typescript-no-match', + 'integration.test.ts': 'typescript-no-match', + + 'integ_test1.py': 'python', + 'integ_test2.py': 'python', + 'integ_test3.py': 'python', + 'integ.integ-test.py': 'python-no-match', }, 'other/other-data': { - 'integ.other-test1.js': 'content', + 'integ.other-test1.js': 'javascript', + 'integ.other-test1.ts': 'typescript', + 'integ_other-test1.py': 'python', }, }); }); afterEach(() => { mockfs.restore(); + stderrMock.mockReset(); }); - describe('from cli args', () => { - test('find all', async () => { - const integTests = await tests.fromCliArgs(); - - expect(integTests.length).toEqual(3); - expect(integTests[0].fileName).toEqual(expect.stringMatching(/integ.integ-test1.js$/)); - expect(integTests[1].fileName).toEqual(expect.stringMatching(/integ.integ-test2.js$/)); - expect(integTests[2].fileName).toEqual(expect.stringMatching(/integ.integ-test3.js$/)); + describe.each([ + ['javascript', 'js', 'integ.test1.js'], + ['typescript', 'ts', 'integ.test1.ts'], + ['python', 'py', 'integ_test1.py'], + ])('%s', (language, fileExtension, namedTest) => { + const cliOptions = { + language: [language], + }; + + test('assert test inputs', () => { + expect(namedTest).toContain('.' + fileExtension); }); - - test('find named tests', async () => { - const integTests = await tests.fromCliArgs({ tests: ['test-data/integ.integ-test1.js'] }); - - expect(integTests.length).toEqual(1); - expect(integTests[0].fileName).toEqual(expect.stringMatching(/integ.integ-test1.js$/)); + describe('from cli args', () => { + test('find all', async () => { + const integTests = await tests.fromCliOptions(cliOptions); + + expect(integTests.length).toEqual(3); + expect(integTests[0].fileName).toEqual(expect.stringMatching(new RegExp(`^.*test1\\.${fileExtension}$`))); + expect(integTests[1].fileName).toEqual(expect.stringMatching(new RegExp(`^.*test2\\.${fileExtension}$`))); + expect(integTests[2].fileName).toEqual(expect.stringMatching(new RegExp(`^.*test3\\.${fileExtension}$`))); + }); + + test('find named tests', async () => { + const integTests = await tests.fromCliOptions({ ...cliOptions, tests: [`test-data/${namedTest}`] }); + + expect(integTests.length).toEqual(1); + expect(integTests[0].fileName).toEqual(expect.stringMatching(namedTest)); + }); + + + test('test not found', async () => { + const integTests = await tests.fromCliOptions({ ...cliOptions, tests: [`test-data/${namedTest}`.replace('test1', 'test42')] }); + + expect(integTests.length).toEqual(0); + expect(stderrMock.mock.calls[0][0].trim()).toMatch( + new RegExp(`No such integ test: test-data\\/.*test42\\.${fileExtension}`), + ); + expect(stderrMock.mock.calls[1][0]).toMatch( + new RegExp(`Available tests: test-data\\/.*test1\\.${fileExtension} test-data\\/.*test2\\.${fileExtension} test-data\\/.*test3\\.${fileExtension}`), + ); + }); + + test('exclude tests', async () => { + const integTests = await tests.fromCliOptions({ ...cliOptions, tests: [`test-data/${namedTest}`], exclude: true }); + + const fileNames = integTests.map(test => test.fileName); + expect(integTests.length).toEqual(2); + expect(fileNames).not.toContain( + `test/test-data/${namedTest}`, + ); + }); + + test('match regex', async () => { + const integTests = await tests.fromCliOptions({ + language: [language], + testRegex: [`[12]\\.${fileExtension}$`], + }); + + expect(integTests.length).toEqual(2); + expect(integTests[0].fileName).toEqual(expect.stringMatching(new RegExp(`1\\.${fileExtension}$`))); + expect(integTests[1].fileName).toEqual(expect.stringMatching(new RegExp(`2\\.${fileExtension}$`))); + }); + + test('match regex with path', async () => { + const otherTestDir = new IntegrationTests('.'); + const integTests = await otherTestDir.fromCliOptions({ + language: [language], + testRegex: [`other-data/integ.*\\.${fileExtension}$`], + }); + + expect(integTests.length).toEqual(1); + expect(integTests[0].fileName).toEqual(expect.stringMatching(new RegExp(`.*other-test1\\.${fileExtension}$`))); + }); }); + }); + describe('Same test file in JS and TS is only running JS', () => { + const cliOptions = { + language: ['javascript', 'typescript'], + }; - test('test not found', async () => { - const integTests = await tests.fromCliArgs({ tests: ['test-data/integ.integ-test16.js'] }); - - expect(integTests.length).toEqual(0); - expect(stderrMock.mock.calls[0][0]).toContain( - 'No such integ test: test-data/integ.integ-test16.js', - ); - expect(stderrMock.mock.calls[1][0]).toContain( - 'Available tests: test-data/integ.integ-test1.js test-data/integ.integ-test2.js test-data/integ.integ-test3.js', - ); - }); - - test('exclude tests', async () => { - const integTests = await tests.fromCliArgs({ tests: ['test-data/integ.integ-test1.js'], exclude: true }); - - const fileNames = integTests.map(test => test.fileName); - expect(integTests.length).toEqual(2); - expect(fileNames).not.toContain( - 'test/test-data/integ.integ-test1.js', - ); - }); - - test('match regex', async () => { - const integTests = await tests.fromCliArgs({ testRegex: ['1\.js$', '2\.js'] }); - - expect(integTests.length).toEqual(2); - expect(integTests[0].fileName).toEqual(expect.stringMatching(/integ.integ-test1.js$/)); - expect(integTests[1].fileName).toEqual(expect.stringMatching(/integ.integ-test2.js$/)); - }); - - test('match regex with path', async () => { - const otherTestDir = new IntegrationTests('.'); - const integTests = await otherTestDir.fromCliArgs({ testRegex: ['other-data/integ\..*\.js$'] }); - - expect(integTests.length).toEqual(1); - expect(integTests[0].fileName).toEqual(expect.stringMatching(/integ.other-test1.js$/)); - }); - - test('can set app command', async () => { - const otherTestDir = new IntegrationTests('test'); - const integTests = await otherTestDir.fromCliArgs({ app: 'node --no-warnings {filePath}' }); + test('find only JS files', async () => { + const integTests = await tests.fromCliOptions(cliOptions); expect(integTests.length).toEqual(3); - expect(integTests[0].appCommand).toEqual('node --no-warnings {filePath}'); + expect(integTests[0].fileName).toEqual(expect.stringMatching(new RegExp('^.*test1\\.js$'))); + expect(integTests[1].fileName).toEqual(expect.stringMatching(new RegExp('^.*test2\\.js$'))); + expect(integTests[2].fileName).toEqual(expect.stringMatching(new RegExp('^.*test3\\.js$'))); }); }); }); diff --git a/packages/@aws-cdk/integ-runner/tsconfig.json b/packages/@aws-cdk/integ-runner/tsconfig.json index 602608087631f..f8f20f4a3ec44 100644 --- a/packages/@aws-cdk/integ-runner/tsconfig.json +++ b/packages/@aws-cdk/integ-runner/tsconfig.json @@ -15,14 +15,14 @@ "resolveJsonModule": true, "composite": true, "incremental": true - }, + }, "include": [ "**/*.ts", "**/*.d.ts", "lib/init-templates/*/*/add-project.hook.ts" ], "exclude": [ - "lib/init-templates/*/typescript/**/*.ts" + "lib/init-templates/*/typescript/**/*.ts", + "test/language-tests/**/integ.*.ts" ] } -