Skip to content

Commit 837b02a

Browse files
committed
refactor: use api clock mock when creating workspace
1 parent 5781268 commit 837b02a

File tree

10 files changed

+53
-28
lines changed

10 files changed

+53
-28
lines changed

coderd/autobuild/lifecycle_executor_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,7 @@ func TestExecutorPrebuilds(t *testing.T) {
12611261
// Given: a user claims the prebuilt workspace
12621262
dbWorkspace := dbgen.ClaimPrebuild(
12631263
t, db,
1264+
clock.Now(),
12641265
user.ID,
12651266
"claimedWorkspace-autostop",
12661267
preset.ID,
@@ -1362,6 +1363,7 @@ func TestExecutorPrebuilds(t *testing.T) {
13621363
// Given: a user claims the prebuilt workspace
13631364
dbWorkspace := dbgen.ClaimPrebuild(
13641365
t, db,
1366+
clock.Now(),
13651367
user.ID,
13661368
"claimedWorkspace-autostart",
13671369
preset.ID,
@@ -1500,8 +1502,8 @@ func setupTestDBWorkspaceBuild(
15001502
Architecture: "i386",
15011503
OperatingSystem: "linux",
15021504
LifecycleState: database.WorkspaceAgentLifecycleStateReady,
1503-
StartedAt: sql.NullTime{Time: time.Now().Add(time.Hour), Valid: true},
1504-
ReadyAt: sql.NullTime{Time: time.Now().Add(-1 * time.Hour), Valid: true},
1505+
StartedAt: sql.NullTime{Time: clock.Now().Add(time.Hour), Valid: true},
1506+
ReadyAt: sql.NullTime{Time: clock.Now().Add(-1 * time.Hour), Valid: true},
15051507
APIKeyScope: database.AgentKeyScopeEnumAll,
15061508
})
15071509

@@ -1538,8 +1540,9 @@ func setupTestDBPrebuiltWorkspace(
15381540
OrganizationID: orgID,
15391541
OwnerID: database.PrebuildsSystemUserID,
15401542
Deleted: false,
1541-
CreatedAt: time.Now().Add(-time.Hour * 2),
1543+
CreatedAt: clock.Now().Add(-time.Hour * 2),
15421544
AutostartSchedule: options.AutostartSchedule,
1545+
LastUsedAt: clock.Now(),
15431546
})
15441547
setupTestDBWorkspaceBuild(ctx, t, clock, db, ps, orgID, workspace.ID, templateVersionID, presetID, buildTransition)
15451548

coderd/database/dbgen/dbgen.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,7 @@ func UserSecret(t testing.TB, db database.Store, seed database.UserSecret) datab
14391439
func ClaimPrebuild(
14401440
t testing.TB,
14411441
db database.Store,
1442+
now time.Time,
14421443
newUserID uuid.UUID,
14431444
newName string,
14441445
presetID uuid.UUID,
@@ -1449,6 +1450,7 @@ func ClaimPrebuild(
14491450
claimedWorkspace, err := db.ClaimPrebuiltWorkspace(genCtx, database.ClaimPrebuiltWorkspaceParams{
14501451
NewUserID: newUserID,
14511452
NewName: newName,
1453+
Now: now,
14521454
PresetID: presetID,
14531455
AutostartSchedule: autostartSchedule,
14541456
NextStartAt: nextStartAt,

coderd/database/queries.sql.go

Lines changed: 8 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/prebuilds.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
UPDATE workspaces w
33
SET owner_id = @new_user_id::uuid,
44
name = @new_name::text,
5-
updated_at = NOW(),
5+
updated_at = @now::timestamptz,
66
-- Update autostart_schedule, next_start_at and ttl according to template and workspace-level
77
-- configurations, allowing the workspace to be managed by the lifecycle executor as expected.
88
autostart_schedule = @autostart_schedule,
99
next_start_at = @next_start_at,
1010
ttl = @workspace_ttl,
1111
-- Update last_used_at during claim to ensure the claimed workspace is treated as recently used.
1212
-- This avoids unintended dormancy caused by prebuilds having stale usage timestamps.
13-
last_used_at = NOW(),
13+
last_used_at = @now::timestamptz,
1414
-- Clear dormant and deletion timestamps as a safeguard to ensure a clean lifecycle state after claim.
1515
-- These fields should not be set on prebuilds, but we defensively reset them here to prevent
1616
-- accidental dormancy or deletion by the lifecycle executor.

coderd/prebuilds/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package prebuilds
33
import (
44
"context"
55
"database/sql"
6+
"time"
67

78
"github.com/google/uuid"
89
"golang.org/x/xerrors"
@@ -57,6 +58,7 @@ type StateSnapshotter interface {
5758
type Claimer interface {
5859
Claim(
5960
ctx context.Context,
61+
now time.Time,
6062
userID uuid.UUID,
6163
name string,
6264
presetID uuid.UUID,

coderd/prebuilds/noop.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package prebuilds
33
import (
44
"context"
55
"database/sql"
6+
"time"
67

78
"github.com/google/uuid"
89

@@ -29,7 +30,7 @@ var DefaultReconciler ReconciliationOrchestrator = NoopReconciler{}
2930

3031
type NoopClaimer struct{}
3132

32-
func (NoopClaimer) Claim(context.Context, uuid.UUID, string, uuid.UUID, sql.NullString, sql.NullTime, sql.NullInt64) (*uuid.UUID, error) {
33+
func (NoopClaimer) Claim(context.Context, time.Time, uuid.UUID, string, uuid.UUID, sql.NullString, sql.NullTime, sql.NullInt64) (*uuid.UUID, error) {
3334
// Not entitled to claim prebuilds in AGPL version.
3435
return nil, ErrAGPLDoesNotSupportPrebuiltWorkspaces
3536
}

coderd/workspaces.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -635,13 +635,16 @@ func createWorkspace(
635635
claimedWorkspace *database.Workspace
636636
)
637637

638+
// Use injected Clock to allow time mocking in tests
639+
now := api.Clock.Now()
640+
638641
// If a template preset was chosen, try claim a prebuilt workspace.
639642
if req.TemplateVersionPresetID != uuid.Nil {
640643
// Try and claim an eligible prebuild, if available.
641644
// On successful claim, initialize all lifecycle fields from template and workspace-level config
642645
// so the newly claimed workspace is properly managed by the lifecycle executor.
643646
claimedWorkspace, err = claimPrebuild(
644-
ctx, prebuildsClaimer, db, api.Logger, req, owner,
647+
ctx, prebuildsClaimer, db, api.Logger, now, req, owner,
645648
dbAutostartSchedule, nextStartAt, dbTTL)
646649
// If claiming fails with an expected error (no claimable prebuilds or AGPL does not support prebuilds),
647650
// we fall back to creating a new workspace. Otherwise, propagate the unexpected error.
@@ -668,8 +671,6 @@ func createWorkspace(
668671
}
669672
}
670673

671-
now := dbtime.Now()
672-
673674
// No prebuild found; regular flow.
674675
if claimedWorkspace == nil {
675676
// Workspaces are created without any versions.
@@ -880,14 +881,16 @@ func requestTemplate(ctx context.Context, rw http.ResponseWriter, req codersdk.C
880881
func claimPrebuild(
881882
ctx context.Context,
882883
claimer prebuilds.Claimer,
883-
db database.Store, logger slog.Logger,
884+
db database.Store,
885+
logger slog.Logger,
886+
now time.Time,
884887
req codersdk.CreateWorkspaceRequest,
885888
owner workspaceOwner,
886889
autostartSchedule sql.NullString,
887890
nextStartAt sql.NullTime,
888891
ttl sql.NullInt64,
889892
) (*database.Workspace, error) {
890-
claimedID, err := claimer.Claim(ctx, owner.ID, req.Name, req.TemplateVersionPresetID, autostartSchedule, nextStartAt, ttl)
893+
claimedID, err := claimer.Claim(ctx, now, owner.ID, req.Name, req.TemplateVersionPresetID, autostartSchedule, nextStartAt, ttl)
891894
if err != nil {
892895
// TODO: enhance this by clarifying whether this *specific* prebuild failed or whether there are none to claim.
893896
return nil, xerrors.Errorf("claim prebuild: %w", err)

enterprise/coderd/prebuilds/claim.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"database/sql"
66
"errors"
7+
"time"
78

89
"github.com/google/uuid"
910
"golang.org/x/xerrors"
@@ -24,6 +25,7 @@ func NewEnterpriseClaimer(store database.Store) *EnterpriseClaimer {
2425

2526
func (c EnterpriseClaimer) Claim(
2627
ctx context.Context,
28+
now time.Time,
2729
userID uuid.UUID,
2830
name string,
2931
presetID uuid.UUID,
@@ -34,6 +36,7 @@ func (c EnterpriseClaimer) Claim(
3436
result, err := c.store.ClaimPrebuiltWorkspace(ctx, database.ClaimPrebuiltWorkspaceParams{
3537
NewUserID: userID,
3638
NewName: name,
39+
Now: now,
3740
PresetID: presetID,
3841
AutostartSchedule: autostartSchedule,
3942
NextStartAt: nextStartAt,

enterprise/coderd/prebuilds/claim_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"github.com/stretchr/testify/require"
1616
"golang.org/x/xerrors"
1717

18+
"github.com/coder/coder/v2/coderd/database/dbtime"
19+
1820
"github.com/coder/coder/v2/coderd/files"
1921
"github.com/coder/quartz"
2022

@@ -132,7 +134,9 @@ func TestClaimPrebuild(t *testing.T) {
132134
t.Run(name, func(t *testing.T) {
133135
t.Parallel()
134136

135-
// Setup.
137+
// Setup
138+
clock := quartz.NewMock(t)
139+
clock.Set(dbtime.Now())
136140
ctx := testutil.Context(t, testutil.WaitSuperLong)
137141
db, pubsub := dbtestutil.NewDB(t)
138142

@@ -144,6 +148,7 @@ func TestClaimPrebuild(t *testing.T) {
144148
Options: &coderdtest.Options{
145149
Database: spy,
146150
Pubsub: pubsub,
151+
Clock: clock,
147152
},
148153
LicenseOptions: &coderdenttest.LicenseOptions{
149154
Features: license.Features{
@@ -238,6 +243,7 @@ func TestClaimPrebuild(t *testing.T) {
238243
// When: a user creates a new workspace with a preset for which prebuilds are configured.
239244
workspaceName := strings.ReplaceAll(testutil.GetRandomName(t), "_", "-")
240245
params := database.ClaimPrebuiltWorkspaceParams{
246+
Now: clock.Now(),
241247
NewUserID: user.ID,
242248
NewName: workspaceName,
243249
PresetID: presets[0].ID,

enterprise/coderd/workspaces_test.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,9 +2210,9 @@ func TestPrebuildsAutobuild(t *testing.T) {
22102210
t.Run("DormantOnlyAfterClaimed", func(t *testing.T) {
22112211
t.Parallel()
22122212

2213-
// Set the clock to dbtime.Now() to match the workspace's LastUsedAt
2213+
// Set the clock to Monday, January 1st, 2024 at 8:00 AM UTC to keep the test deterministic
22142214
clock := quartz.NewMock(t)
2215-
clock.Set(dbtime.Now())
2215+
clock.Set(time.Date(2024, 1, 1, 8, 0, 0, 0, time.UTC))
22162216

22172217
// Setup
22182218
ctx := testutil.Context(t, testutil.WaitSuperLong)
@@ -2237,9 +2237,7 @@ func TestPrebuildsAutobuild(t *testing.T) {
22372237
),
22382238
},
22392239
LicenseOptions: &coderdenttest.LicenseOptions{
2240-
Features: license.Features{
2241-
codersdk.FeatureAdvancedTemplateScheduling: 1,
2242-
},
2240+
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
22432241
},
22442242
})
22452243

@@ -2284,9 +2282,11 @@ func TestPrebuildsAutobuild(t *testing.T) {
22842282
require.Nil(t, prebuild.DormantAt)
22852283
require.Nil(t, prebuild.DeletingAt)
22862284

2287-
// When: the autobuild executor ticks *after* the dormant TTL
2285+
// When: the autobuild executor ticks *after* the dormant TTL (10:00 AM UTC)
2286+
next := clock.Now().Add(dormantTTL).Add(time.Minute)
2287+
clock.Set(next) // 10:01 AM UTC
22882288
go func() {
2289-
tickCh <- prebuild.LastUsedAt.Add(dormantTTL).Add(time.Minute)
2289+
tickCh <- next
22902290
}()
22912291

22922292
// Then: the prebuilt workspace should remain in a start transition
@@ -2299,7 +2299,8 @@ func TestPrebuildsAutobuild(t *testing.T) {
22992299
require.Nil(t, prebuild.DormantAt)
23002300
require.Nil(t, prebuild.DeletingAt)
23012301

2302-
// Given: a user claims the prebuilt workspace
2302+
// Given: a user claims the prebuilt workspace sometime later
2303+
clock.Set(clock.Now().Add(1 * time.Hour)) // 11:01 AM UTC
23032304
workspace := claimPrebuild(t, ctx, client, userClient, user.Username, version, presets[0].ID)
23042305
require.Equal(t, prebuild.ID, workspace.ID)
23052306
// Then: the claimed workspace should have DormantAt and DeletingAt unset (nil),
@@ -2308,9 +2309,11 @@ func TestPrebuildsAutobuild(t *testing.T) {
23082309
require.Nil(t, workspace.DeletingAt)
23092310
require.True(t, workspace.LastUsedAt.After(prebuild.LastUsedAt))
23102311

2311-
// When: the autobuild executor ticks *after* the dormant TTL
2312+
// When: the autobuild executor ticks *after* the dormant TTL (1:01 PM UTC)
2313+
next = clock.Now().Add(dormantTTL).Add(time.Minute)
2314+
clock.Set(next) // 1:02 PM UTC
23122315
go func() {
2313-
tickCh <- workspace.LastUsedAt.Add(dormantTTL).Add(time.Minute)
2316+
tickCh <- next
23142317
}()
23152318

23162319
// Then: the workspace should transition to stopped state for breaching dormant TTL

0 commit comments

Comments
 (0)