@@ -837,6 +837,73 @@ func TestWorkspaceAutobuild(t *testing.T) {
837837 require .True (t , ws .LastUsedAt .After (dormantLastUsedAt ))
838838 })
839839
840+ // This test has been added to ensure we don't introduce a regression
841+ // to this issue https://github.com/coder/coder/issues/20711.
842+ t .Run ("DormantAutostop" , func (t * testing.T ) {
843+ t .Parallel ()
844+
845+ var (
846+ ticker = make (chan time.Time )
847+ statCh = make (chan autobuild.Stats )
848+ inactiveTTL = time .Minute
849+ logger = slogtest .Make (t , & slogtest.Options {IgnoreErrors : true }).Leveled (slog .LevelDebug )
850+ )
851+
852+ client , db , user := coderdenttest .NewWithDatabase (t , & coderdenttest.Options {
853+ Options : & coderdtest.Options {
854+ AutobuildTicker : ticker ,
855+ AutobuildStats : statCh ,
856+ IncludeProvisionerDaemon : true ,
857+ TemplateScheduleStore : schedule .NewEnterpriseTemplateScheduleStore (agplUserQuietHoursScheduleStore (), notifications .NewNoopEnqueuer (), logger , nil ),
858+ },
859+ LicenseOptions : & coderdenttest.LicenseOptions {
860+ Features : license.Features {codersdk .FeatureAdvancedTemplateScheduling : 1 },
861+ },
862+ })
863+
864+ // Create a template version that includes agents on both start AND stop builds.
865+ // This simulates a template without `count = data.coder_workspace.me.start_count`.
866+ authToken := uuid .NewString ()
867+ version := coderdtest .CreateTemplateVersion (t , client , user .OrganizationID , & echo.Responses {
868+ Parse : echo .ParseComplete ,
869+ ProvisionPlan : echo .PlanComplete ,
870+ ProvisionApply : echo .ProvisionApplyWithAgent (authToken ),
871+ })
872+
873+ template := coderdtest .CreateTemplate (t , client , user .OrganizationID , version .ID , func (ctr * codersdk.CreateTemplateRequest ) {
874+ ctr .TimeTilDormantMillis = ptr.Ref [int64 ](inactiveTTL .Milliseconds ())
875+ })
876+
877+ coderdtest .AwaitTemplateVersionJobCompleted (t , client , version .ID )
878+ ws := coderdtest .CreateWorkspace (t , client , template .ID )
879+ build := coderdtest .AwaitWorkspaceBuildJobCompleted (t , client , ws .LatestBuild .ID )
880+ require .Equal (t , codersdk .WorkspaceStatusRunning , build .Status )
881+
882+ // Simulate the workspace becoming inactive and transitioning to dormant.
883+ tickTime := ws .LastUsedAt .Add (inactiveTTL * 2 )
884+
885+ p , err := coderdtest .GetProvisionerForTags (db , time .Now (), ws .OrganizationID , nil )
886+ require .NoError (t , err )
887+ coderdtest .UpdateProvisionerLastSeenAt (t , db , p .ID , tickTime )
888+ ticker <- tickTime
889+ stats := <- statCh
890+
891+ // Expect workspace to transition to stopped state.
892+ require .Len (t , stats .Transitions , 1 )
893+ require .Equal (t , stats .Transitions [ws .ID ], database .WorkspaceTransitionStop )
894+
895+ // The autostop build should succeed even though the template includes
896+ // agents without `count = data.coder_workspace.me.start_count`.
897+ // This verifies that provisionerd has permission to create agents on
898+ // dormant workspaces during stop builds.
899+ ws = coderdtest .MustWorkspace (t , client , ws .ID )
900+ require .NotNil (t , ws .DormantAt , "workspace should be marked as dormant" )
901+ require .Equal (t , codersdk .WorkspaceTransitionStop , ws .LatestBuild .Transition )
902+
903+ latestBuild := coderdtest .AwaitWorkspaceBuildJobCompleted (t , client , ws .LatestBuild .ID )
904+ require .Equal (t , codersdk .WorkspaceStatusStopped , latestBuild .Status )
905+ })
906+
840907 // This test serves as a regression prevention for generating
841908 // audit logs in the same transaction the transition workspaces to
842909 // the dormant state. The auditor that is passed to autobuild does
0 commit comments