mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
They're currently randomly in a bunch of different files. This cleans up the handler functions to be in the file of the type they return.
615 lines
24 KiB
Go
615 lines
24 KiB
Go
package coderd_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/coderd/autobuild/schedule"
|
|
"github.com/coder/coder/coderd/coderdtest"
|
|
"github.com/coder/coder/coderd/database"
|
|
"github.com/coder/coder/codersdk"
|
|
"github.com/coder/coder/provisioner/echo"
|
|
"github.com/coder/coder/provisionersdk/proto"
|
|
)
|
|
|
|
func TestWorkspace(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
_, err := client.Workspace(context.Background(), workspace.ID)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestPostWorkspacesByOrganization(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("InvalidTemplate", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
_, err := client.CreateWorkspace(context.Background(), user.OrganizationID, codersdk.CreateWorkspaceRequest{
|
|
TemplateID: uuid.New(),
|
|
Name: "workspace",
|
|
})
|
|
require.Error(t, err)
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
|
})
|
|
|
|
t.Run("NoTemplateAccess", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
first := coderdtest.CreateFirstUser(t, client)
|
|
|
|
other := coderdtest.CreateAnotherUser(t, client, first.OrganizationID)
|
|
org, err := other.CreateOrganization(context.Background(), codersdk.Me, codersdk.CreateOrganizationRequest{
|
|
Name: "another",
|
|
})
|
|
require.NoError(t, err)
|
|
version := coderdtest.CreateTemplateVersion(t, other, org.ID, nil)
|
|
template := coderdtest.CreateTemplate(t, other, org.ID, version.ID)
|
|
|
|
_, err = client.CreateWorkspace(context.Background(), first.OrganizationID, codersdk.CreateWorkspaceRequest{
|
|
TemplateID: template.ID,
|
|
Name: "workspace",
|
|
})
|
|
require.Error(t, err)
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
|
})
|
|
|
|
t.Run("AlreadyExists", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
_, err := client.CreateWorkspace(context.Background(), user.OrganizationID, codersdk.CreateWorkspaceRequest{
|
|
TemplateID: template.ID,
|
|
Name: workspace.Name,
|
|
})
|
|
require.Error(t, err)
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
|
})
|
|
|
|
t.Run("Create", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
_ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
})
|
|
}
|
|
|
|
func TestWorkspacesByOrganization(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("ListEmpty", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
_, err := client.WorkspacesByOrganization(context.Background(), user.OrganizationID)
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("List", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
_ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
workspaces, err := client.WorkspacesByOrganization(context.Background(), user.OrganizationID)
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces, 1)
|
|
})
|
|
}
|
|
|
|
func TestWorkspacesByOwner(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("ListEmpty", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
_, err := client.WorkspacesByOwner(context.Background(), user.OrganizationID, codersdk.Me)
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("List", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
_ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
workspaces, err := client.WorkspacesByOwner(context.Background(), user.OrganizationID, codersdk.Me)
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces, 1)
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceByOwnerAndName(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("NotFound", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
_, err := client.WorkspaceByOwnerAndName(context.Background(), user.OrganizationID, codersdk.Me, "something")
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
|
|
})
|
|
t.Run("Get", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
_, err := client.WorkspaceByOwnerAndName(context.Background(), user.OrganizationID, codersdk.Me, workspace.Name)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestPostWorkspaceBuild(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("NoTemplateVersion", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
_, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
|
TemplateVersionID: uuid.New(),
|
|
Transition: database.WorkspaceTransitionStart,
|
|
})
|
|
require.Error(t, err)
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
|
})
|
|
|
|
t.Run("TemplateVersionFailedImport", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
Provision: []*proto.Provision_Response{{}},
|
|
})
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
_, err := client.CreateWorkspace(context.Background(), user.OrganizationID, codersdk.CreateWorkspaceRequest{
|
|
TemplateID: template.ID,
|
|
Name: "workspace",
|
|
})
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
|
|
})
|
|
|
|
t.Run("AlreadyActive", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
closeDaemon := coderdtest.NewProvisionerDaemon(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
// Close here so workspace build doesn't process!
|
|
closeDaemon.Close()
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
_, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
|
TemplateVersionID: template.ActiveVersionID,
|
|
Transition: database.WorkspaceTransitionStart,
|
|
})
|
|
require.Error(t, err)
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
|
})
|
|
|
|
t.Run("UpdatePriorAfterField", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
|
TemplateVersionID: template.ActiveVersionID,
|
|
Transition: database.WorkspaceTransitionStart,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, workspace.LatestBuild.ID.String(), build.BeforeID.String())
|
|
|
|
firstBuild, err := client.WorkspaceBuild(context.Background(), workspace.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, build.ID.String(), firstBuild.AfterID.String())
|
|
})
|
|
|
|
t.Run("WithState", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
closeDaemon := coderdtest.NewProvisionerDaemon(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
_ = closeDaemon.Close()
|
|
wantState := []byte("something")
|
|
build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
|
TemplateVersionID: template.ActiveVersionID,
|
|
Transition: database.WorkspaceTransitionStart,
|
|
ProvisionerState: wantState,
|
|
})
|
|
require.NoError(t, err)
|
|
gotState, err := client.WorkspaceBuildState(context.Background(), build.ID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, wantState, gotState)
|
|
})
|
|
|
|
t.Run("Delete", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
build, err := client.CreateWorkspaceBuild(context.Background(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
|
Transition: database.WorkspaceTransitionDelete,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, workspace.LatestBuild.ID.String(), build.BeforeID.String())
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
|
|
|
|
workspaces, err := client.WorkspacesByOwner(context.Background(), user.OrganizationID, user.UserID)
|
|
require.NoError(t, err)
|
|
require.Len(t, workspaces, 0)
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceBuildByName(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("NotFound", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
_, err := client.WorkspaceBuildByName(context.Background(), workspace.ID, "something")
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
|
|
})
|
|
|
|
t.Run("Found", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, nil)
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
coderdtest.NewProvisionerDaemon(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
build, err := client.WorkspaceBuild(context.Background(), workspace.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
_, err = client.WorkspaceBuildByName(context.Background(), workspace.ID, build.Name)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|
t.Parallel()
|
|
var dublinLoc = mustLocation(t, "Europe/Dublin")
|
|
|
|
testCases := []struct {
|
|
name string
|
|
schedule string
|
|
expectedError string
|
|
at time.Time
|
|
expectedNext time.Time
|
|
expectedInterval time.Duration
|
|
}{
|
|
{
|
|
name: "disable autostart",
|
|
schedule: "",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "friday to monday",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 9 * * 1-5",
|
|
expectedError: "",
|
|
at: time.Date(2022, 5, 6, 9, 31, 0, 0, dublinLoc),
|
|
expectedNext: time.Date(2022, 5, 9, 9, 30, 0, 0, dublinLoc),
|
|
expectedInterval: 71*time.Hour + 59*time.Minute,
|
|
},
|
|
{
|
|
name: "monday to tuesday",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 9 * * 1-5",
|
|
expectedError: "",
|
|
at: time.Date(2022, 5, 9, 9, 31, 0, 0, dublinLoc),
|
|
expectedNext: time.Date(2022, 5, 10, 9, 30, 0, 0, dublinLoc),
|
|
expectedInterval: 23*time.Hour + 59*time.Minute,
|
|
},
|
|
{
|
|
// DST in Ireland began on Mar 27 in 2022 at 0100. Forward 1 hour.
|
|
name: "DST start",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 9 * * *",
|
|
expectedError: "",
|
|
at: time.Date(2022, 3, 26, 9, 31, 0, 0, dublinLoc),
|
|
expectedNext: time.Date(2022, 3, 27, 9, 30, 0, 0, dublinLoc),
|
|
expectedInterval: 22*time.Hour + 59*time.Minute,
|
|
},
|
|
{
|
|
// DST in Ireland ends on Oct 30 in 2022 at 0200. Back 1 hour.
|
|
name: "DST end",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 9 * * *",
|
|
expectedError: "",
|
|
at: time.Date(2022, 10, 29, 9, 31, 0, 0, dublinLoc),
|
|
expectedNext: time.Date(2022, 10, 30, 9, 30, 0, 0, dublinLoc),
|
|
expectedInterval: 24*time.Hour + 59*time.Minute,
|
|
},
|
|
{
|
|
name: "invalid location",
|
|
schedule: "CRON_TZ=Imaginary/Place 30 9 * * 1-5",
|
|
expectedError: "status code 500: invalid autostart schedule: parse schedule: provided bad location Imaginary/Place: unknown time zone Imaginary/Place",
|
|
},
|
|
{
|
|
name: "invalid schedule",
|
|
schedule: "asdf asdf asdf ",
|
|
expectedError: `status code 500: invalid autostart schedule: validate weekly schedule: expected schedule to consist of 5 fields with an optional CRON_TZ=<timezone> prefix`,
|
|
},
|
|
{
|
|
name: "only 3 values",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 9 *",
|
|
expectedError: `status code 500: invalid autostart schedule: validate weekly schedule: expected schedule to consist of 5 fields with an optional CRON_TZ=<timezone> prefix`,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
testCase := testCase
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
var (
|
|
ctx = context.Background()
|
|
client = coderdtest.New(t, nil)
|
|
_ = coderdtest.NewProvisionerDaemon(t, client)
|
|
user = coderdtest.CreateFirstUser(t, client)
|
|
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
|
|
)
|
|
|
|
// ensure test invariant: new workspaces have no autostart schedule.
|
|
require.Empty(t, workspace.AutostartSchedule, "expected newly-minted workspace to have no autostart schedule")
|
|
|
|
err := client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
|
Schedule: testCase.schedule,
|
|
})
|
|
|
|
if testCase.expectedError != "" {
|
|
require.EqualError(t, err, testCase.expectedError, "unexpected error when setting workspace autostart schedule")
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err, "expected no error setting workspace autostart schedule")
|
|
|
|
updated, err := client.Workspace(ctx, workspace.ID)
|
|
require.NoError(t, err, "fetch updated workspace")
|
|
|
|
require.Equal(t, testCase.schedule, updated.AutostartSchedule, "expected autostart schedule to equal requested")
|
|
|
|
if testCase.schedule == "" {
|
|
return
|
|
}
|
|
sched, err := schedule.Weekly(updated.AutostartSchedule)
|
|
require.NoError(t, err, "parse returned schedule")
|
|
|
|
next := sched.Next(testCase.at)
|
|
require.Equal(t, testCase.expectedNext, next, "unexpected next scheduled autostart time")
|
|
interval := next.Sub(testCase.at)
|
|
require.Equal(t, testCase.expectedInterval, interval, "unexpected interval")
|
|
})
|
|
}
|
|
|
|
t.Run("NotFound", func(t *testing.T) {
|
|
var (
|
|
ctx = context.Background()
|
|
client = coderdtest.New(t, nil)
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
|
wsid = uuid.New()
|
|
req = codersdk.UpdateWorkspaceAutostartRequest{
|
|
Schedule: "9 30 1-5",
|
|
}
|
|
)
|
|
|
|
err := client.UpdateWorkspaceAutostart(ctx, wsid, req)
|
|
require.IsType(t, err, &codersdk.Error{}, "expected codersdk.Error")
|
|
coderSDKErr, _ := err.(*codersdk.Error) //nolint:errorlint
|
|
require.Equal(t, coderSDKErr.StatusCode(), 404, "expected status code 404")
|
|
require.Equal(t, fmt.Sprintf("workspace %q does not exist", wsid), coderSDKErr.Message, "unexpected response code")
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceUpdateAutostop(t *testing.T) {
|
|
t.Parallel()
|
|
var dublinLoc = mustLocation(t, "Europe/Dublin")
|
|
|
|
testCases := []struct {
|
|
name string
|
|
schedule string
|
|
expectedError string
|
|
at time.Time
|
|
expectedNext time.Time
|
|
expectedInterval time.Duration
|
|
}{
|
|
{
|
|
name: "disable autostop",
|
|
schedule: "",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "friday to monday",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 17 * * 1-5",
|
|
expectedError: "",
|
|
at: time.Date(2022, 5, 6, 17, 31, 0, 0, dublinLoc),
|
|
expectedNext: time.Date(2022, 5, 9, 17, 30, 0, 0, dublinLoc),
|
|
expectedInterval: 71*time.Hour + 59*time.Minute,
|
|
},
|
|
{
|
|
name: "monday to tuesday",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 17 * * 1-5",
|
|
expectedError: "",
|
|
at: time.Date(2022, 5, 9, 17, 31, 0, 0, dublinLoc),
|
|
expectedNext: time.Date(2022, 5, 10, 17, 30, 0, 0, dublinLoc),
|
|
expectedInterval: 23*time.Hour + 59*time.Minute,
|
|
},
|
|
{
|
|
// DST in Ireland began on Mar 27 in 2022 at 0100. Forward 1 hour.
|
|
name: "DST start",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 17 * * *",
|
|
expectedError: "",
|
|
at: time.Date(2022, 3, 26, 17, 31, 0, 0, dublinLoc),
|
|
expectedNext: time.Date(2022, 3, 27, 17, 30, 0, 0, dublinLoc),
|
|
expectedInterval: 22*time.Hour + 59*time.Minute,
|
|
},
|
|
{
|
|
// DST in Ireland ends on Oct 30 in 2022 at 0200. Back 1 hour.
|
|
name: "DST end",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 17 * * *",
|
|
expectedError: "",
|
|
at: time.Date(2022, 10, 29, 17, 31, 0, 0, dublinLoc),
|
|
expectedNext: time.Date(2022, 10, 30, 17, 30, 0, 0, dublinLoc),
|
|
expectedInterval: 24*time.Hour + 59*time.Minute,
|
|
},
|
|
{
|
|
name: "invalid location",
|
|
schedule: "CRON_TZ=Imaginary/Place 30 17 * * 1-5",
|
|
expectedError: "status code 500: invalid autostop schedule: parse schedule: provided bad location Imaginary/Place: unknown time zone Imaginary/Place",
|
|
},
|
|
{
|
|
name: "invalid schedule",
|
|
schedule: "asdf asdf asdf ",
|
|
expectedError: `status code 500: invalid autostop schedule: validate weekly schedule: expected schedule to consist of 5 fields with an optional CRON_TZ=<timezone> prefix`,
|
|
},
|
|
{
|
|
name: "only 3 values",
|
|
schedule: "CRON_TZ=Europe/Dublin 30 9 *",
|
|
expectedError: `status code 500: invalid autostop schedule: validate weekly schedule: expected schedule to consist of 5 fields with an optional CRON_TZ=<timezone> prefix`,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
testCase := testCase
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
var (
|
|
ctx = context.Background()
|
|
client = coderdtest.New(t, nil)
|
|
_ = coderdtest.NewProvisionerDaemon(t, client)
|
|
user = coderdtest.CreateFirstUser(t, client)
|
|
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
|
|
)
|
|
|
|
// ensure test invariant: new workspaces have no autostop schedule.
|
|
require.Empty(t, workspace.AutostopSchedule, "expected newly-minted workspace to have no autstop schedule")
|
|
|
|
err := client.UpdateWorkspaceAutostop(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostopRequest{
|
|
Schedule: testCase.schedule,
|
|
})
|
|
|
|
if testCase.expectedError != "" {
|
|
require.EqualError(t, err, testCase.expectedError, "unexpected error when setting workspace autostop schedule")
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err, "expected no error setting workspace autostop schedule")
|
|
|
|
updated, err := client.Workspace(ctx, workspace.ID)
|
|
require.NoError(t, err, "fetch updated workspace")
|
|
|
|
require.Equal(t, testCase.schedule, updated.AutostopSchedule, "expected autostop schedule to equal requested")
|
|
|
|
if testCase.schedule == "" {
|
|
return
|
|
}
|
|
sched, err := schedule.Weekly(updated.AutostopSchedule)
|
|
require.NoError(t, err, "parse returned schedule")
|
|
|
|
next := sched.Next(testCase.at)
|
|
require.Equal(t, testCase.expectedNext, next, "unexpected next scheduled autostop time")
|
|
interval := next.Sub(testCase.at)
|
|
require.Equal(t, testCase.expectedInterval, interval, "unexpected interval")
|
|
})
|
|
}
|
|
|
|
t.Run("NotFound", func(t *testing.T) {
|
|
var (
|
|
ctx = context.Background()
|
|
client = coderdtest.New(t, nil)
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
|
wsid = uuid.New()
|
|
req = codersdk.UpdateWorkspaceAutostopRequest{
|
|
Schedule: "9 30 1-5",
|
|
}
|
|
)
|
|
|
|
err := client.UpdateWorkspaceAutostop(ctx, wsid, req)
|
|
require.IsType(t, err, &codersdk.Error{}, "expected codersdk.Error")
|
|
coderSDKErr, _ := err.(*codersdk.Error) //nolint:errorlint
|
|
require.Equal(t, coderSDKErr.StatusCode(), 404, "expected status code 404")
|
|
require.Equal(t, fmt.Sprintf("workspace %q does not exist", wsid), coderSDKErr.Message, "unexpected response code")
|
|
})
|
|
}
|
|
|
|
func mustLocation(t *testing.T, location string) *time.Location {
|
|
loc, err := time.LoadLocation(location)
|
|
if err != nil {
|
|
t.Errorf("failed to load location %s: %s", location, err.Error())
|
|
}
|
|
|
|
return loc
|
|
}
|