@@ -426,6 +426,16 @@ describe('Change Set Formatter', () => {
426426 ResourceType : 'AWS::DynamoDB::Table' ,
427427 Replacement : 'True' ,
428428 Scope : [ 'Properties' ] ,
429+ BeforeContext : JSON . stringify ( {
430+ Properties : {
431+ BillingMode : 'PROVISIONED'
432+ }
433+ } ) ,
434+ AfterContext : JSON . stringify ( {
435+ Properties : {
436+ BillingMode : 'PAY_PER_REQUEST'
437+ }
438+ } ) ,
429439 Details : [
430440 {
431441 Target : {
@@ -458,164 +468,135 @@ describe('Change Set Formatter', () => {
458468 )
459469 expect ( markdown ) . toContain ( '**Physical ID:** `my-table-123`' )
460470 expect ( markdown ) . toContain ( '⚠️ **This resource will be replaced**' )
461- expect ( markdown ) . toContain (
462- '**BillingMode:** `PROVISIONED` → `PAY_PER_REQUEST`'
463- )
471+ expect ( markdown ) . toContain ( '```diff' )
464472 expect ( markdown ) . toContain ( '⚠️ Requires recreation: Always' )
465473 } )
466474
467- test ( 'diffs Tags arrays correctly ' , ( ) => {
475+ test ( 'displays AfterContext for Add actions in console output ' , ( ) => {
468476 const changesSummary = JSON . stringify ( {
469477 changes : [
470478 {
471479 Type : 'Resource' ,
472480 ResourceChange : {
473- Action : 'Modify' ,
474- LogicalResourceId : 'MyParameter' ,
475- ResourceType : 'AWS::SSM::Parameter' ,
476- Replacement : 'False' ,
477- Scope : [ 'Properties' ] ,
478- Details : [
479- {
480- Target : {
481- Attribute : 'Properties' ,
482- Name : 'Tags' ,
483- RequiresRecreation : 'Never' ,
484- BeforeValue : JSON . stringify ( [
485- { Key : 'Version' , Value : 'v1' } ,
486- { Key : 'Team' , Value : 'DevOps' } ,
487- { Key : 'Environment' , Value : 'test' }
488- ] ) ,
489- AfterValue : JSON . stringify ( [
490- { Key : 'Version' , Value : 'v2' } ,
491- { Key : 'UpdateType' , Value : 'InPlace' } ,
492- { Key : 'Environment' , Value : 'production' }
493- ] )
494- }
495- }
496- ]
481+ Action : 'Add' ,
482+ LogicalResourceId : 'NewBucket' ,
483+ ResourceType : 'AWS::S3::Bucket' ,
484+ AfterContext :
485+ '{"BucketName":"my-bucket","Versioning":{"Status":"Enabled"}}'
497486 }
498487 }
499488 ] ,
500489 totalChanges : 1 ,
501490 truncated : false
502491 } )
503492
504- const markdown = generateChangeSetMarkdown ( changesSummary )
493+ displayChangeSet ( changesSummary , 1 , true )
505494
506- expect ( markdown ) . toContain ( '**Tags:**' )
507- expect ( markdown ) . toContain ( '**Tags.Environment:** `test` → `production`' )
508- expect ( markdown ) . toContain ( '**Tags.Team:** `DevOps` → (removed)' )
509- expect ( markdown ) . toContain ( '**Tags.UpdateType:** (added) → `InPlace`' )
510- expect ( markdown ) . toContain ( '**Tags.Version:** `v1` → `v2`' )
495+ expect ( core . info ) . toHaveBeenCalledWith (
496+ expect . stringContaining ( 'Properties:' )
497+ )
498+ expect ( core . info ) . toHaveBeenCalledWith (
499+ expect . stringContaining ( 'BucketName' )
500+ )
511501 } )
512502
513- test ( 'diffs nested objects correctly ' , ( ) => {
503+ test ( 'displays BeforeContext for Remove actions in console output ' , ( ) => {
514504 const changesSummary = JSON . stringify ( {
515505 changes : [
516506 {
517507 Type : 'Resource' ,
518508 ResourceChange : {
519- Action : 'Modify' ,
520- LogicalResourceId : 'MyResource' ,
509+ Action : 'Remove' ,
510+ LogicalResourceId : 'OldBucket' ,
511+ ResourceType : 'AWS::S3::Bucket' ,
512+ BeforeContext : '{"BucketName":"old-bucket"}'
513+ }
514+ }
515+ ] ,
516+ totalChanges : 1 ,
517+ truncated : false
518+ } )
519+
520+ displayChangeSet ( changesSummary , 1 , true )
521+
522+ expect ( core . info ) . toHaveBeenCalledWith (
523+ expect . stringContaining ( 'Properties:' )
524+ )
525+ expect ( core . info ) . toHaveBeenCalledWith (
526+ expect . stringContaining ( 'BucketName' )
527+ )
528+ } )
529+
530+ test ( 'handles invalid JSON in AfterContext gracefully' , ( ) => {
531+ const changesSummary = JSON . stringify ( {
532+ changes : [
533+ {
534+ Type : 'Resource' ,
535+ ResourceChange : {
536+ Action : 'Add' ,
537+ LogicalResourceId : 'NewResource' ,
521538 ResourceType : 'AWS::Custom::Resource' ,
522- Replacement : 'False' ,
523- Scope : [ 'Properties' ] ,
524- Details : [
525- {
526- Target : {
527- Attribute : 'Properties' ,
528- Name : 'Config' ,
529- RequiresRecreation : 'Never' ,
530- BeforeValue : JSON . stringify ( {
531- Setting : 'old' ,
532- Nested : { Value : 'a' }
533- } ) ,
534- AfterValue : JSON . stringify ( {
535- Setting : 'new' ,
536- Nested : { Value : 'b' }
537- } )
538- }
539- }
540- ]
539+ AfterContext : 'invalid-json{'
541540 }
542541 }
543542 ] ,
544543 totalChanges : 1 ,
545544 truncated : false
546545 } )
547546
548- const markdown = generateChangeSetMarkdown ( changesSummary )
547+ displayChangeSet ( changesSummary , 1 , true )
549548
550- expect ( markdown ) . toContain ( '**Config.Setting:** `old` → `new`' )
551- expect ( markdown ) . toContain ( '**Config.Nested.Value:** `a` → `b`' )
549+ expect ( core . info ) . toHaveBeenCalledWith (
550+ expect . stringContaining ( 'invalid-json{' )
551+ )
552552 } )
553553
554- test ( 'handles generic arrays as JSON strings ' , ( ) => {
554+ test ( 'handles invalid JSON in BeforeContext gracefully ' , ( ) => {
555555 const changesSummary = JSON . stringify ( {
556556 changes : [
557557 {
558558 Type : 'Resource' ,
559559 ResourceChange : {
560- Action : 'Modify ' ,
561- LogicalResourceId : 'MyResource ' ,
560+ Action : 'Remove ' ,
561+ LogicalResourceId : 'OldResource ' ,
562562 ResourceType : 'AWS::Custom::Resource' ,
563- Replacement : 'False' ,
564- Scope : [ 'Properties' ] ,
565- Details : [
566- {
567- Target : {
568- Attribute : 'Properties' ,
569- Name : 'Items' ,
570- RequiresRecreation : 'Never' ,
571- BeforeValue : JSON . stringify ( [ 'a' , 'b' ] ) ,
572- AfterValue : JSON . stringify ( [ 'a' , 'c' ] )
573- }
574- }
575- ]
563+ BeforeContext : 'invalid-json{'
576564 }
577565 }
578566 ] ,
579567 totalChanges : 1 ,
580568 truncated : false
581569 } )
582570
583- const markdown = generateChangeSetMarkdown ( changesSummary )
571+ displayChangeSet ( changesSummary , 1 , true )
584572
585- expect ( markdown ) . toContain ( '**Items:**' )
586- expect ( markdown ) . toContain ( '["a","b"] ')
587- expect ( markdown ) . toContain ( '["a","c"]' )
573+ expect ( core . info ) . toHaveBeenCalledWith (
574+ expect . stringContaining ( 'invalid-json{ ')
575+ )
588576 } )
589577
590- test ( 'handles array additions and removals ' , ( ) => {
578+ test ( 'generates diff view for resources with BeforeContext/AfterContext ' , ( ) => {
591579 const changesSummary = JSON . stringify ( {
592580 changes : [
593581 {
594582 Type : 'Resource' ,
595583 ResourceChange : {
596584 Action : 'Modify' ,
597- LogicalResourceId : 'MyResource ' ,
598- ResourceType : 'AWS::Custom::Resource ' ,
585+ LogicalResourceId : 'MyTopic ' ,
586+ ResourceType : 'AWS::SNS::Topic ' ,
599587 Replacement : 'False' ,
600- Scope : [ 'Properties' ] ,
601- Details : [
602- {
603- Target : {
604- Attribute : 'Properties' ,
605- Name : 'NewList' ,
606- RequiresRecreation : 'Never' ,
607- AfterValue : JSON . stringify ( [ 'x' , 'y' ] )
608- }
609- } ,
610- {
611- Target : {
612- Attribute : 'Properties' ,
613- Name : 'OldList' ,
614- RequiresRecreation : 'Never' ,
615- BeforeValue : JSON . stringify ( [ 'a' , 'b' ] )
616- }
588+ BeforeContext : JSON . stringify ( {
589+ Properties : {
590+ DisplayName : 'old-name' ,
591+ Tags : [ { Key : 'Env' , Value : 'dev' } ]
617592 }
618- ]
593+ } ) ,
594+ AfterContext : JSON . stringify ( {
595+ Properties : {
596+ DisplayName : 'new-name' ,
597+ Tags : [ { Key : 'Env' , Value : 'prod' } ]
598+ }
599+ } )
619600 }
620601 }
621602 ] ,
@@ -625,36 +606,38 @@ describe('Change Set Formatter', () => {
625606
626607 const markdown = generateChangeSetMarkdown ( changesSummary )
627608
628- expect ( markdown ) . toContain ( '**NewList:** (added) → `["x","y"]`' )
629- expect ( markdown ) . toContain ( '**OldList:** `["a","b"]` → (removed)' )
609+ expect ( markdown ) . toContain ( '```diff' )
610+ expect ( markdown ) . toContain ( '-' )
611+ expect ( markdown ) . toContain ( '+' )
630612 } )
631613
632- test ( 'handles primitive value additions and removals ' , ( ) => {
614+ test ( 'shows recreation warnings in diff view ' , ( ) => {
633615 const changesSummary = JSON . stringify ( {
634616 changes : [
635617 {
636618 Type : 'Resource' ,
637619 ResourceChange : {
638620 Action : 'Modify' ,
639- LogicalResourceId : 'MyResource' ,
640- ResourceType : 'AWS::Custom::Resource' ,
641- Replacement : 'False' ,
642- Scope : [ 'Properties' ] ,
621+ LogicalResourceId : 'MyParam' ,
622+ ResourceType : 'AWS::SSM::Parameter' ,
623+ Replacement : 'True' ,
624+ BeforeContext : JSON . stringify ( {
625+ Properties : {
626+ Name : '/old/path' ,
627+ Value : 'old'
628+ }
629+ } ) ,
630+ AfterContext : JSON . stringify ( {
631+ Properties : {
632+ Name : '/new/path' ,
633+ Value : 'new'
634+ }
635+ } ) ,
643636 Details : [
644637 {
645638 Target : {
646- Attribute : 'Properties' ,
647- Name : 'NewProp' ,
648- RequiresRecreation : 'Never' ,
649- AfterValue : 'new-value'
650- }
651- } ,
652- {
653- Target : {
654- Attribute : 'Properties' ,
655- Name : 'OldProp' ,
656- RequiresRecreation : 'Never' ,
657- BeforeValue : 'old-value'
639+ Name : 'Name' ,
640+ RequiresRecreation : 'Always'
658641 }
659642 }
660643 ]
@@ -667,8 +650,8 @@ describe('Change Set Formatter', () => {
667650
668651 const markdown = generateChangeSetMarkdown ( changesSummary )
669652
670- expect ( markdown ) . toContain ( '**NewProp:** (added) → `new-value` ' )
671- expect ( markdown ) . toContain ( '**OldProp:** `old-value` → (removed) ' )
653+ expect ( markdown ) . toContain ( '```diff ' )
654+ expect ( markdown ) . toContain ( '⚠️ Requires recreation: Always ' )
672655 } )
673656
674657 test ( 'displays AfterContext for Add actions in console output' , ( ) => {
0 commit comments