diff --git a/packages/aws-cdk/test/_helpers/io-recorder.ts b/packages/aws-cdk/test/_helpers/io-recorder.ts index 99b892207..ea30177da 100644 --- a/packages/aws-cdk/test/_helpers/io-recorder.ts +++ b/packages/aws-cdk/test/_helpers/io-recorder.ts @@ -38,6 +38,13 @@ function defaultScrubbers(): Scrubber[] { { pattern: /\n\s+at\s+[^\n]*/g, replacement: '' }, // The OS temp dir (tests chdir into a temp dir) { pattern: new RegExp(escapeRegExp(fs.realpathSync(os.tmpdir())), 'g'), replacement: '' }, + // The random suffix of mkdtemp-created assembly output dirs, e.g. + // "/cdk.outAb12Cd". Anchored on the path separator (either flavor, and + // normalized to '/' so snapshots match cross-platform) so prose that + // merely starts with "cdk.out" is never mangled; mkdtemp dirs always sit + // directly under the temp dir, so the separator is always present (and + // the TMP scrubber above has already run — scrubbers apply in array order). + { pattern: /[\\/]cdk\.out[a-zA-Z0-9]{6}/g, replacement: '/cdk.out' }, ]; } diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_--ignore-errors_tolerates_error_annotations_on_a_selected_stack.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_--ignore-errors_tolerates_error_annotations_on_a_selected_stack.ndjson new file mode 100644 index 000000000..9fb6c9feb --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_--ignore-errors_tolerates_error_annotations_on_a_selected_stack.ndjson @@ -0,0 +1,4 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"error","code":"CDK_TOOLKIT_E9600","message":"ERROR this is an error (Construct Annotations)\n resource\n Rule Construct-Annotations::error-annotation"} +{"seq":3,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_--strict_fails_synthesis_on_warning_annotations.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_--strict_fails_synthesis_on_warning_annotations.ndjson new file mode 100644 index 000000000..87be57808 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_--strict_fails_synthesis_on_warning_annotations.ndjson @@ -0,0 +1,3 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"error","code":"CDK_TOOLKIT_E9600","message":"WARNING this is a warning (Construct Annotations)\n resource\n Acknowledge with 'Construct-Annotations::warning-annotation'"} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_CI_mode_single_stack_skips_the_flags_warning_to_keep_stdout_valid_YAML.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_CI_mode_single_stack_skips_the_flags_warning_to_keep_stdout_valid_YAML.ndjson new file mode 100644 index 000000000..a73a1a2c2 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_CI_mode_single_stack_skips_the_flags_warning_to_keep_stdout_valid_YAML.ndjson @@ -0,0 +1,3 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"result","code":null,"message":"Resources:\n TemplateName: Test-Stack-A\n"} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_CI_mode_single_stack_with_--quiet_allows_the_flags_warning.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_CI_mode_single_stack_with_--quiet_allows_the_flags_warning.ndjson new file mode 100644 index 000000000..35b5c9f3f --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_CI_mode_single_stack_with_--quiet_allows_the_flags_warning.ndjson @@ -0,0 +1,3 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_stage-only_app_selects_no_stacks_and_still_succeeds.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_stage-only_app_selects_no_stacks_and_still_succeeds.ndjson new file mode 100644 index 000000000..4ef188131 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_stage-only_app_selects_no_stacks_and_still_succeeds.ndjson @@ -0,0 +1,5 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"info","code":null,"message":"Successfully synthesized to /cdk.out"} +{"seq":3,"type":"notify","action":"synth","level":"info","code":null,"message":"Supply a stack id () to display its template."} +{"seq":4,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_validateOnSynth_stack_with_errors_fails_synth_when_validation_is_on.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_validateOnSynth_stack_with_errors_fails_synth_when_validation_is_on.ndjson new file mode 100644 index 000000000..7d133eea3 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_validateOnSynth_stack_with_errors_fails_synth_when_validation_is_on.ndjson @@ -0,0 +1,3 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"error","code":"CDK_TOOLKIT_E9600","message":"ERROR this is an error (Construct Annotations)\n resource\n Rule Construct-Annotations::error-annotation"} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_validateOnSynth_stack_with_errors_is_tolerated_with_--no-validation.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_validateOnSynth_stack_with_errors_is_tolerated_with_--no-validation.ndjson new file mode 100644 index 000000000..a45d09f1b --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_a_validateOnSynth_stack_with_errors_is_tolerated_with_--no-validation.ndjson @@ -0,0 +1,5 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"info","code":null,"message":"Successfully synthesized to /cdk.out"} +{"seq":3,"type":"notify","action":"synth","level":"info","code":null,"message":"Supply a stack id (Test-Stack-A-Display-Name, Test-Stack-B) to display its template."} +{"seq":4,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_an_unmatched_pattern_fails_with_the_historical_error_message.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_an_unmatched_pattern_fails_with_the_historical_error_message.ndjson new file mode 100644 index 000000000..414d10c8c --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_an_unmatched_pattern_fails_with_the_historical_error_message.ndjson @@ -0,0 +1,2 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_explicitly_selected_stack_with_error_annotations_fails_synthesis.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_explicitly_selected_stack_with_error_annotations_fails_synthesis.ndjson new file mode 100644 index 000000000..7d133eea3 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_explicitly_selected_stack_with_error_annotations_fails_synthesis.ndjson @@ -0,0 +1,3 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"error","code":"CDK_TOOLKIT_E9600","message":"ERROR this is an error (Construct Annotations)\n resource\n Rule Construct-Annotations::error-annotation"} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_multiple_stacks_print_the_success_and_supply-a-stack-id_lines.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_multiple_stacks_print_the_success_and_supply-a-stack-id_lines.ndjson new file mode 100644 index 000000000..a45d09f1b --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_multiple_stacks_print_the_success_and_supply-a-stack-id_lines.ndjson @@ -0,0 +1,5 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"info","code":null,"message":"Successfully synthesized to /cdk.out"} +{"seq":3,"type":"notify","action":"synth","level":"info","code":null,"message":"Supply a stack id (Test-Stack-A-Display-Name, Test-Stack-B) to display its template."} +{"seq":4,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_nested-assembly_stacks_are_addressed_by_hierarchical_id_in_the_supply_line.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_nested-assembly_stacks_are_addressed_by_hierarchical_id_in_the_supply_line.ndjson new file mode 100644 index 000000000..f27f61719 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_nested-assembly_stacks_are_addressed_by_hierarchical_id_in_the_supply_line.ndjson @@ -0,0 +1,5 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"info","code":null,"message":"Successfully synthesized to /cdk.out"} +{"seq":3,"type":"notify","action":"synth","level":"info","code":null,"message":"Supply a stack id (Test-Stack-A-Display-Name, Test-Stack-A/nested) to display its template."} +{"seq":4,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_non-exclusive_selection_expands_to_upstream_dependencies.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_non-exclusive_selection_expands_to_upstream_dependencies.ndjson new file mode 100644 index 000000000..ddecad314 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_non-exclusive_selection_expands_to_upstream_dependencies.ndjson @@ -0,0 +1,6 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"info","code":"CDK_TOOLKIT_I1002","message":"Including dependency stacks: Test-Stack-B"} +{"seq":3,"type":"notify","action":"synth","level":"info","code":null,"message":"Successfully synthesized to /cdk.out"} +{"seq":4,"type":"notify","action":"synth","level":"info","code":null,"message":"Supply a stack id (Test-Stack-B, Test-Stack-D) to display its template."} +{"seq":5,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_prints_the_obscured_YAML_template_and_the_flags_warning.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_prints_the_obscured_YAML_template_and_the_flags_warning.ndjson new file mode 100644 index 000000000..3435f6c64 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_prints_the_obscured_YAML_template_and_the_flags_warning.ndjson @@ -0,0 +1,4 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"result","code":null,"message":"Resources:\n TemplateName: Test-Stack-A\n"} +{"seq":3,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_with_--json_prints_the_JSON_template.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_with_--json_prints_the_JSON_template.ndjson new file mode 100644 index 000000000..78e2339d4 --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_with_--json_prints_the_JSON_template.ndjson @@ -0,0 +1,4 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"result","code":null,"message":"{\n \"Resources\": {\n \"TemplateName\": \"Test-Stack-A\"\n }\n}"} +{"seq":3,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_with_--quiet_prints_no_template_but_keeps_the_flags_warning.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_with_--quiet_prints_no_template_but_keeps_the_flags_warning.ndjson new file mode 100644 index 000000000..35b5c9f3f --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_single_stack_with_--quiet_prints_no_template_but_keeps_the_flags_warning.ndjson @@ -0,0 +1,3 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_stack_ids_containing_glob_metacharacters_are_handed_to_toolkit-lib_literally.ndjson b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_stack_ids_containing_glob_metacharacters_are_handed_to_toolkit-lib_literally.ndjson new file mode 100644 index 000000000..bfec4f8ce --- /dev/null +++ b/packages/aws-cdk/test/commands/__io_snapshots__/synth/cdk_synth_stack_ids_containing_glob_metacharacters_are_handed_to_toolkit-lib_literally.ndjson @@ -0,0 +1,4 @@ +{"seq":0,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1000","message":"Starting Synthesis ..."} +{"seq":1,"type":"notify","action":"synth","level":"trace","code":"CDK_CLI_I1001","message":"\n✨ Synthesis time: \n"} +{"seq":2,"type":"notify","action":"synth","level":"result","code":null,"message":"Resources:\n TemplateName: Data[prod]\n"} +{"seq":3,"type":"notify","action":"synth","level":"warn","code":null,"message":"1 feature flags are not configured. Run 'cdk flags --unstable=flags' to learn more."} diff --git a/packages/aws-cdk/test/commands/synth.test.ts b/packages/aws-cdk/test/commands/synth.test.ts new file mode 100644 index 000000000..ebc639209 --- /dev/null +++ b/packages/aws-cdk/test/commands/synth.test.ts @@ -0,0 +1,249 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import type { FeatureFlag } from '@aws-cdk/toolkit-lib'; +import { Toolkit } from '@aws-cdk/toolkit-lib'; +import { Deployments } from '../../lib/api/deployments'; +import { CdkToolkit } from '../../lib/cli/cdk-toolkit'; +import { CliIoHost } from '../../lib/cli/io-host'; +import { instanceMockFrom, MockCloudExecutable } from '../_helpers'; +import type { TestAssembly } from '../_helpers'; +import { IoHostRecorder } from '../_helpers/io-recorder'; + +// `cdk synth` prints the template of a single selected stack to stdout (or a +// success message for several stacks) and appends a feature-flags warning with +// CI/quiet-dependent gating. Its stdout is a scripting surface (must stay valid +// YAML in CI mode), so these tests run `CdkToolkit.synth` and snapshot +// everything the user sees (via IoHostRecorder) — the committed NDJSON is the +// assertion for selection, template rendering, message levels and gating. +// +// Coverage notes: the cli.ts argument mapping (settings-file `quiet` override, +// the `--exclusively` branch) sits above the entry point used here and is NOT +// pinned by these snapshots. Several scenarios are also asserted, spy-style, in +// test/cli/cdk-toolkit.test.ts describe('synth') — these snapshots are the +// authoritative record of the user-visible output. +describe('cdk synth', () => { + const ioHost = CliIoHost.instance(); + let recorder: IoHostRecorder; + + // A flag that `FlagOperations.filterNeedsAttention` keeps (unconfigured, not + // obsolete, unconfigured behavior differs from the recommended value), so + // `displayFlagsMessage` deterministically emits its warning when not gated. + const UNCONFIGURED_FLAG: FeatureFlag = { + module: 'aws-cdk-lib', + name: '@aws-cdk/testing:synthSnapshotFlag', + recommendedValue: true, + unconfiguredBehavesLike: { v2: false }, + }; + + beforeEach(() => { + jest.resetAllMocks(); + ioHost.isCI = false; + ioHost.currentAction = 'synth'; + jest.spyOn(Toolkit.prototype, 'flags').mockResolvedValue([UNCONFIGURED_FLAG]); + // The recorder observes the host (it does not spy on `notify`), so the real + // notify path — and any output listeners — run without any pass-through. + recorder = IoHostRecorder.create(ioHost); + }); + + afterEach(() => { + recorder.matchSnapshot(); + }); + + async function synth( + assembly: TestAssembly, + stackNames: string[], + options: { + exclusively?: boolean; + quiet?: boolean; + autoValidate?: boolean; + json?: boolean; + strict?: boolean; + ignoreErrors?: boolean; + } = {}, + ) { + const cloudExecutable = await MockCloudExecutable.create(assembly, undefined, ioHost, 'synth'); + const toolkit = new CdkToolkit({ + ioHost, + cloudExecutable, + configuration: cloudExecutable.configuration, + sdkProvider: cloudExecutable.sdkProvider, + deployments: instanceMockFrom(Deployments), + strict: options.strict, + ignoreErrors: options.ignoreErrors, + }); + await toolkit.synth( + stackNames, + options.exclusively ?? true, + options.quiet ?? false, + options.autoValidate, + options.json, + ); + } + + const STACK_A = { + stackName: 'Test-Stack-A', + displayName: 'Test-Stack-A-Display-Name', + // Rules.CheckBootstrapVersion must be obscured from the printed template + template: { + Resources: { TemplateName: 'Test-Stack-A' }, + Rules: { CheckBootstrapVersion: { Assertions: [] } }, + }, + env: 'aws://123456789012/bermuda-triangle-1', + }; + + const STACK_B = { + stackName: 'Test-Stack-B', + template: { Resources: { TemplateName: 'Test-Stack-B' } }, + env: 'aws://123456789012/bermuda-triangle-1', + }; + + const STACK_WITH_ERROR = { + stackName: 'witherrors', + displayName: 'Test-Stack-A/witherrors', + env: 'aws://123456789012/bermuda-triangle-1', + template: { resource: 'errorresource' }, + metadata: { + '/resource': [ + { + type: cxschema.ArtifactMetadataEntryType.ERROR, + data: 'this is an error', + }, + ], + }, + }; + + test('single stack prints the obscured YAML template and the flags warning', async () => { + await synth({ stacks: [STACK_A] }, ['Test-Stack-A-Display-Name']); + }); + + test('single stack with --json prints the JSON template', async () => { + await synth({ stacks: [STACK_A] }, ['Test-Stack-A-Display-Name'], { json: true }); + }); + + test('single stack with --quiet prints no template but keeps the flags warning', async () => { + await synth({ stacks: [STACK_A] }, ['Test-Stack-A-Display-Name'], { quiet: true }); + }); + + test('multiple stacks print the success and supply-a-stack-id lines', async () => { + await synth({ stacks: [STACK_A, STACK_B] }, []); + }); + + test('nested-assembly stacks are addressed by hierarchical id in the supply line', async () => { + // Select the nested stack explicitly (the no-pattern default only selects + // top-level stacks), so the snapshot pins the hierarchical-id handoff. + await synth({ + stacks: [STACK_A, STACK_B], + nestedAssemblies: [{ + stacks: [{ + stackName: 'nested', + displayName: 'Test-Stack-A/nested', + template: { Resources: { TemplateName: 'nested' } }, + env: 'aws://123456789012/bermuda-triangle-1', + }], + }], + }, ['Test-Stack-A-Display-Name', 'Test-Stack-A/nested']); + }); + + test('stack ids containing glob metacharacters are handed to toolkit-lib literally', async () => { + // 'Data[prod]' glob-matches 'Datap' when re-interpreted as a pattern, so an + // unescaped id handoff would select both stacks and print the supply line + // instead of the template. The snapshot must show Data[prod]'s template. + await synth({ + stacks: [ + { + stackName: 'DataProd', + displayName: 'Data[prod]', + template: { Resources: { TemplateName: 'Data[prod]' } }, + env: 'aws://123456789012/bermuda-triangle-1', + }, + { + stackName: 'Datap', + template: { Resources: { TemplateName: 'Datap' } }, + env: 'aws://123456789012/bermuda-triangle-1', + }, + ], + }, ['Data\\[prod\\]']); + }); + + test('CI mode single stack skips the flags warning to keep stdout valid YAML', async () => { + ioHost.isCI = true; + await synth({ stacks: [STACK_A] }, ['Test-Stack-A-Display-Name']); + }); + + test('CI mode single stack with --quiet allows the flags warning', async () => { + ioHost.isCI = true; + await synth({ stacks: [STACK_A] }, ['Test-Stack-A-Display-Name'], { quiet: true }); + }); + + test('explicitly selected stack with error annotations fails synthesis', async () => { + await expect(synth({ + stacks: [STACK_A, STACK_B], + nestedAssemblies: [{ stacks: [STACK_WITH_ERROR] }], + }, ['Test-Stack-A/witherrors'], { quiet: true })).rejects.toThrow(/Synthesis finished with errors/); + }); + + test('a validateOnSynth stack with errors fails synth when validation is on', async () => { + await expect(synth({ + stacks: [STACK_A, STACK_B], + nestedAssemblies: [{ stacks: [{ ...STACK_WITH_ERROR, properties: { validateOnSynth: true } }] }], + }, [], { quiet: true, autoValidate: true })).rejects.toThrow(/Synthesis finished with errors/); + }); + + test('a validateOnSynth stack with errors is tolerated with --no-validation', async () => { + await synth({ + stacks: [STACK_A, STACK_B], + nestedAssemblies: [{ stacks: [{ ...STACK_WITH_ERROR, properties: { validateOnSynth: true } }] }], + }, [], { quiet: true, autoValidate: false }); + }); + + test('an unmatched pattern fails with the historical error message', async () => { + await expect(synth({ stacks: [STACK_A] }, ['NoSuchStack'])) + .rejects.toThrow(/No stacks match the name\(s\) NoSuchStack/); + }); + + test('a stage-only app selects no stacks and still succeeds', async () => { + // Pipeline-style apps have no top-level stacks; the no-pattern MainAssembly + // default selects nothing and synth must not fail. + // + // The snapshot deliberately pins the current output as-is, including the + // awkward empty-parens "Supply a stack id ()" line — this is a baseline, + // not an endorsement; fixing that wording should show up as a snapshot diff. + await synth({ + stacks: [], + nestedAssemblies: [{ stacks: [{ ...STACK_B, stackName: 'staged', displayName: 'MyStage/staged' }] }], + }, []); + }); + + test('non-exclusive selection expands to upstream dependencies', async () => { + // Selecting the dependent stack without --exclusively pulls in its + // dependency, taking the multi-stack path. + await synth({ + stacks: [ + STACK_B, + { + stackName: 'Test-Stack-D', + template: { Resources: { TemplateName: 'Test-Stack-D' } }, + env: 'aws://123456789012/bermuda-triangle-1', + depends: ['Test-Stack-B'], + }, + ], + }, ['Test-Stack-D'], { exclusively: false }); + }); + + test('--strict fails synthesis on warning annotations', async () => { + await expect(synth({ + stacks: [{ + ...STACK_B, + metadata: { + '/resource': [{ type: cxschema.ArtifactMetadataEntryType.WARN, data: 'this is a warning' }], + }, + }], + }, ['Test-Stack-B'], { quiet: true, strict: true })).rejects.toThrow(/Synthesis finished with warnings/); + }); + + test('--ignore-errors tolerates error annotations on a selected stack', async () => { + await synth({ + stacks: [STACK_A, STACK_B], + nestedAssemblies: [{ stacks: [STACK_WITH_ERROR] }], + }, ['Test-Stack-A/witherrors'], { quiet: true, ignoreErrors: true }); + }); +});