@@ -309,7 +309,136 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
309
309
310
310
err := client .UpdateWorkspaceAutostart (ctx ,wsid ,req )
311
311
require .IsType (t ,err ,& codersdk.Error {},"expected codersdk.Error" )
312
- coderSDKErr ,_ := err .(* codersdk.Error )
312
+ coderSDKErr ,_ := err .(* codersdk.Error )//nolint:errorlint
313
+ require .Equal (t ,coderSDKErr .StatusCode (),404 ,"expected status code 404" )
314
+ require .Equal (t ,fmt .Sprintf ("workspace %q does not exist" ,wsid ),coderSDKErr .Message ,"unexpected response code" )
315
+ })
316
+ }
317
+
318
+ func TestWorkspaceUpdateAutostop (t * testing.T ) {
319
+ t .Parallel ()
320
+ var dublinLoc = mustLocation (t ,"Europe/Dublin" )
321
+
322
+ testCases := []struct {
323
+ name string
324
+ schedule string
325
+ expectedError string
326
+ at time.Time
327
+ expectedNext time.Time
328
+ expectedInterval time.Duration
329
+ }{
330
+ {
331
+ name :"disable autostop" ,
332
+ schedule :"" ,
333
+ expectedError :"" ,
334
+ },
335
+ {
336
+ name :"friday to monday" ,
337
+ schedule :"CRON_TZ=Europe/Dublin 30 17 1-5" ,
338
+ expectedError :"" ,
339
+ at :time .Date (2022 ,5 ,6 ,17 ,31 ,0 ,0 ,dublinLoc ),
340
+ expectedNext :time .Date (2022 ,5 ,9 ,17 ,30 ,0 ,0 ,dublinLoc ),
341
+ expectedInterval :71 * time .Hour + 59 * time .Minute ,
342
+ },
343
+ {
344
+ name :"monday to tuesday" ,
345
+ schedule :"CRON_TZ=Europe/Dublin 30 17 1-5" ,
346
+ expectedError :"" ,
347
+ at :time .Date (2022 ,5 ,9 ,17 ,31 ,0 ,0 ,dublinLoc ),
348
+ expectedNext :time .Date (2022 ,5 ,10 ,17 ,30 ,0 ,0 ,dublinLoc ),
349
+ expectedInterval :23 * time .Hour + 59 * time .Minute ,
350
+ },
351
+ {
352
+ // DST in Ireland began on Mar 27 in 2022 at 0100. Forward 1 hour.
353
+ name :"DST start" ,
354
+ schedule :"CRON_TZ=Europe/Dublin 30 17 *" ,
355
+ expectedError :"" ,
356
+ at :time .Date (2022 ,3 ,26 ,17 ,31 ,0 ,0 ,dublinLoc ),
357
+ expectedNext :time .Date (2022 ,3 ,27 ,17 ,30 ,0 ,0 ,dublinLoc ),
358
+ expectedInterval :22 * time .Hour + 59 * time .Minute ,
359
+ },
360
+ {
361
+ // DST in Ireland ends on Oct 30 in 2022 at 0200. Back 1 hour.
362
+ name :"DST end" ,
363
+ schedule :"CRON_TZ=Europe/Dublin 30 17 *" ,
364
+ expectedError :"" ,
365
+ at :time .Date (2022 ,10 ,29 ,17 ,31 ,0 ,0 ,dublinLoc ),
366
+ expectedNext :time .Date (2022 ,10 ,30 ,17 ,30 ,0 ,0 ,dublinLoc ),
367
+ expectedInterval :24 * time .Hour + 59 * time .Minute ,
368
+ },
369
+ {
370
+ name :"invalid location" ,
371
+ schedule :"CRON_TZ=Imaginary/Place 30 17 1-5" ,
372
+ expectedError :"status code 500: invalid autostop schedule: parse schedule: provided bad location Imaginary/Place: unknown time zone Imaginary/Place" ,
373
+ },
374
+ {
375
+ name :"invalid schedule" ,
376
+ schedule :"asdf asdf asdf " ,
377
+ expectedError :`status code 500: invalid autostop schedule: parse schedule: failed to parse int from asdf: strconv.Atoi: parsing "asdf": invalid syntax` ,
378
+ },
379
+ }
380
+
381
+ for _ ,testCase := range testCases {
382
+ testCase := testCase
383
+ t .Run (testCase .name ,func (t * testing.T ) {
384
+ t .Parallel ()
385
+ var (
386
+ ctx = context .Background ()
387
+ client = coderdtest .New (t ,nil )
388
+ _ = coderdtest .NewProvisionerDaemon (t ,client )
389
+ user = coderdtest .CreateFirstUser (t ,client )
390
+ version = coderdtest .CreateTemplateVersion (t ,client ,user .OrganizationID ,nil )
391
+ _ = coderdtest .AwaitTemplateVersionJob (t ,client ,version .ID )
392
+ project = coderdtest .CreateTemplate (t ,client ,user .OrganizationID ,version .ID )
393
+ workspace = coderdtest .CreateWorkspace (t ,client ,codersdk .Me ,project .ID )
394
+ )
395
+
396
+ // ensure test invariant: new workspaces have no autostop schedule.
397
+ require .Empty (t ,workspace .AutostopSchedule ,"expected newly-minted workspace to have no autstop schedule" )
398
+
399
+ err := client .UpdateWorkspaceAutostop (ctx ,workspace .ID , codersdk.UpdateWorkspaceAutostopRequest {
400
+ Schedule :testCase .schedule ,
401
+ })
402
+
403
+ if testCase .expectedError != "" {
404
+ require .EqualError (t ,err ,testCase .expectedError ,"unexpected error when setting workspace autostop schedule" )
405
+ return
406
+ }
407
+
408
+ require .NoError (t ,err ,"expected no error setting workspace autostop schedule" )
409
+
410
+ updated ,err := client .Workspace (ctx ,workspace .ID )
411
+ require .NoError (t ,err ,"fetch updated workspace" )
412
+
413
+ require .Equal (t ,testCase .schedule ,updated .AutostopSchedule ,"expected autostop schedule to equal requested" )
414
+
415
+ if testCase .schedule == "" {
416
+ return
417
+ }
418
+ sched ,err := schedule .Weekly (updated .AutostopSchedule )
419
+ require .NoError (t ,err ,"parse returned schedule" )
420
+
421
+ next := sched .Next (testCase .at )
422
+ require .Equal (t ,testCase .expectedNext ,next ,"unexpected next scheduled autostop time" )
423
+ interval := next .Sub (testCase .at )
424
+ require .Equal (t ,testCase .expectedInterval ,interval ,"unexpected interval" )
425
+ })
426
+ }
427
+
428
+ t .Run ("NotFound" ,func (t * testing.T ) {
429
+ var (
430
+ ctx = context .Background ()
431
+ client = coderdtest .New (t ,nil )
432
+ _ = coderdtest .CreateFirstUser (t ,client )
433
+ wsid = uuid .New ()
434
+ req = codersdk.UpdateWorkspaceAutostopRequest {
435
+ Schedule :"9 30 1-5" ,
436
+ }
437
+ )
438
+
439
+ err := client .UpdateWorkspaceAutostop (ctx ,wsid ,req )
440
+ require .IsType (t ,err ,& codersdk.Error {},"expected codersdk.Error" )
441
+ coderSDKErr ,_ := err .(* codersdk.Error )//nolint:errorlint
313
442
require .Equal (t ,coderSDKErr .StatusCode (),404 ,"expected status code 404" )
314
443
require .Equal (t ,fmt .Sprintf ("workspace %q does not exist" ,wsid ),coderSDKErr .Message ,"unexpected response code" )
315
444
})