From 81468323e08f0e35f1ad85316f24f695f61329cf Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 6 Mar 2026 09:14:11 +0000 Subject: [PATCH] fix(coderd): use dbtime.Now() instead of time.Now() in test assertions against DB timestamps (#22685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `time.Now()` has nanosecond precision while Postgres timestamps are microsecond precision. When tests compare `time.Now()` against DB-sourced timestamps using `Before`/`After`/`WithinRange`/etc., there is a non-zero flake risk from the precision mismatch. This replaces `time.Now()` with `dbtime.Now()` (which rounds to microsecond precision) in all test assertions that compare against database timestamps. Follows from #22684. ## Changes (11 files) | File | Changes | |---|---| | `coderd/apikey_test.go` | 11 comparisons with `ExpiresAt` | | `coderd/users_test.go` | 2 comparisons with `ExpiresAt` | | `coderd/oauth2_test.go` | 1 comparison with `token.Expiry` | | `coderd/workspaces_test.go` | 2 comparisons with `DormantAt` | | `coderd/workspaceagents_test.go` | 3 comparisons with `ConnectedAt`/`DisconnectedAt` | | `coderd/workspaceapps/db_test.go` | 1 comparison with `token.Expiry` | | `coderd/provisionerdserver/provisionerdserver_test.go` | 1 comparison with `key.ExpiresAt` | | `enterprise/coderd/workspaces_test.go` | 1 comparison with `DormantAt` | | `enterprise/coderd/license/license_test.go` | 3 `NotBefore` values | | `enterprise/coderd/licenses_test.go` | 2 `NotBefore` values | | `enterprise/coderd/users_test.go` | 3 `Next()` comparisons | ## Not changed (intentionally) - `scaletest/placebo/run_test.go` — compares wall-clock elapsed time, not DB timestamps - `cli/server_test.go`, `coderd/jwtutils/jwt_test.go`, `enterprise/aibridgeproxyd/aibridgeproxyd_test.go` — TLS cert fields, not DB-stored - `coderd/azureidentity/azureidentity_test.go` — Azure cert expiry, not DB 🤖 Generated by Claude Opus 4.6 but reviewed manually. --- coderd/apikey_test.go | 22 +++++++++---------- coderd/oauth2_test.go | 2 +- .../provisionerdserver_test.go | 2 +- coderd/users_test.go | 4 ++-- coderd/workspaceagents_test.go | 6 ++--- coderd/workspaceapps/db_test.go | 3 ++- coderd/workspaces_test.go | 4 ++-- enterprise/coderd/license/license_test.go | 6 ++--- enterprise/coderd/licenses_test.go | 5 +++-- enterprise/coderd/users_test.go | 7 +++--- enterprise/coderd/workspaces_test.go | 2 +- 11 files changed, 33 insertions(+), 30 deletions(-) diff --git a/coderd/apikey_test.go b/coderd/apikey_test.go index cf96efdcd8..823e2faa6b 100644 --- a/coderd/apikey_test.go +++ b/coderd/apikey_test.go @@ -48,8 +48,8 @@ func TestTokenCRUD(t *testing.T) { require.EqualValues(t, len(keys), 1) require.Contains(t, res.Key, keys[0].ID) // expires_at should default to 30 days - require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6)) - require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8)) + require.Greater(t, keys[0].ExpiresAt, dbtime.Now().Add(time.Hour*24*6)) + require.Less(t, keys[0].ExpiresAt, dbtime.Now().Add(time.Hour*24*8)) require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope) require.Len(t, keys[0].AllowList, 1) require.Equal(t, "*:*", keys[0].AllowList[0].String()) @@ -194,8 +194,8 @@ func TestUserSetTokenDuration(t *testing.T) { require.NoError(t, err) keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{}) require.NoError(t, err) - require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24)) - require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24)) + require.Greater(t, keys[0].ExpiresAt, dbtime.Now().Add(time.Hour*6*24)) + require.Less(t, keys[0].ExpiresAt, dbtime.Now().Add(time.Hour*8*24)) } func TestDefaultTokenDuration(t *testing.T) { @@ -210,8 +210,8 @@ func TestDefaultTokenDuration(t *testing.T) { require.NoError(t, err) keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{}) require.NoError(t, err) - require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*6)) - require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*24*8)) + require.Greater(t, keys[0].ExpiresAt, dbtime.Now().Add(time.Hour*24*6)) + require.Less(t, keys[0].ExpiresAt, dbtime.Now().Add(time.Hour*24*8)) } func TestTokenUserSetMaxLifetime(t *testing.T) { @@ -518,7 +518,7 @@ func TestExpireAPIKey(t *testing.T) { // Verify the token is not expired. key, err := adminClient.APIKeyByID(ctx, codersdk.Me, keyID) require.NoError(t, err) - require.True(t, key.ExpiresAt.After(time.Now())) + require.True(t, key.ExpiresAt.After(dbtime.Now())) auditor.ResetLogs() @@ -529,7 +529,7 @@ func TestExpireAPIKey(t *testing.T) { // Verify the token is expired. key, err = adminClient.APIKeyByID(ctx, codersdk.Me, keyID) require.NoError(t, err) - require.True(t, key.ExpiresAt.Before(time.Now())) + require.True(t, key.ExpiresAt.Before(dbtime.Now())) // Verify audit log. als := auditor.AuditLogs() @@ -556,7 +556,7 @@ func TestExpireAPIKey(t *testing.T) { // Verify the token is expired. key, err := memberClient.APIKeyByID(ctx, codersdk.Me, keyID) require.NoError(t, err) - require.True(t, key.ExpiresAt.Before(time.Now())) + require.True(t, key.ExpiresAt.Before(dbtime.Now())) }) t.Run("MemberCannotExpireOtherUsersToken", func(t *testing.T) { @@ -607,7 +607,7 @@ func TestExpireAPIKey(t *testing.T) { // Invariant: make sure it's actually expired key, err := adminClient.APIKeyByID(ctx, codersdk.Me, keyID) require.NoError(t, err) - require.LessOrEqual(t, key.ExpiresAt, time.Now(), "key should be expired") + require.LessOrEqual(t, key.ExpiresAt, dbtime.Now(), "key should be expired") // Expire it again - should succeed (idempotent). err = adminClient.ExpireAPIKey(ctx, codersdk.Me, keyID) @@ -636,7 +636,7 @@ func TestExpireAPIKey(t *testing.T) { // Verify it's expired. key, err := adminClient.APIKeyByID(ctx, codersdk.Me, keyID) require.NoError(t, err) - require.True(t, key.ExpiresAt.Before(time.Now())) + require.True(t, key.ExpiresAt.Before(dbtime.Now())) // Delete the expired token - should succeed. err = adminClient.DeleteAPIKey(ctx, codersdk.Me, keyID) diff --git a/coderd/oauth2_test.go b/coderd/oauth2_test.go index f898d38e55..9831067ff2 100644 --- a/coderd/oauth2_test.go +++ b/coderd/oauth2_test.go @@ -516,7 +516,7 @@ func TestOAuth2ProviderTokenExchange(t *testing.T) { } else { require.NoError(t, err) require.NotEmpty(t, token.AccessToken) - require.True(t, time.Now().Before(token.Expiry)) + require.True(t, dbtime.Now().Before(token.Expiry)) // Check that the token works. newClient := codersdk.New(userClient.URL) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 75e48fb399..dbbfa94505 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -434,7 +434,7 @@ func TestAcquireJob(t *testing.T) { key, err := db.GetAPIKeyByID(ctx, toks[0]) require.NoError(t, err) require.Equal(t, int64(dv.Sessions.MaximumTokenDuration.Value().Seconds()), key.LifetimeSeconds) - require.WithinDuration(t, time.Now().Add(dv.Sessions.MaximumTokenDuration.Value()), key.ExpiresAt, time.Minute) + require.WithinDuration(t, dbtime.Now().Add(dv.Sessions.MaximumTokenDuration.Value()), key.ExpiresAt, time.Minute) wantedMetadata := &sdkproto.Metadata{ CoderUrl: (&url.URL{}).String(), diff --git a/coderd/users_test.go b/coderd/users_test.go index 3a6ec32cde..5355474d8e 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -302,8 +302,8 @@ func TestPostLogin(t *testing.T) { apiKey, err := client.APIKeyByID(ctx, owner.UserID.String(), split[0]) require.NoError(t, err, "fetch api key") - require.True(t, apiKey.ExpiresAt.After(time.Now().Add(time.Hour*24*6)), "default tokens lasts more than 6 days") - require.True(t, apiKey.ExpiresAt.Before(time.Now().Add(time.Hour*24*8)), "default tokens lasts less than 8 days") + require.True(t, apiKey.ExpiresAt.After(dbtime.Now().Add(time.Hour*24*6)), "default tokens lasts more than 6 days") + require.True(t, apiKey.ExpiresAt.Before(dbtime.Now().Add(time.Hour*24*8)), "default tokens lasts less than 8 days") require.Greater(t, apiKey.LifetimeSeconds, key.LifetimeSeconds, "token should have longer lifetime") }) } diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 4096d51f2c..c6c9e98f89 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -3041,13 +3041,13 @@ func TestUserTailnetTelemetry(t *testing.T) { telemetryConnection := snapshot.UserTailnetConnections[0] require.Equal(t, memberUser.ID.String(), telemetryConnection.UserID) require.GreaterOrEqual(t, telemetryConnection.ConnectedAt, predialTime) - require.LessOrEqual(t, telemetryConnection.ConnectedAt, time.Now()) + require.LessOrEqual(t, telemetryConnection.ConnectedAt, dbtime.Now()) require.NotEmpty(t, telemetryConnection.PeerID) requireEqualOrBothNil(t, telemetryConnection.DeviceID, tc.expected.DeviceID) requireEqualOrBothNil(t, telemetryConnection.DeviceOS, tc.expected.DeviceOS) requireEqualOrBothNil(t, telemetryConnection.CoderDesktopVersion, tc.expected.CoderDesktopVersion) - beforeDisconnectTime := time.Now() + beforeDisconnectTime := dbtime.Now() err = wsConn.Close(websocket.StatusNormalClosure, "done") require.NoError(t, err) @@ -3060,7 +3060,7 @@ func TestUserTailnetTelemetry(t *testing.T) { require.Equal(t, telemetryConnection.PeerID, telemetryDisconnection.PeerID) require.NotNil(t, telemetryDisconnection.DisconnectedAt) require.GreaterOrEqual(t, *telemetryDisconnection.DisconnectedAt, beforeDisconnectTime) - require.LessOrEqual(t, *telemetryDisconnection.DisconnectedAt, time.Now()) + require.LessOrEqual(t, *telemetryDisconnection.DisconnectedAt, dbtime.Now()) requireEqualOrBothNil(t, telemetryConnection.DeviceID, tc.expected.DeviceID) requireEqualOrBothNil(t, telemetryConnection.DeviceOS, tc.expected.DeviceOS) requireEqualOrBothNil(t, telemetryConnection.CoderDesktopVersion, tc.expected.CoderDesktopVersion) diff --git a/coderd/workspaceapps/db_test.go b/coderd/workspaceapps/db_test.go index aa436f4cc3..b856ff8882 100644 --- a/coderd/workspaceapps/db_test.go +++ b/coderd/workspaceapps/db_test.go @@ -24,6 +24,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/connectionlog" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/jwtutils" "github.com/coder/coder/v2/coderd/tracing" @@ -310,7 +311,7 @@ func Test_ResolveRequest(t *testing.T) { CORSBehavior: codersdk.CORSBehaviorSimple, }, token) require.NotZero(t, token.Expiry) - require.WithinDuration(t, time.Now().Add(workspaceapps.DefaultTokenExpiry), token.Expiry.Time(), time.Minute) + require.WithinDuration(t, dbtime.Now().Add(workspaceapps.DefaultTokenExpiry), token.Expiry.Time(), time.Minute) // Check that the token was set in the response and is valid. require.Len(t, w.Cookies(), 1) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index cc7b6b5543..8acc8f1b5f 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -4379,7 +4379,7 @@ func TestWorkspaceDormant(t *testing.T) { // The template doesn't have a time_til_dormant_autodelete set so this should be nil. require.Nil(t, workspace.DeletingAt) require.NotNil(t, workspace.DormantAt) - require.WithinRange(t, *workspace.DormantAt, time.Now().Add(-time.Second*10), time.Now()) + require.WithinRange(t, *workspace.DormantAt, dbtime.Now().Add(-time.Second*10), dbtime.Now()) require.Equal(t, lastUsedAt, workspace.LastUsedAt) workspace = coderdtest.MustWorkspace(t, client, workspace.ID) @@ -4433,7 +4433,7 @@ func TestWorkspaceDormant(t *testing.T) { workspace, err = client.Workspace(ctx, workspace.ID) require.NoError(t, err, "fetch dormant workspace") if assert.NotNil(t, workspace.DormantAt, "workspace must be dormant") { - require.WithinDuration(t, *workspace.DormantAt, time.Now(), 10*time.Second) + require.WithinDuration(t, *workspace.DormantAt, dbtime.Now(), 10*time.Second) } // Starting a dormant workspace should 'wake' it. wb, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{ diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index db0f6898c9..e290036f5f 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -749,7 +749,7 @@ func TestEntitlements(t *testing.T) { Features: license.Features{ codersdk.FeatureHighAvailability: 1, }, - NotBefore: time.Now().Add(-time.Hour * 2), + NotBefore: dbtime.Now().Add(-time.Hour * 2), GraceAt: time.Now().Add(-time.Hour), ExpiresAt: time.Now().Add(time.Hour), }), @@ -799,7 +799,7 @@ func TestEntitlements(t *testing.T) { db, _ := dbtestutil.NewDB(t) db.InsertLicense(context.Background(), database.InsertLicenseParams{ JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ - NotBefore: time.Now().Add(-time.Hour * 2), + NotBefore: dbtime.Now().Add(-time.Hour * 2), GraceAt: time.Now().Add(-time.Hour), ExpiresAt: time.Now().Add(time.Hour), Features: license.Features{ @@ -1430,7 +1430,7 @@ func TestUsageLimitFeatures(t *testing.T) { UUID: uuid.New(), JWT: coderdenttest.GenerateLicense(t, coderdenttest.LicenseOptions{ IssuedAt: time.Now().Add(-time.Minute * 2), - NotBefore: time.Now().Add(-time.Minute * 2), + NotBefore: dbtime.Now().Add(-time.Minute * 2), ExpiresAt: time.Now().Add(time.Hour * 2), Features: license.Features{ codersdk.FeatureManagedAgentLimit: 100, diff --git a/enterprise/coderd/licenses_test.go b/enterprise/coderd/licenses_test.go index fbcbbf654e..73d16535d4 100644 --- a/enterprise/coderd/licenses_test.go +++ b/enterprise/coderd/licenses_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/xerrors" + "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" @@ -145,7 +146,7 @@ func TestPostLicense(t *testing.T) { Features: license.Features{ codersdk.FeatureAuditLog: 1, }, - NotBefore: time.Now().Add(time.Hour), + NotBefore: dbtime.Now().Add(time.Hour), GraceAt: time.Now().Add(2 * time.Hour), ExpiresAt: time.Now().Add(3 * time.Hour), }) @@ -168,7 +169,7 @@ func TestPostLicense(t *testing.T) { Features: license.Features{ codersdk.FeatureAuditLog: 1, }, - NotBefore: time.Now().Add(time.Hour), + NotBefore: dbtime.Now().Add(time.Hour), GraceAt: time.Now().Add(2 * time.Hour), ExpiresAt: time.Now().Add(-time.Hour), }) diff --git a/enterprise/coderd/users_test.go b/enterprise/coderd/users_test.go index 6876ca72b4..d6c8324a07 100644 --- a/enterprise/coderd/users_test.go +++ b/enterprise/coderd/users_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/schedule/cron" "github.com/coder/coder/v2/codersdk" @@ -87,7 +88,7 @@ func TestUserQuietHours(t *testing.T) { require.False(t, sched1.UserSet) require.Equal(t, defaultScheduleParsed.TimeParsed().Format(TimeFormatHHMM), sched1.Time) require.Equal(t, defaultScheduleParsed.Location().String(), sched1.Timezone) - require.WithinDuration(t, defaultScheduleParsed.Next(time.Now()), sched1.Next, 15*time.Second) + require.WithinDuration(t, defaultScheduleParsed.Next(dbtime.Now()), sched1.Next, 15*time.Second) // Set their quiet hours. customQuietHoursSchedule := "CRON_TZ=Australia/Sydney 0 0 * * *" @@ -110,7 +111,7 @@ func TestUserQuietHours(t *testing.T) { require.True(t, sched2.UserSet) require.Equal(t, customScheduleParsed.TimeParsed().Format(TimeFormatHHMM), sched2.Time) require.Equal(t, customScheduleParsed.Location().String(), sched2.Timezone) - require.WithinDuration(t, customScheduleParsed.Next(time.Now()), sched2.Next, 15*time.Second) + require.WithinDuration(t, customScheduleParsed.Next(dbtime.Now()), sched2.Next, 15*time.Second) // Get quiet hours for a user that has them set. sched3, err := client.UserQuietHoursSchedule(ctx, user.ID.String()) @@ -119,7 +120,7 @@ func TestUserQuietHours(t *testing.T) { require.True(t, sched3.UserSet) require.Equal(t, customScheduleParsed.TimeParsed().Format(TimeFormatHHMM), sched3.Time) require.Equal(t, customScheduleParsed.Location().String(), sched3.Timezone) - require.WithinDuration(t, customScheduleParsed.Next(time.Now()), sched3.Next, 15*time.Second) + require.WithinDuration(t, customScheduleParsed.Next(dbtime.Now()), sched3.Next, 15*time.Second) // Try setting a garbage schedule. _, err = client.UpdateUserQuietHoursSchedule(ctx, user.ID.String(), codersdk.UpdateUserQuietHoursScheduleRequest{ diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index a5d2c70ded..57855a3bd4 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -4023,7 +4023,7 @@ func TestWorkspaceLock(t *testing.T) { require.NotNil(t, workspace.DeletingAt) require.NotNil(t, workspace.DormantAt) require.Equal(t, workspace.DormantAt.Add(dormantTTL), *workspace.DeletingAt) - require.WithinRange(t, *workspace.DormantAt, time.Now().Add(-time.Second), time.Now()) + require.WithinRange(t, *workspace.DormantAt, dbtime.Now().Add(-time.Second), dbtime.Now()) // Locking a workspace shouldn't update the last_used_at. require.Equal(t, lastUsedAt, workspace.LastUsedAt)