@@ -2,6 +2,7 @@ package coderd_test
2
2
3
3
import (
4
4
"context"
5
+ "database/sql"
5
6
"net/http"
6
7
"sync/atomic"
7
8
"testing"
@@ -18,8 +19,10 @@ import (
18
19
"github.com/coder/coder/v2/coderd/autobuild"
19
20
"github.com/coder/coder/v2/coderd/coderdtest"
20
21
"github.com/coder/coder/v2/coderd/database"
22
+ "github.com/coder/coder/v2/coderd/database/dbauthz"
21
23
"github.com/coder/coder/v2/coderd/database/dbfake"
22
24
"github.com/coder/coder/v2/coderd/database/dbtestutil"
25
+ "github.com/coder/coder/v2/coderd/database/dbtime"
23
26
"github.com/coder/coder/v2/coderd/notifications"
24
27
"github.com/coder/coder/v2/coderd/rbac"
25
28
agplschedule "github.com/coder/coder/v2/coderd/schedule"
@@ -1138,7 +1141,6 @@ func TestWorkspaceAutobuild(t *testing.T) {
1138
1141
1139
1142
// First create a template that only supports Monday-Friday
1140
1143
template := coderdtest .CreateTemplate (t , client , user .OrganizationID , version1 .ID , func (ctr * codersdk.CreateTemplateRequest ) {
1141
- ctr .AllowUserAutostart = ptr .Ref (true )
1142
1144
ctr .AutostartRequirement = & codersdk.TemplateAutostartRequirement {DaysOfWeek : codersdk .BitmapToWeekdays (0b00011111 )}
1143
1145
})
1144
1146
require .Equal (t , version1 .ID , template .ActiveVersionID )
@@ -1680,6 +1682,91 @@ func TestAdminViewAllWorkspaces(t *testing.T) {
1680
1682
require .Equal (t , 0 , len (memberViewWorkspaces .Workspaces ), "member in other org should see 0 workspaces" )
1681
1683
}
1682
1684
1685
+ func TestNextStartAtIsNullifiedOnScheduleChange (t * testing.T ) {
1686
+ t .Parallel ()
1687
+
1688
+ var (
1689
+ tickCh = make (chan time.Time )
1690
+ statsCh = make (chan autobuild.Stats )
1691
+ clock = quartz .NewMock (t )
1692
+ )
1693
+
1694
+ // Set the clock to 8AM Monday, 1st January, 2024 to keep
1695
+ // this test deterministic.
1696
+ clock .Set (time .Date (2024 , 1 , 1 , 8 , 0 , 0 , 0 , time .UTC ))
1697
+
1698
+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true }).Leveled (slog .LevelDebug )
1699
+ client , db , user := coderdenttest .NewWithDatabase (t , & coderdenttest.Options {
1700
+ Options : & coderdtest.Options {
1701
+ AutobuildTicker : tickCh ,
1702
+ IncludeProvisionerDaemon : true ,
1703
+ AutobuildStats : statsCh ,
1704
+ Logger : & logger ,
1705
+ TemplateScheduleStore : schedule .NewEnterpriseTemplateScheduleStore (agplUserQuietHoursScheduleStore (), notifications .NewNoopEnqueuer (), logger , clock ),
1706
+ },
1707
+ LicenseOptions : & coderdenttest.LicenseOptions {
1708
+ Features : license.Features {codersdk .FeatureAdvancedTemplateScheduling : 1 },
1709
+ },
1710
+ })
1711
+
1712
+ version := coderdtest .CreateTemplateVersion (t , client , user .OrganizationID , nil )
1713
+ coderdtest .AwaitTemplateVersionJobCompleted (t , client , version .ID )
1714
+
1715
+ // Create a template that allows autostart Monday-Sunday
1716
+ template := coderdtest .CreateTemplate (t , client , user .OrganizationID , version .ID , func (ctr * codersdk.CreateTemplateRequest ) {
1717
+ ctr .AutostartRequirement = & codersdk.TemplateAutostartRequirement {DaysOfWeek : codersdk .AllDaysOfWeek }
1718
+ })
1719
+ require .Equal (t , version .ID , template .ActiveVersionID )
1720
+
1721
+ // Create a workspace with a schedule Sunday-Saturday
1722
+ sched , err := cron .Weekly ("CRON_TZ=UTC 0 9 * * 0-6" )
1723
+ require .NoError (t , err )
1724
+ ws := coderdtest .CreateWorkspace (t , client , template .ID , func (cwr * codersdk.CreateWorkspaceRequest ) {
1725
+ cwr .AutostartSchedule = ptr .Ref (sched .String ())
1726
+ })
1727
+
1728
+ coderdtest .AwaitWorkspaceBuildJobCompleted (t , client , ws .LatestBuild .ID )
1729
+ ws = coderdtest .MustTransitionWorkspace (t , client , ws .ID , database .WorkspaceTransitionStart , database .WorkspaceTransitionStop )
1730
+
1731
+ // Check we have a 'NextStartAt'
1732
+ require .NotNil (t , ws .NextStartAt )
1733
+
1734
+ // Create a new slightly different cron schedule that could
1735
+ // potentially make NextStartAt invalid.
1736
+ sched , err = cron .Weekly ("CRON_TZ=UTC 0 9 * * 1-6" )
1737
+ require .NoError (t , err )
1738
+ ctx := testutil .Context (t , testutil .WaitShort )
1739
+
1740
+ // We want to test the database nullifies the NextStartAt so we
1741
+ // make a raw DB call here. We pass in NextStartAt here so we
1742
+ // can test the database will nullify it and not us.
1743
+ //nolint: gocritic // We need system context to modify this.
1744
+ err = db .UpdateWorkspaceAutostart (dbauthz .AsSystemRestricted (ctx ), database.UpdateWorkspaceAutostartParams {
1745
+ ID : ws .ID ,
1746
+ AutostartSchedule : sql.NullString {Valid : true , String : sched .String ()},
1747
+ NextStartAt : sql.NullTime {Valid : true , Time : * ws .NextStartAt },
1748
+ })
1749
+ require .NoError (t , err )
1750
+
1751
+ ws = coderdtest .MustWorkspace (t , client , ws .ID )
1752
+
1753
+ // Check 'NextStartAt' has been nullified
1754
+ require .Nil (t , ws .NextStartAt )
1755
+
1756
+ // Now we let the lifecycle executor run. This should spot that the
1757
+ // NextStartAt is null and update it for us.
1758
+ next := dbtime .Now ()
1759
+ tickCh <- next
1760
+ stats := <- statsCh
1761
+ assert .Len (t , stats .Errors , 0 )
1762
+ assert .Len (t , stats .Transitions , 0 )
1763
+
1764
+ // Ensure NextStartAt has been set, and is the expected value
1765
+ ws = coderdtest .MustWorkspace (t , client , ws .ID )
1766
+ require .NotNil (t , ws .NextStartAt )
1767
+ require .Equal (t , sched .Next (next ), ws .NextStartAt .UTC ())
1768
+ }
1769
+
1683
1770
func must [T any ](value T , err error ) T {
1684
1771
if err != nil {
1685
1772
panic (err )
0 commit comments