Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(coderd/database/dbpurge): make API keys retention configurable#21037

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
mafredri wants to merge3 commits intomafredri/feat-coderd-db-retention-policy-3
base:mafredri/feat-coderd-db-retention-policy-3
Choose a base branch
Loading
frommafredri/feat-coderd-db-retention-policy-4
Draft
Changes from1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PrevPrevious commit
test(coderd/database/dbpurge): convert API keys tests to table-driven
  • Loading branch information
@mafredri
mafredri committedDec 1, 2025
commit03f5ec52ebb71ee5a1bb96a8e901e8c81cfe6d12
293 changes: 118 additions & 175 deletionscoderd/database/dbpurge/dbpurge_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1271,195 +1271,138 @@ func TestDeleteOldAuditLogs(t *testing.T) {
func TestDeleteExpiredAPIKeys(t *testing.T) {
t.Parallel()

t.Run("RetentionEnabled", func(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitShort)

clk := quartz.NewMock(t)
now := time.Date(2025, 1, 15, 7, 30, 0, 0, time.UTC)
retentionPeriod := 7 * 24 * time.Hour // 7 days
expiredLongAgo := now.Add(-retentionPeriod).Add(-24 * time.Hour) // Expired 8 days ago (should be deleted)
expiredRecently := now.Add(-retentionPeriod).Add(24 * time.Hour) // Expired 6 days ago (should be kept)
notExpired := now.Add(24 * time.Hour) // Expires tomorrow (should be kept)
clk.Set(now).MustWait(ctx)

db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
user := dbgen.User(t, db, database.User{})

// Create API key that expired long ago (should be deleted)
oldExpiredKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: expiredLongAgo,
TokenName: "old-expired-key",
})

// Create API key that expired recently (should be kept)
recentExpiredKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: expiredRecently,
TokenName: "recent-expired-key",
})

// Create API key that hasn't expired yet (should be kept)
activeKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: notExpired,
TokenName: "active-key",
})
now := time.Date(2025, 1, 15, 7, 30, 0, 0, time.UTC)

// Run the purge with configured retention period
done := awaitDoTick(ctx, t, clk)
closer := dbpurge.New(ctx, logger, db, &codersdk.DeploymentValues{
Retention: codersdk.RetentionConfig{
APIKeys: serpent.Duration(retentionPeriod),
testCases := []struct {
name string
retentionConfig codersdk.RetentionConfig
oldExpiredTime time.Time
recentExpiredTime *time.Time // nil means no recent expired key created
activeTime *time.Time // nil means no active key created
expectOldExpiredDeleted bool
expectedKeysRemaining int
}{
{
name: "RetentionEnabled",
retentionConfig: codersdk.RetentionConfig{
APIKeys: serpent.Duration(7 * 24 * time.Hour), // 7 days
},
}, clk)
defer closer.Close()
testutil.TryReceive(ctx, t, done)

// Verify results
_, err := db.GetAPIKeyByID(ctx, oldExpiredKey.ID)
require.Error(t, err, "old expired key should be deleted")

_, err = db.GetAPIKeyByID(ctx, recentExpiredKey.ID)
require.NoError(t, err, "recently expired key should be kept")

_, err = db.GetAPIKeyByID(ctx, activeKey.ID)
require.NoError(t, err, "active key should be kept")
})

t.Run("RetentionDisabled", func(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitShort)

clk := quartz.NewMock(t)
now := time.Date(2025, 1, 15, 7, 30, 0, 0, time.UTC)
expiredLongAgo := now.Add(-365 * 24 * time.Hour) // Expired 1 year ago
clk.Set(now).MustWait(ctx)

db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
user := dbgen.User(t, db, database.User{})

// Create API key that expired long ago (should NOT be deleted when retention is 0)
oldExpiredKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: expiredLongAgo,
TokenName: "old-expired-key",
})

// Run the purge with retention disabled (0)
done := awaitDoTick(ctx, t, clk)
closer := dbpurge.New(ctx, logger, db, &codersdk.DeploymentValues{
Retention: codersdk.RetentionConfig{
APIKeys: serpent.Duration(0), // disabled
oldExpiredTime: now.Add(-8 * 24 * time.Hour), // Expired 8 days ago
recentExpiredTime: ptr(now.Add(-6 * 24 * time.Hour)), // Expired 6 days ago
activeTime: ptr(now.Add(24 * time.Hour)), // Expires tomorrow
expectOldExpiredDeleted: true,
expectedKeysRemaining: 2, // recent expired + active
},
{
name: "RetentionDisabled",
retentionConfig: codersdk.RetentionConfig{
APIKeys: serpent.Duration(0),
},
}, clk)
defer closer.Close()
testutil.TryReceive(ctx, t, done)

// Verify old expired key is still present
_, err := db.GetAPIKeyByID(ctx, oldExpiredKey.ID)
require.NoError(t, err, "old expired key should NOT be deleted when retention is disabled")
})

t.Run("GlobalRetentionFallback", func(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitShort)

clk := quartz.NewMock(t)
now := time.Date(2025, 1, 15, 7, 30, 0, 0, time.UTC)
retentionPeriod := 14 * 24 * time.Hour // 14 days global
expiredLongAgo := now.Add(-retentionPeriod).Add(-24 * time.Hour) // Expired 15 days ago (should be deleted)
expiredRecently := now.Add(-retentionPeriod).Add(24 * time.Hour) // Expired 13 days ago (should be kept)
clk.Set(now).MustWait(ctx)

db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
user := dbgen.User(t, db, database.User{})
oldExpiredTime: now.Add(-365 * 24 * time.Hour), // Expired 1 year ago
recentExpiredTime: nil,
activeTime: nil,
expectOldExpiredDeleted: false,
expectedKeysRemaining: 1, // old expired is kept
},
{
name: "GlobalRetentionFallback",
retentionConfig: codersdk.RetentionConfig{
Global: serpent.Duration(14 * 24 * time.Hour), // 14 days global
APIKeys: serpent.Duration(0), // Not set, should fall back to global
},
oldExpiredTime: now.Add(-15 * 24 * time.Hour), // Expired 15 days ago
recentExpiredTime: ptr(now.Add(-13 * 24 * time.Hour)), // Expired 13 days ago
activeTime: nil,
expectOldExpiredDeleted: true,
expectedKeysRemaining: 1, // only recent expired remains
},
{
name: "CustomRetention30Days",
retentionConfig: codersdk.RetentionConfig{
APIKeys: serpent.Duration(30 * 24 * time.Hour), // 30 days
},
oldExpiredTime: now.Add(-31 * 24 * time.Hour), // Expired 31 days ago
recentExpiredTime: ptr(now.Add(-29 * 24 * time.Hour)), // Expired 29 days ago
activeTime: nil,
expectOldExpiredDeleted: true,
expectedKeysRemaining: 1, // only recent expired remains
},
}

// Create API key that expired long ago (should be deleted)
oldExpiredKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: expiredLongAgo,
TokenName: "old-expired-key",
})
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

// Create API key that expired recently (should be kept)
recentExpiredKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: expiredRecently,
TokenName: "recent-expired-key",
})
ctx := testutil.Context(t, testutil.WaitShort)
clk := quartz.NewMock(t)
clk.Set(now).MustWait(ctx)

// Run the purge with global retention (API keys retention is 0, so it falls back)
done := awaitDoTick(ctx, t, clk)
closer := dbpurge.New(ctx, logger, db, &codersdk.DeploymentValues{
Retention: codersdk.RetentionConfig{
Global: serpent.Duration(retentionPeriod), // Use global
APIKeys: serpent.Duration(0), // Not set, should fall back to global
},
}, clk)
defer closer.Close()
testutil.TryReceive(ctx, t, done)
db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
user := dbgen.User(t, db, database.User{})

// Verify results
_, err := db.GetAPIKeyByID(ctx, oldExpiredKey.ID)
require.Error(t, err, "old expired key should be deleted via global retention")
// Create API key that expired long ago.
oldExpiredKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: tc.oldExpiredTime,
TokenName: "old-expired-key",
})

_, err = db.GetAPIKeyByID(ctx, recentExpiredKey.ID)
require.NoError(t, err, "recently expired key should be kept")
})
// Create API key that expired recently if specified.
var recentExpiredKey database.APIKey
if tc.recentExpiredTime != nil {
recentExpiredKey, _ = dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: *tc.recentExpiredTime,
TokenName: "recent-expired-key",
})
}

t.Run("CustomRetention30Days", func(t *testing.T) {
t.Parallel()
// Create API key that hasn't expired yet if specified.
var activeKey database.APIKey
if tc.activeTime != nil {
activeKey, _ = dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: *tc.activeTime,
TokenName: "active-key",
})
}

ctx := testutil.Context(t, testutil.WaitShort)
// Run the purge.
done := awaitDoTick(ctx, t, clk)
closer := dbpurge.New(ctx, logger, db, &codersdk.DeploymentValues{
Retention: tc.retentionConfig,
}, clk)
defer closer.Close()
testutil.TryReceive(ctx, t, done)

clk := quartz.NewMock(t)
now := time.Date(2025, 1, 15, 7, 30, 0, 0, time.UTC)
retentionPeriod := 30 * 24 * time.Hour // 30 days
expiredLongAgo := now.Add(-retentionPeriod).Add(-24 * time.Hour) // Expired 31 days ago (should be deleted)
expiredRecently := now.Add(-retentionPeriod).Add(24 * time.Hour) // Expired 29 days ago (should be kept)
clk.Set(now).MustWait(ctx)
// Verify total keys remaining.
keys, err := db.GetAPIKeysLastUsedAfter(ctx, time.Time{})
require.NoError(t, err)
require.Len(t, keys, tc.expectedKeysRemaining, "unexpected number of keys remaining")

db, _ := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
user := dbgen.User(t, db, database.User{})
// Verify results.
_, err = db.GetAPIKeyByID(ctx, oldExpiredKey.ID)
if tc.expectOldExpiredDeleted {
require.Error(t, err, "old expired key should be deleted")
} else {
require.NoError(t, err, "old expired key should NOT be deleted")
}

// Create API key that expired long ago (should be deleted)
oldExpiredKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: expiredLongAgo,
TokenName: "old-expired-key",
})
if tc.recentExpiredTime != nil {
_, err = db.GetAPIKeyByID(ctx, recentExpiredKey.ID)
require.NoError(t, err, "recently expired key should be kept")
}

// Create API key that expired recently (should be kept)
recentExpiredKey, _ := dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
ExpiresAt: expiredRecently,
TokenName: "recent-expired-key",
if tc.activeTime != nil {
_, err = db.GetAPIKeyByID(ctx, activeKey.ID)
require.NoError(t, err, "active key should be kept")
}
})
}
}

// Run the purge with 30-day retention
done := awaitDoTick(ctx, t, clk)
closer := dbpurge.New(ctx, logger, db, &codersdk.DeploymentValues{
Retention: codersdk.RetentionConfig{
APIKeys: serpent.Duration(retentionPeriod),
},
}, clk)
defer closer.Close()
testutil.TryReceive(ctx, t, done)

// Verify results
_, err := db.GetAPIKeyByID(ctx, oldExpiredKey.ID)
require.Error(t, err, "old expired key should be deleted with 30-day retention")

_, err = db.GetAPIKeyByID(ctx, recentExpiredKey.ID)
require.NoError(t, err, "recently expired key should be kept with 30-day retention")
})
// ptr is a helper to create a pointer to a value.
func ptr[T any](v T) *T {
return &v
}

[8]ページ先頭

©2009-2025 Movatter.jp