diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/private/count-assembly-results.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/private/count-assembly-results.ts index 64d0fede5..fbbc5beb4 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/private/count-assembly-results.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/private/count-assembly-results.ts @@ -10,7 +10,7 @@ export function countAssemblyResults(span: IMessageSpan, assembly: cxapi.Cl span.incCounter('warnings', sum(stacksRecursively.map(s => s.messages.filter(m => m.level === SynthesisMessageLevel.WARNING).length))); const annotationErrorCodes = stacksRecursively - .flatMap(s => Object.values(s.metadata) + .flatMap(s => Object.values(s.metadata ?? {}) .flatMap(ms => ms.filter(m => m.type === ANNOTATION_ERROR_CODE_TYPE))); for (const annotationErrorCode of annotationErrorCodes) { span.incCounter(`errorAnn:${annotationErrorCode.data}`); diff --git a/packages/@aws-cdk/toolkit-lib/test/toolkit/count-assembly-results.test.ts b/packages/@aws-cdk/toolkit-lib/test/toolkit/count-assembly-results.test.ts new file mode 100644 index 000000000..09b8a417b --- /dev/null +++ b/packages/@aws-cdk/toolkit-lib/test/toolkit/count-assembly-results.test.ts @@ -0,0 +1,51 @@ +import { countAssemblyResults } from '../../lib/toolkit/private/count-assembly-results'; + +// Minimal span that records incCounter calls; only the surface +// countAssemblyResults touches is implemented. +function fakeSpan() { + const counters: Array<{ name: string; delta?: number }> = []; + return { + counters, + span: { + incCounter: (name: string, delta?: number) => { + counters.push({ name, delta }); + }, + } as any, + }; +} + +// Minimal CloudAssembly stand-in. `metadata` is deliberately undefined on the +// stack to reproduce the crash: countAssemblyResults used to call +// Object.values(stack.metadata) with no null guard. +function assemblyWithStack(stack: any): any { + return { + stacksRecursively: [stack], + nestedAssemblies: [], + }; +} + +describe('countAssemblyResults', () => { + test('does not throw when a stack has no metadata', () => { + const { span } = fakeSpan(); + const stack = { + messages: [], + metadata: undefined, // the crashing input + }; + + expect(() => countAssemblyResults(span, assemblyWithStack(stack))).not.toThrow(); + }); + + test('still counts annotation error codes when metadata is present', () => { + const { span, counters } = fakeSpan(); + const stack = { + messages: [], + metadata: { + '/some/path': [{ type: 'aws:cdk:error-code', data: 'MY_ERROR' }], + }, + }; + + countAssemblyResults(span, assemblyWithStack(stack)); + + expect(counters).toContainEqual({ name: 'errorAnn:MY_ERROR', delta: undefined }); + }); +});