feat: add server flag to disable user custom quiet hours (#11124)

This commit is contained in:
Dean Sheather
2023-12-15 19:33:51 +10:00
committed by GitHub
parent a58e4febb9
commit 1e49190e12
22 changed files with 358 additions and 46 deletions
+2 -2
View File
@@ -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)
+14 -3
View File
@@ -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
+131
View File
@@ -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)
})
}