mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: plumb time_til_autostop_notify template field (#26439)
This commit is contained in:
@@ -84,10 +84,11 @@ func (*EnterpriseTemplateScheduleStore) Get(ctx context.Context, db database.Sto
|
||||
}
|
||||
|
||||
return agpl.TemplateScheduleOptions{
|
||||
UserAutostartEnabled: tpl.AllowUserAutostart,
|
||||
UserAutostopEnabled: tpl.AllowUserAutostop,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
ActivityBump: time.Duration(tpl.ActivityBump),
|
||||
UserAutostartEnabled: tpl.AllowUserAutostart,
|
||||
UserAutostopEnabled: tpl.AllowUserAutostop,
|
||||
DefaultTTL: time.Duration(tpl.DefaultTTL),
|
||||
ActivityBump: time.Duration(tpl.ActivityBump),
|
||||
TimeTilAutostopNotify: time.Duration(tpl.TimeTilAutostopNotify),
|
||||
AutostopRequirement: agpl.TemplateAutostopRequirement{
|
||||
// #nosec G115 - Safe conversion as we've verified tpl.AutostopRequirementDaysOfWeek is <= 255
|
||||
DaysOfWeek: uint8(tpl.AutostopRequirementDaysOfWeek),
|
||||
@@ -116,6 +117,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
|
||||
|
||||
if int64(opts.DefaultTTL) == tpl.DefaultTTL &&
|
||||
int64(opts.ActivityBump) == tpl.ActivityBump &&
|
||||
int64(opts.TimeTilAutostopNotify) == tpl.TimeTilAutostopNotify &&
|
||||
int16(opts.AutostopRequirement.DaysOfWeek) == tpl.AutostopRequirementDaysOfWeek &&
|
||||
opts.AutostartRequirement.DaysOfWeek == tpl.AutostartAllowedDays() &&
|
||||
opts.AutostopRequirement.Weeks == tpl.AutostopRequirementWeeks &&
|
||||
@@ -153,6 +155,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
|
||||
AllowUserAutostop: opts.UserAutostopEnabled,
|
||||
DefaultTTL: int64(opts.DefaultTTL),
|
||||
ActivityBump: int64(opts.ActivityBump),
|
||||
TimeTilAutostopNotify: int64(opts.TimeTilAutostopNotify),
|
||||
AutostopRequirementDaysOfWeek: int16(opts.AutostopRequirement.DaysOfWeek),
|
||||
AutostopRequirementWeeks: opts.AutostopRequirement.Weeks,
|
||||
// Database stores the inverse of the allowed days of the week.
|
||||
|
||||
@@ -1385,6 +1385,77 @@ func TestTemplateUpdatePrebuilds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTemplateScheduleTimeTilAutostopNotify verifies that the enterprise
|
||||
// template schedule store round-trips the TimeTilAutostopNotify field through
|
||||
// Set/Get, and that the field participates in the no-op short-circuit
|
||||
// comparison in Set. If the short-circuit compared the wrong field, an update
|
||||
// that changes only TimeTilAutostopNotify would be silently swallowed.
|
||||
func TestTemplateScheduleTimeTilAutostopNotify(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
||||
db, _ = dbtestutil.NewDB(t)
|
||||
ctx = testutil.Context(t, testutil.WaitLong)
|
||||
user = dbgen.User(t, db, database.User{})
|
||||
file = dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
||||
|
||||
templateJob = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
FileID: file.ID,
|
||||
InitiatorID: user.ID,
|
||||
Tags: database.StringMap{"foo": "bar"},
|
||||
})
|
||||
templateVersion = dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
CreatedBy: user.ID,
|
||||
JobID: templateJob.ID,
|
||||
OrganizationID: templateJob.OrganizationID,
|
||||
})
|
||||
template = dbgen.Template(t, db, database.Template{
|
||||
ActiveVersionID: templateVersion.ID,
|
||||
CreatedBy: user.ID,
|
||||
OrganizationID: templateJob.OrganizationID,
|
||||
})
|
||||
)
|
||||
|
||||
// Setup the template schedule store.
|
||||
notifyEnq := notifications.NewNoopEnqueuer()
|
||||
const userQuietHoursSchedule = "CRON_TZ=UTC 0 0 * * *" // midnight UTC
|
||||
userQuietHoursStore, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(userQuietHoursSchedule, true)
|
||||
require.NoError(t, err)
|
||||
userQuietHoursStorePtr := &atomic.Pointer[agplschedule.UserQuietHoursScheduleStore]{}
|
||||
userQuietHoursStorePtr.Store(&userQuietHoursStore)
|
||||
templateScheduleStore := schedule.NewEnterpriseTemplateScheduleStore(userQuietHoursStorePtr, notifyEnq, logger, nil)
|
||||
|
||||
// baseOpts is reused across Set calls so that only TimeTilAutostopNotify
|
||||
// differs between the first and second update. This is what exercises the
|
||||
// no-op short-circuit guard: every other compared field stays identical.
|
||||
baseOpts := agplschedule.TemplateScheduleOptions{
|
||||
DefaultTTL: 24 * time.Hour,
|
||||
}
|
||||
|
||||
// Set then Get round-trip: TimeTilAutostopNotify should persist.
|
||||
firstOpts := baseOpts
|
||||
firstOpts.TimeTilAutostopNotify = time.Hour
|
||||
template, err = templateScheduleStore.Set(ctx, db, template, firstOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
gotOpts, err := templateScheduleStore.Get(ctx, db, template.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, time.Hour, gotOpts.TimeTilAutostopNotify)
|
||||
|
||||
// No-op short-circuit guard: change ONLY TimeTilAutostopNotify and keep all
|
||||
// other options identical. If the short-circuit compared the wrong field,
|
||||
// this update would be treated as a no-op and the value would remain 1h.
|
||||
secondOpts := baseOpts
|
||||
secondOpts.TimeTilAutostopNotify = 2 * time.Hour
|
||||
template, err = templateScheduleStore.Set(ctx, db, template, secondOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
gotOpts, err = templateScheduleStore.Get(ctx, db, template.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2*time.Hour, gotOpts.TimeTilAutostopNotify)
|
||||
}
|
||||
|
||||
func must[V any](v V, err error) V {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
Reference in New Issue
Block a user