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