@@ -425,6 +425,174 @@ func TestWorkspaceAgentAppStatus(t *testing.T) {
425425 })
426426}
427427
428+ func TestWorkspaceAgentAppStatus_ActivityBump (t * testing.T ) {
429+ t .Parallel ()
430+
431+ client , db := coderdtest .NewWithDatabase (t , nil )
432+ user := coderdtest .CreateFirstUser (t , client )
433+
434+ tests := []struct {
435+ name string
436+ prevState * codersdk.WorkspaceAppStatusState // nil means no previous state
437+ newState codersdk.WorkspaceAppStatusState
438+ shouldBump bool
439+ }{
440+ {
441+ name : "FirstStatusBumps" ,
442+ prevState : nil ,
443+ newState : codersdk .WorkspaceAppStatusStateWorking ,
444+ shouldBump : true ,
445+ },
446+ {
447+ name : "WorkingToIdleBumps" ,
448+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateWorking ),
449+ newState : codersdk .WorkspaceAppStatusStateIdle ,
450+ shouldBump : true ,
451+ },
452+ {
453+ name : "WorkingToCompleteBumps" ,
454+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateWorking ),
455+ newState : codersdk .WorkspaceAppStatusStateComplete ,
456+ shouldBump : true ,
457+ },
458+ {
459+ name : "CompleteToIdleNoBump" ,
460+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateComplete ),
461+ newState : codersdk .WorkspaceAppStatusStateIdle ,
462+ shouldBump : false ,
463+ },
464+ {
465+ name : "CompleteToCompleteNoBump" ,
466+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateComplete ),
467+ newState : codersdk .WorkspaceAppStatusStateComplete ,
468+ shouldBump : false ,
469+ },
470+ {
471+ name : "FailureToIdleNoBump" ,
472+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateFailure ),
473+ newState : codersdk .WorkspaceAppStatusStateIdle ,
474+ shouldBump : false ,
475+ },
476+ {
477+ name : "FailureToFailureNoBump" ,
478+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateFailure ),
479+ newState : codersdk .WorkspaceAppStatusStateFailure ,
480+ shouldBump : false ,
481+ },
482+ {
483+ name : "CompleteToWorkingBumps" ,
484+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateComplete ),
485+ newState : codersdk .WorkspaceAppStatusStateWorking ,
486+ shouldBump : true ,
487+ },
488+ {
489+ name : "FailureToCompleteNoBump" ,
490+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateFailure ),
491+ newState : codersdk .WorkspaceAppStatusStateComplete ,
492+ shouldBump : false ,
493+ },
494+ {
495+ name : "WorkingToFailureBumps" ,
496+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateWorking ),
497+ newState : codersdk .WorkspaceAppStatusStateFailure ,
498+ shouldBump : true ,
499+ },
500+ {
501+ name : "IdleToIdleNoBump" ,
502+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateIdle ),
503+ newState : codersdk .WorkspaceAppStatusStateIdle ,
504+ shouldBump : false ,
505+ },
506+ {
507+ name : "IdleToWorkingBumps" ,
508+ prevState : ptr .Ref (codersdk .WorkspaceAppStatusStateIdle ),
509+ newState : codersdk .WorkspaceAppStatusStateWorking ,
510+ shouldBump : true ,
511+ },
512+ }
513+
514+ for _ , tt := range tests {
515+ tt := tt
516+ t .Run (tt .name , func (t * testing.T ) {
517+ t .Parallel ()
518+
519+ // Create workspace with agent and app.
520+ r := dbfake .WorkspaceBuild (t , db , database.WorkspaceTable {
521+ OrganizationID : user .OrganizationID ,
522+ OwnerID : user .UserID ,
523+ }).WithAgent (func (a []* proto.Agent ) []* proto.Agent {
524+ a [0 ].Apps = []* proto.App {{Slug : "test-app" }}
525+ return a
526+ }).Do ()
527+
528+ ctx := testutil .Context (t , testutil .WaitMedium )
529+
530+ // Configure template with activity_bump to enable deadline bumping.
531+ _ , err := client .UpdateTemplateMeta (ctx , r .Template .ID , codersdk.UpdateTemplateMeta {
532+ ActivityBumpMillis : time .Hour .Milliseconds (),
533+ })
534+ require .NoError (t , err )
535+
536+ // Set the workspace build deadline to the past to ensure the 5%
537+ // threshold is met for activity bumping.
538+ pastDeadline := dbtime .Now ().Add (- 30 * time .Minute )
539+ err = db .UpdateWorkspaceBuildDeadlineByID (dbauthz .AsSystemRestricted (ctx ), database.UpdateWorkspaceBuildDeadlineByIDParams {
540+ ID : r .Build .ID ,
541+ UpdatedAt : dbtime .Now (),
542+ Deadline : pastDeadline ,
543+ MaxDeadline : time.Time {},
544+ })
545+ require .NoError (t , err )
546+
547+ agentClient := agentsdk .New (client .URL , agentsdk .WithFixedToken (r .AgentToken ))
548+
549+ // If there's a previous state, report it first.
550+ if tt .prevState != nil {
551+ err := agentClient .PatchAppStatus (ctx , agentsdk.PatchAppStatus {
552+ AppSlug : "test-app" ,
553+ State : * tt .prevState ,
554+ Message : "previous state" ,
555+ })
556+ require .NoError (t , err )
557+
558+ // Reset deadline to past again to meet 5% threshold for next bump.
559+ err = db .UpdateWorkspaceBuildDeadlineByID (dbauthz .AsSystemRestricted (ctx ), database.UpdateWorkspaceBuildDeadlineByIDParams {
560+ ID : r .Build .ID ,
561+ UpdatedAt : dbtime .Now (),
562+ Deadline : pastDeadline ,
563+ MaxDeadline : time.Time {},
564+ })
565+ require .NoError (t , err )
566+ }
567+
568+ // Get the deadline before the new status report.
569+ beforeBuild , err := db .GetWorkspaceBuildByID (dbauthz .AsSystemRestricted (ctx ), r .Build .ID )
570+ require .NoError (t , err )
571+ beforeDeadline := beforeBuild .Deadline
572+
573+ // Report the new state.
574+ err = agentClient .PatchAppStatus (ctx , agentsdk.PatchAppStatus {
575+ AppSlug : "test-app" ,
576+ State : tt .newState ,
577+ Message : "new state" ,
578+ })
579+ require .NoError (t , err )
580+
581+ // Check if deadline changed.
582+ afterBuild , err := db .GetWorkspaceBuildByID (dbauthz .AsSystemRestricted (ctx ), r .Build .ID )
583+ require .NoError (t , err )
584+ afterDeadline := afterBuild .Deadline
585+
586+ didBump := afterDeadline .After (beforeDeadline )
587+ if tt .shouldBump {
588+ require .True (t , didBump , "wanted deadline to bump but it didn't" )
589+ } else {
590+ require .False (t , didBump , "wanted deadline not to bump but it did" )
591+ }
592+ })
593+ }
594+ }
595+
428596func TestWorkspaceAgentConnectRPC (t * testing.T ) {
429597 t .Parallel ()
430598
0 commit comments