@@ -5,8 +5,10 @@ package stackruntime
55
66import (
77 "context"
8+ "fmt"
89 "path"
910 "sort"
11+ "strings"
1012 "testing"
1113 "time"
1214
@@ -501,6 +503,241 @@ func TestApplyWithCheckableObjects(t *testing.T) {
501503 }
502504}
503505
506+ func TestApplyWithForcePlanTimestamp (t * testing.T ) {
507+ ctx := context .Background ()
508+ cfg := loadMainBundleConfigForTest (t , "with-plantimestamp" )
509+
510+ forcedPlanTimestamp := "1991-08-25T20:57:08Z"
511+ fakePlanTimestamp , err := time .Parse (time .RFC3339 , forcedPlanTimestamp )
512+ if err != nil {
513+ t .Fatal (err )
514+ }
515+
516+ changesCh := make (chan stackplan.PlannedChange )
517+ diagsCh := make (chan tfdiags.Diagnostic )
518+ req := PlanRequest {
519+ Config : cfg ,
520+ ProviderFactories : map [addrs.Provider ]providers.Factory {
521+ addrs .NewDefaultProvider ("testing" ): func () (providers.Interface , error ) {
522+ return stacks_testing_provider .NewProvider (), nil
523+ },
524+ },
525+ ForcePlanTimestamp : & fakePlanTimestamp ,
526+ }
527+ resp := PlanResponse {
528+ PlannedChanges : changesCh ,
529+ Diagnostics : diagsCh ,
530+ }
531+
532+ go Plan (ctx , & req , & resp )
533+ planChanges , diags := collectPlanOutput (changesCh , diagsCh )
534+ if len (diags ) > 0 {
535+ t .Fatalf ("expected no diagnostics, got %s" , diags .ErrWithWarnings ())
536+ }
537+ // Sanity check that the plan timestamp was set correctly
538+ output := expectOutput (t , "plantimestamp" , planChanges )
539+ plantimestampValue , err := output .NewValue .Decode (cty .String )
540+ if err != nil {
541+ t .Fatal (err )
542+ }
543+
544+ if plantimestampValue .AsString () != forcedPlanTimestamp {
545+ t .Errorf ("expected plantimestamp to be %q, got %q" , forcedPlanTimestamp , plantimestampValue .AsString ())
546+ }
547+
548+ var raw []* anypb.Any
549+ for _ , change := range planChanges {
550+ proto , err := change .PlannedChangeProto ()
551+ if err != nil {
552+ t .Fatal (err )
553+ }
554+ raw = append (raw , proto .Raw ... )
555+ }
556+
557+ applyReq := ApplyRequest {
558+ Config : cfg ,
559+ RawPlan : raw ,
560+ ProviderFactories : map [addrs.Provider ]providers.Factory {
561+ addrs .NewDefaultProvider ("testing" ): func () (providers.Interface , error ) {
562+ return stacks_testing_provider .NewProvider (), nil
563+ },
564+ },
565+ }
566+
567+ applyChangesCh := make (chan stackstate.AppliedChange )
568+ diagsCh = make (chan tfdiags.Diagnostic )
569+
570+ applyResp := ApplyResponse {
571+ AppliedChanges : applyChangesCh ,
572+ Diagnostics : diagsCh ,
573+ }
574+
575+ go Apply (ctx , & applyReq , & applyResp )
576+ applyChanges , applyDiags := collectApplyOutput (applyChangesCh , diagsCh )
577+ if len (applyDiags ) > 0 {
578+ t .Fatalf ("expected no diagnostics, got %s" , applyDiags .ErrWithWarnings ())
579+ }
580+
581+ wantChanges := []stackstate.AppliedChange {
582+ & stackstate.AppliedChangeComponentInstance {
583+ ComponentAddr : stackaddrs.AbsComponent {
584+ Item : stackaddrs.Component {
585+ Name : "second-self" ,
586+ },
587+ },
588+ ComponentInstanceAddr : stackaddrs.AbsComponentInstance {
589+ Item : stackaddrs.ComponentInstance {
590+ Component : stackaddrs.Component {
591+ Name : "second-self" ,
592+ },
593+ },
594+ },
595+ OutputValues : map [addrs.OutputValue ]cty.Value {
596+ // We want to make sure the plantimestamp is set correctly
597+ {Name : "input" }: cty .StringVal (forcedPlanTimestamp ),
598+ // plantimestamp should also be set for the module runtime used in the components
599+ {Name : "out" }: cty .StringVal (fmt .Sprintf ("module-output-%s" , forcedPlanTimestamp )),
600+ },
601+ },
602+ & stackstate.AppliedChangeComponentInstance {
603+ ComponentAddr : stackaddrs.AbsComponent {
604+ Item : stackaddrs.Component {
605+ Name : "self" ,
606+ },
607+ },
608+ ComponentInstanceAddr : stackaddrs.AbsComponentInstance {
609+ Item : stackaddrs.ComponentInstance {
610+ Component : stackaddrs.Component {
611+ Name : "self" ,
612+ },
613+ },
614+ },
615+ OutputValues : map [addrs.OutputValue ]cty.Value {
616+ // We want to make sure the plantimestamp is set correctly
617+ {Name : "input" }: cty .StringVal (forcedPlanTimestamp ),
618+ // plantimestamp should also be set for the module runtime used in the components
619+ {Name : "out" }: cty .StringVal (fmt .Sprintf ("module-output-%s" , forcedPlanTimestamp )),
620+ },
621+ },
622+ }
623+
624+ sort .SliceStable (applyChanges , func (i , j int ) bool {
625+ return appliedChangeSortKey (applyChanges [i ]) < appliedChangeSortKey (applyChanges [j ])
626+ })
627+
628+ if diff := cmp .Diff (wantChanges , applyChanges , ctydebug .CmpOptions , cmpCollectionsSet ); diff != "" {
629+ t .Errorf ("wrong changes\n %s" , diff )
630+ }
631+ }
632+
633+ func TestApplyWithDefaultPlanTimestamp (t * testing.T ) {
634+ ctx := context .Background ()
635+ cfg := loadMainBundleConfigForTest (t , "with-plantimestamp" )
636+
637+ dayOfWritingThisTest := "2024-06-21T06:37:08Z"
638+ dayOfWritingThisTestTime , err := time .Parse (time .RFC3339 , dayOfWritingThisTest )
639+ if err != nil {
640+ t .Fatal (err )
641+ }
642+
643+ changesCh := make (chan stackplan.PlannedChange )
644+ diagsCh := make (chan tfdiags.Diagnostic )
645+ req := PlanRequest {
646+ Config : cfg ,
647+ ProviderFactories : map [addrs.Provider ]providers.Factory {
648+ addrs .NewDefaultProvider ("testing" ): func () (providers.Interface , error ) {
649+ return stacks_testing_provider .NewProvider (), nil
650+ },
651+ },
652+ }
653+ resp := PlanResponse {
654+ PlannedChanges : changesCh ,
655+ Diagnostics : diagsCh ,
656+ }
657+
658+ go Plan (ctx , & req , & resp )
659+ planChanges , diags := collectPlanOutput (changesCh , diagsCh )
660+ if len (diags ) > 0 {
661+ t .Fatalf ("expected no diagnostics, got %s" , diags .ErrWithWarnings ())
662+ }
663+ // Sanity check that the plan timestamp was set correctly
664+ output := expectOutput (t , "plantimestamp" , planChanges )
665+ plantimestampValue , err := output .NewValue .Decode (cty .String )
666+ if err != nil {
667+ t .Fatal (err )
668+ }
669+
670+ plantimestamp , err := time .Parse (time .RFC3339 , plantimestampValue .AsString ())
671+ if err != nil {
672+ t .Fatal (err )
673+ }
674+
675+ if plantimestamp .Before (dayOfWritingThisTestTime ) {
676+ t .Errorf ("expected plantimestamp to be later than %q, got %q" , dayOfWritingThisTest , plantimestampValue .AsString ())
677+ }
678+
679+ var raw []* anypb.Any
680+ for _ , change := range planChanges {
681+ proto , err := change .PlannedChangeProto ()
682+ if err != nil {
683+ t .Fatal (err )
684+ }
685+ raw = append (raw , proto .Raw ... )
686+ }
687+
688+ applyReq := ApplyRequest {
689+ Config : cfg ,
690+ RawPlan : raw ,
691+ ProviderFactories : map [addrs.Provider ]providers.Factory {
692+ addrs .NewDefaultProvider ("testing" ): func () (providers.Interface , error ) {
693+ return stacks_testing_provider .NewProvider (), nil
694+ },
695+ },
696+ }
697+
698+ applyChangesCh := make (chan stackstate.AppliedChange )
699+ diagsCh = make (chan tfdiags.Diagnostic )
700+
701+ applyResp := ApplyResponse {
702+ AppliedChanges : applyChangesCh ,
703+ Diagnostics : diagsCh ,
704+ }
705+
706+ go Apply (ctx , & applyReq , & applyResp )
707+ applyChanges , applyDiags := collectApplyOutput (applyChangesCh , diagsCh )
708+ if len (applyDiags ) > 0 {
709+ t .Fatalf ("expected no diagnostics, got %s" , applyDiags .ErrWithWarnings ())
710+ }
711+
712+ for _ , x := range applyChanges {
713+ if v , ok := x .(* stackstate.AppliedChangeComponentInstance ); ok {
714+ if actualTimestampValue , ok := v .OutputValues [addrs.OutputValue {
715+ Name : "input" ,
716+ }]; ok {
717+ actualTimestamp , err := time .Parse (time .RFC3339 , actualTimestampValue .AsString ())
718+ if err != nil {
719+ t .Fatalf ("Could not parse component output value: %q" , err )
720+ }
721+ if actualTimestamp .Before (dayOfWritingThisTestTime ) {
722+ t .Error ("Timestamp is before day of writing this test, that should be incorrect." )
723+ }
724+ }
725+
726+ if actualTimestampValue , ok := v .OutputValues [addrs.OutputValue {
727+ Name : "out" ,
728+ }]; ok {
729+ actualTimestamp , err := time .Parse (time .RFC3339 , strings .ReplaceAll (actualTimestampValue .AsString (), "module-output-" , "" ))
730+ if err != nil {
731+ t .Fatalf ("Could not parse component output value: %q" , err )
732+ }
733+ if actualTimestamp .Before (dayOfWritingThisTestTime ) {
734+ t .Error ("Timestamp is before day of writing this test, that should be incorrect." )
735+ }
736+ }
737+ }
738+ }
739+ }
740+
504741func collectApplyOutput (changesCh <- chan stackstate.AppliedChange , diagsCh <- chan tfdiags.Diagnostic ) ([]stackstate.AppliedChange , tfdiags.Diagnostics ) {
505742 var changes []stackstate.AppliedChange
506743 var diags tfdiags.Diagnostics
0 commit comments