mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add server flag to disable user custom quiet hours (#11124)
This commit is contained in:
@@ -207,7 +207,7 @@ func TestTemplateUpdateBuildDeadlines(t *testing.T) {
|
||||
wsBuild, err = db.GetWorkspaceBuildByID(ctx, wsBuild.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
userQuietHoursStore, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(userQuietHoursSchedule)
|
||||
userQuietHoursStore, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(userQuietHoursSchedule, true)
|
||||
require.NoError(t, err)
|
||||
userQuietHoursStorePtr := &atomic.Pointer[agplschedule.UserQuietHoursScheduleStore]{}
|
||||
userQuietHoursStorePtr.Store(&userQuietHoursStore)
|
||||
@@ -490,7 +490,7 @@ func TestTemplateUpdateBuildDeadlinesSkip(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
userQuietHoursStore, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(userQuietHoursSchedule)
|
||||
userQuietHoursStore, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(userQuietHoursSchedule, true)
|
||||
require.NoError(t, err)
|
||||
userQuietHoursStorePtr := &atomic.Pointer[agplschedule.UserQuietHoursScheduleStore]{}
|
||||
userQuietHoursStorePtr.Store(&userQuietHoursStore)
|
||||
|
||||
@@ -18,17 +18,19 @@ import (
|
||||
// enterprise customers.
|
||||
type enterpriseUserQuietHoursScheduleStore struct {
|
||||
defaultSchedule string
|
||||
userCanSet bool
|
||||
}
|
||||
|
||||
var _ agpl.UserQuietHoursScheduleStore = &enterpriseUserQuietHoursScheduleStore{}
|
||||
|
||||
func NewEnterpriseUserQuietHoursScheduleStore(defaultSchedule string) (agpl.UserQuietHoursScheduleStore, error) {
|
||||
func NewEnterpriseUserQuietHoursScheduleStore(defaultSchedule string, userCanSet bool) (agpl.UserQuietHoursScheduleStore, error) {
|
||||
if defaultSchedule == "" {
|
||||
return nil, xerrors.Errorf("default schedule must be set")
|
||||
}
|
||||
|
||||
s := &enterpriseUserQuietHoursScheduleStore{
|
||||
defaultSchedule: defaultSchedule,
|
||||
userCanSet: userCanSet,
|
||||
}
|
||||
|
||||
// The context is only used for tracing so using a background ctx is fine.
|
||||
@@ -64,8 +66,9 @@ func (s *enterpriseUserQuietHoursScheduleStore) parseSchedule(ctx context.Contex
|
||||
}
|
||||
|
||||
return agpl.UserQuietHoursScheduleOptions{
|
||||
Schedule: sched,
|
||||
UserSet: userSet,
|
||||
Schedule: sched,
|
||||
UserSet: userSet,
|
||||
UserCanSet: s.userCanSet,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -73,6 +76,10 @@ func (s *enterpriseUserQuietHoursScheduleStore) Get(ctx context.Context, db data
|
||||
ctx, span := tracing.StartSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
if !s.userCanSet {
|
||||
return s.parseSchedule(ctx, "")
|
||||
}
|
||||
|
||||
user, err := db.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return agpl.UserQuietHoursScheduleOptions{}, xerrors.Errorf("get user by ID: %w", err)
|
||||
@@ -85,6 +92,10 @@ func (s *enterpriseUserQuietHoursScheduleStore) Set(ctx context.Context, db data
|
||||
ctx, span := tracing.StartSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
if !s.userCanSet {
|
||||
return agpl.UserQuietHoursScheduleOptions{}, agpl.ErrUserCannotSetQuietHoursSchedule
|
||||
}
|
||||
|
||||
opts, err := s.parseSchedule(ctx, rawSchedule)
|
||||
if err != nil {
|
||||
return opts, err
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package schedule_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbmock"
|
||||
agpl "github.com/coder/coder/v2/coderd/schedule"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/schedule"
|
||||
)
|
||||
|
||||
func TestEnterpriseUserQuietHoursSchedule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
defaultSchedule = "CRON_TZ=UTC 15 10 * * *"
|
||||
userCustomSchedule1 = "CRON_TZ=Australia/Sydney 30 2 * * *"
|
||||
userCustomSchedule2 = "CRON_TZ=Australia/Sydney 0 18 * * *"
|
||||
)
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
userID := uuid.New()
|
||||
s, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(defaultSchedule, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
mDB := dbmock.NewMockStore(gomock.NewController(t))
|
||||
|
||||
// User has no schedule set, use default.
|
||||
mDB.EXPECT().GetUserByID(gomock.Any(), userID).Return(database.User{}, nil).Times(1)
|
||||
opts, err := s.Get(context.Background(), mDB, userID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, opts.Schedule)
|
||||
require.Equal(t, defaultSchedule, opts.Schedule.String())
|
||||
require.False(t, opts.UserSet)
|
||||
require.True(t, opts.UserCanSet)
|
||||
|
||||
// User has a custom schedule set.
|
||||
mDB.EXPECT().GetUserByID(gomock.Any(), userID).Return(database.User{
|
||||
QuietHoursSchedule: userCustomSchedule1,
|
||||
}, nil).Times(1)
|
||||
opts, err = s.Get(context.Background(), mDB, userID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, opts.Schedule)
|
||||
require.Equal(t, userCustomSchedule1, opts.Schedule.String())
|
||||
require.True(t, opts.UserSet)
|
||||
require.True(t, opts.UserCanSet)
|
||||
|
||||
// Set user schedule.
|
||||
mDB.EXPECT().UpdateUserQuietHoursSchedule(gomock.Any(), database.UpdateUserQuietHoursScheduleParams{
|
||||
ID: userID,
|
||||
QuietHoursSchedule: userCustomSchedule2,
|
||||
}).Return(database.User{}, nil).Times(1)
|
||||
opts, err = s.Set(context.Background(), mDB, userID, userCustomSchedule2)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, opts.Schedule)
|
||||
require.Equal(t, userCustomSchedule2, opts.Schedule.String())
|
||||
require.True(t, opts.UserSet)
|
||||
})
|
||||
|
||||
t.Run("BadDefaultSchedule", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := schedule.NewEnterpriseUserQuietHoursScheduleStore("bad schedule", true)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `parse daily schedule "bad schedule"`)
|
||||
})
|
||||
|
||||
t.Run("BadGotSchedule", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
userID := uuid.New()
|
||||
s, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(defaultSchedule, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
mDB := dbmock.NewMockStore(gomock.NewController(t))
|
||||
|
||||
// User has a custom schedule set.
|
||||
mDB.EXPECT().GetUserByID(gomock.Any(), userID).Return(database.User{
|
||||
QuietHoursSchedule: "bad schedule",
|
||||
}, nil).Times(1)
|
||||
_, err = s.Get(context.Background(), mDB, userID)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `parse daily schedule "bad schedule"`)
|
||||
})
|
||||
|
||||
t.Run("BadSetSchedule", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(defaultSchedule, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use the mock DB here. It won't get used, but if it ever does it will
|
||||
// fail the test.
|
||||
mDB := dbmock.NewMockStore(gomock.NewController(t))
|
||||
_, err = s.Set(context.Background(), mDB, uuid.New(), "bad schedule")
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, `parse daily schedule "bad schedule"`)
|
||||
})
|
||||
|
||||
t.Run("UserCannotSet", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
userID := uuid.New()
|
||||
s, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(defaultSchedule, false) // <---
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use the mock DB here. It won't get used, but if it ever does it will
|
||||
// fail the test.
|
||||
mDB := dbmock.NewMockStore(gomock.NewController(t))
|
||||
|
||||
// Should never reach out to DB to check user's custom schedule.
|
||||
opts, err := s.Get(context.Background(), mDB, userID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, opts.Schedule)
|
||||
require.Equal(t, defaultSchedule, opts.Schedule.String())
|
||||
require.False(t, opts.UserSet)
|
||||
require.False(t, opts.UserCanSet)
|
||||
|
||||
// Set user schedule should fail.
|
||||
_, err = s.Set(context.Background(), mDB, userID, userCustomSchedule1)
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, agpl.ErrUserCannotSetQuietHoursSchedule)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user