@@ -42,6 +42,7 @@ import (
4242agplschedule"github.com/coder/coder/v2/coderd/schedule"
4343"github.com/coder/coder/v2/coderd/schedule/cron"
4444"github.com/coder/coder/v2/coderd/util/ptr"
45+ "github.com/coder/coder/v2/coderd/workspacestats"
4546"github.com/coder/coder/v2/codersdk"
4647entaudit"github.com/coder/coder/v2/enterprise/audit"
4748"github.com/coder/coder/v2/enterprise/audit/backends"
@@ -2767,6 +2768,114 @@ func TestPrebuildUpdateLifecycleParams(t *testing.T) {
27672768}
27682769}
27692770
2771+ func TestPrebuildActivityBump (t * testing.T ) {
2772+ t .Parallel ()
2773+
2774+ clock := quartz .NewMock (t )
2775+ clock .Set (dbtime .Now ())
2776+
2777+ // Setup
2778+ log := testutil .Logger (t )
2779+ client ,db ,owner := coderdenttest .NewWithDatabase (t ,& coderdenttest.Options {
2780+ Options :& coderdtest.Options {
2781+ IncludeProvisionerDaemon :true ,
2782+ Clock :clock ,
2783+ },
2784+ LicenseOptions :& coderdenttest.LicenseOptions {
2785+ Features : license.Features {
2786+ codersdk .FeatureWorkspacePrebuilds :1 ,
2787+ },
2788+ },
2789+ })
2790+
2791+ // Given: a template and a template version with preset and a prebuilt workspace
2792+ presetID := uuid .New ()
2793+ version := coderdtest .CreateTemplateVersion (t ,client ,owner .OrganizationID ,nil )
2794+ _ = coderdtest .AwaitTemplateVersionJobCompleted (t ,client ,version .ID )
2795+ // Configure activity bump on the template
2796+ activityBump := time .Hour
2797+ template := coderdtest .CreateTemplate (t ,client ,owner .OrganizationID ,version .ID ,func (ctr * codersdk.CreateTemplateRequest ) {
2798+ ctr .ActivityBumpMillis = ptr.Ref [int64 ](activityBump .Milliseconds ())
2799+ })
2800+ dbgen .Preset (t ,db , database.InsertPresetParams {
2801+ ID :presetID ,
2802+ TemplateVersionID :version .ID ,
2803+ DesiredInstances : sql.NullInt32 {Int32 :1 ,Valid :true },
2804+ })
2805+ // Given: a prebuild with an expired Deadline
2806+ deadline := clock .Now ().Add (- 30 * time .Minute )
2807+ wb := dbfake .WorkspaceBuild (t ,db , database.WorkspaceTable {
2808+ OwnerID :database .PrebuildsSystemUserID ,
2809+ TemplateID :template .ID ,
2810+ }).Seed (database.WorkspaceBuild {
2811+ TemplateVersionID :version .ID ,
2812+ TemplateVersionPresetID : uuid.NullUUID {
2813+ UUID :presetID ,
2814+ Valid :true ,
2815+ },
2816+ Deadline :deadline ,
2817+ }).WithAgent (func (agent []* proto.Agent ) []* proto.Agent {
2818+ return agent
2819+ }).Do ()
2820+
2821+ // Mark the prebuilt workspace's agent as ready so the prebuild can be claimed
2822+ // nolint:gocritic
2823+ ctx := dbauthz .AsSystemRestricted (testutil .Context (t ,testutil .WaitLong ))
2824+ agent ,err := db .GetWorkspaceAgentAndLatestBuildByAuthToken (ctx ,uuid .MustParse (wb .AgentToken ))
2825+ require .NoError (t ,err )
2826+ err = db .UpdateWorkspaceAgentLifecycleStateByID (ctx , database.UpdateWorkspaceAgentLifecycleStateByIDParams {
2827+ ID :agent .WorkspaceAgent .ID ,
2828+ LifecycleState :database .WorkspaceAgentLifecycleStateReady ,
2829+ })
2830+ require .NoError (t ,err )
2831+
2832+ // Given: a prebuilt workspace with a Deadline and an empty MaxDeadline
2833+ prebuild := coderdtest .MustWorkspace (t ,client ,wb .Workspace .ID )
2834+ require .Equal (t ,deadline .UTC (),prebuild .LatestBuild .Deadline .Time .UTC ())
2835+ require .Zero (t ,prebuild .LatestBuild .MaxDeadline )
2836+
2837+ // When: activity bump is applied to an unclaimed prebuild
2838+ workspacestats .ActivityBumpWorkspace (ctx ,log ,db ,prebuild .ID ,clock .Now ().Add (10 * time .Hour ))
2839+
2840+ // Then: prebuild Deadline/MaxDeadline remain unchanged
2841+ prebuild = coderdtest .MustWorkspace (t ,client ,wb .Workspace .ID )
2842+ require .Equal (t ,deadline .UTC (),prebuild .LatestBuild .Deadline .Time .UTC ())
2843+ require .Zero (t ,prebuild .LatestBuild .MaxDeadline )
2844+
2845+ // Given: the prebuilt workspace is claimed by a user
2846+ user ,err := client .User (ctx ,"testUser" )
2847+ require .NoError (t ,err )
2848+ claimedWorkspace ,err := client .CreateUserWorkspace (ctx ,user .ID .String (), codersdk.CreateWorkspaceRequest {
2849+ TemplateVersionID :version .ID ,
2850+ TemplateVersionPresetID :presetID ,
2851+ Name :coderdtest .RandomUsername (t ),
2852+ })
2853+ require .NoError (t ,err )
2854+ coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,claimedWorkspace .LatestBuild .ID )
2855+ workspace := coderdtest .MustWorkspace (t ,client ,claimedWorkspace .ID )
2856+ require .Equal (t ,prebuild .ID ,workspace .ID )
2857+ // Claimed workspaces have an empty Deadline and MaxDeadline
2858+ require .Zero (t ,workspace .LatestBuild .Deadline )
2859+ require .Zero (t ,workspace .LatestBuild .MaxDeadline )
2860+
2861+ // Given: the claimed workspace has an expired Deadline
2862+ err = db .UpdateWorkspaceBuildDeadlineByID (ctx , database.UpdateWorkspaceBuildDeadlineByIDParams {
2863+ ID :workspace .LatestBuild .ID ,
2864+ Deadline :deadline ,
2865+ UpdatedAt :clock .Now (),
2866+ })
2867+ require .NoError (t ,err )
2868+ workspace = coderdtest .MustWorkspace (t ,client ,claimedWorkspace .ID )
2869+
2870+ // When: activity bump is applied to a claimed prebuild
2871+ workspacestats .ActivityBumpWorkspace (ctx ,log ,db ,workspace .ID ,clock .Now ().Add (10 * time .Hour ))
2872+
2873+ // Then: Deadline is extended by the activity bump, MaxDeadline remains unset
2874+ workspace = coderdtest .MustWorkspace (t ,client ,claimedWorkspace .ID )
2875+ require .WithinDuration (t ,clock .Now ().Add (activityBump ).UTC (),workspace .LatestBuild .Deadline .Time .UTC (),testutil .WaitMedium )
2876+ require .Zero (t ,workspace .LatestBuild .MaxDeadline )
2877+ }
2878+
27702879// TestWorkspaceTemplateParamsChange tests a workspace with a parameter that
27712880// validation changes on apply. The params used in create workspace are invalid
27722881// according to the static params on import.