mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
Two coderd unit tests (TestPatchCancelTemplateVersion/Success and TestPatchCancelWorkspaceBuild) implied erroneously that the job was canceled successfully. This is not the case, as these unit tests do not include a Provision_Complete response in the input to the echo provisioner. Now explicitly checking the job error and bumping the force cancel interval to be longer. Fixes #3083.
370 lines
14 KiB
Go
370 lines
14 KiB
Go
package coderd_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"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 TestWorkspaceBuild(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
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.WorkspaceBuild(context.Background(), workspace.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestWorkspaceBuildByBuildNumber(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Successful", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
first := coderdtest.CreateFirstUser(t, client)
|
|
user, err := client.User(context.Background(), codersdk.Me)
|
|
require.NoError(t, err, "fetch me")
|
|
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
|
|
_, err = client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
|
|
context.Background(),
|
|
user.Username,
|
|
workspace.Name,
|
|
strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10),
|
|
)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("BuildNumberNotInt", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
first := coderdtest.CreateFirstUser(t, client)
|
|
user, err := client.User(context.Background(), codersdk.Me)
|
|
require.NoError(t, err, "fetch me")
|
|
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
|
|
_, err = client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
|
|
context.Background(),
|
|
user.Username,
|
|
workspace.Name,
|
|
"buildNumber",
|
|
)
|
|
var apiError *codersdk.Error
|
|
require.ErrorAs(t, err, &apiError)
|
|
require.Equal(t, http.StatusBadRequest, apiError.StatusCode())
|
|
require.ErrorContains(t, apiError, "Failed to parse build number as integer.")
|
|
})
|
|
|
|
t.Run("WorkspaceNotFound", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
first := coderdtest.CreateFirstUser(t, client)
|
|
user, err := client.User(context.Background(), codersdk.Me)
|
|
require.NoError(t, err, "fetch me")
|
|
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
|
|
_, err = client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
|
|
context.Background(),
|
|
user.Username,
|
|
"workspaceName",
|
|
strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10),
|
|
)
|
|
var apiError *codersdk.Error
|
|
require.ErrorAs(t, err, &apiError)
|
|
require.Equal(t, http.StatusNotFound, apiError.StatusCode())
|
|
require.ErrorContains(t, apiError, "Resource not found")
|
|
})
|
|
|
|
t.Run("WorkspaceBuildNotFound", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
first := coderdtest.CreateFirstUser(t, client)
|
|
user, err := client.User(context.Background(), codersdk.Me)
|
|
require.NoError(t, err, "fetch me")
|
|
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
|
|
_, err = client.WorkspaceBuildByUsernameAndWorkspaceNameAndBuildNumber(
|
|
context.Background(),
|
|
user.Username,
|
|
workspace.Name,
|
|
"200",
|
|
)
|
|
var apiError *codersdk.Error
|
|
require.ErrorAs(t, err, &apiError)
|
|
require.Equal(t, http.StatusNotFound, apiError.StatusCode())
|
|
require.ErrorContains(t, apiError, fmt.Sprintf("Workspace %q Build 200 does not exist.", workspace.Name))
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceBuilds(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Single", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
first := coderdtest.CreateFirstUser(t, client)
|
|
user, err := client.User(context.Background(), codersdk.Me)
|
|
require.NoError(t, err, "fetch me")
|
|
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
|
|
template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
|
|
builds, err := client.WorkspaceBuilds(context.Background(),
|
|
codersdk.WorkspaceBuildsRequest{WorkspaceID: workspace.ID})
|
|
require.Len(t, builds, 1)
|
|
require.Equal(t, int32(1), builds[0].BuildNumber)
|
|
require.Equal(t, user.Username, builds[0].InitiatorUsername)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("PaginateNonExistentRow", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
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)
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
|
|
_, err := client.WorkspaceBuilds(ctx, codersdk.WorkspaceBuildsRequest{
|
|
WorkspaceID: workspace.ID,
|
|
Pagination: codersdk.Pagination{
|
|
AfterID: uuid.New(),
|
|
},
|
|
})
|
|
var apiError *codersdk.Error
|
|
require.ErrorAs(t, err, &apiError)
|
|
require.Equal(t, http.StatusBadRequest, apiError.StatusCode())
|
|
require.Contains(t, apiError.Message, "does not exist")
|
|
})
|
|
|
|
t.Run("PaginateLimitOffset", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
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)
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
var expectedBuilds []codersdk.WorkspaceBuild
|
|
extraBuilds := 4
|
|
for i := 0; i < extraBuilds; i++ {
|
|
b := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart)
|
|
expectedBuilds = append(expectedBuilds, b)
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, b.ID)
|
|
}
|
|
|
|
pageSize := 3
|
|
firstPage, err := client.WorkspaceBuilds(context.Background(), codersdk.WorkspaceBuildsRequest{
|
|
WorkspaceID: workspace.ID,
|
|
Pagination: codersdk.Pagination{Limit: pageSize, Offset: 0},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, firstPage, pageSize)
|
|
for i := 0; i < pageSize; i++ {
|
|
require.Equal(t, expectedBuilds[extraBuilds-i-1].ID, firstPage[i].ID)
|
|
}
|
|
secondPage, err := client.WorkspaceBuilds(context.Background(), codersdk.WorkspaceBuildsRequest{
|
|
WorkspaceID: workspace.ID,
|
|
Pagination: codersdk.Pagination{Limit: pageSize, Offset: pageSize},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, secondPage, 2)
|
|
require.Equal(t, expectedBuilds[0].ID, secondPage[0].ID)
|
|
require.Equal(t, workspace.LatestBuild.ID, secondPage[1].ID) // build created while creating workspace
|
|
})
|
|
}
|
|
|
|
func TestPatchCancelWorkspaceBuild(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
Parse: echo.ParseComplete,
|
|
Provision: []*proto.Provision_Response{{
|
|
Type: &proto.Provision_Response_Log{
|
|
Log: &proto.Log{},
|
|
},
|
|
}},
|
|
ProvisionDryRun: echo.ProvisionComplete,
|
|
})
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
var build codersdk.WorkspaceBuild
|
|
require.Eventually(t, func() bool {
|
|
var err error
|
|
build, err = client.WorkspaceBuild(context.Background(), workspace.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
return build.Job.Status == codersdk.ProvisionerJobRunning
|
|
}, 5*time.Second, 25*time.Millisecond)
|
|
err := client.CancelWorkspaceBuild(context.Background(), build.ID)
|
|
require.NoError(t, err)
|
|
require.Eventually(t, func() bool {
|
|
var err error
|
|
build, err = client.WorkspaceBuild(context.Background(), build.ID)
|
|
return assert.NoError(t, err) &&
|
|
// The job will never actually cancel successfully because it will never send a
|
|
// provision complete response.
|
|
assert.Empty(t, build.Job.Error) &&
|
|
build.Job.Status == codersdk.ProvisionerJobCanceling
|
|
}, 5*time.Second, 25*time.Millisecond)
|
|
}
|
|
|
|
func TestWorkspaceBuildResources(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("ListRunning", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
IncludeProvisionerD: true,
|
|
})
|
|
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.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
|
|
})
|
|
t.Run("List", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
Parse: echo.ParseComplete,
|
|
Provision: []*proto.Provision_Response{{
|
|
Type: &proto.Provision_Response_Complete{
|
|
Complete: &proto.Provision_Complete{
|
|
Resources: []*proto.Resource{{
|
|
Name: "some",
|
|
Type: "example",
|
|
Agents: []*proto.Agent{{
|
|
Id: "something",
|
|
Auth: &proto.Agent_Token{},
|
|
}},
|
|
}, {
|
|
Name: "another",
|
|
Type: "example",
|
|
}},
|
|
},
|
|
},
|
|
}},
|
|
})
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
resources, err := client.WorkspaceResourcesByBuild(context.Background(), workspace.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resources)
|
|
require.Len(t, resources, 2)
|
|
require.Equal(t, "some", resources[0].Name)
|
|
require.Equal(t, "example", resources[0].Type)
|
|
require.Len(t, resources[0].Agents, 1)
|
|
})
|
|
}
|
|
|
|
func TestWorkspaceBuildLogs(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
before := time.Now()
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
Parse: echo.ParseComplete,
|
|
Provision: []*proto.Provision_Response{{
|
|
Type: &proto.Provision_Response_Log{
|
|
Log: &proto.Log{
|
|
Level: proto.LogLevel_INFO,
|
|
Output: "example",
|
|
},
|
|
},
|
|
}, {
|
|
Type: &proto.Provision_Response_Complete{
|
|
Complete: &proto.Provision_Complete{
|
|
Resources: []*proto.Resource{{
|
|
Name: "some",
|
|
Type: "example",
|
|
Agents: []*proto.Agent{{
|
|
Id: "something",
|
|
Auth: &proto.Agent_Token{},
|
|
}},
|
|
}, {
|
|
Name: "another",
|
|
Type: "example",
|
|
}},
|
|
},
|
|
},
|
|
}},
|
|
})
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
t.Cleanup(cancelFunc)
|
|
logs, err := client.WorkspaceBuildLogsAfter(ctx, workspace.LatestBuild.ID, before.Add(-time.Hour))
|
|
require.NoError(t, err)
|
|
for {
|
|
log, ok := <-logs
|
|
if !ok {
|
|
break
|
|
}
|
|
if log.Output == "example" {
|
|
return
|
|
}
|
|
}
|
|
require.Fail(t, "example message never happened")
|
|
}
|
|
|
|
func TestWorkspaceBuildState(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
wantState := []byte("some kinda state")
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
Parse: echo.ParseComplete,
|
|
ProvisionDryRun: echo.ProvisionComplete,
|
|
Provision: []*proto.Provision_Response{{
|
|
Type: &proto.Provision_Response_Complete{
|
|
Complete: &proto.Provision_Complete{
|
|
State: wantState,
|
|
},
|
|
},
|
|
}},
|
|
})
|
|
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
|
gotState, err := client.WorkspaceBuildState(context.Background(), workspace.LatestBuild.ID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, wantState, gotState)
|
|
}
|