From f1b930b19065dbe675deb9c4c7b1abb663b3dd60 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 17 Dec 2025 17:28:38 +0000 Subject: [PATCH] chore(enterprise): increase coverage of TestWorkspaceBuild (#21304) Relates to #20925 This PR expands the test coverage of `enterprise/coderd/TestWorkspaceBuild` to also exercise the `postWorkspaceBuilds` handler. Previously, it only exercised the `createWorkspace` handler. --- enterprise/coderd/workspacebuilds_test.go | 224 ++++++++++++++-------- 1 file changed, 147 insertions(+), 77 deletions(-) diff --git a/enterprise/coderd/workspacebuilds_test.go b/enterprise/coderd/workspacebuilds_test.go index 8f9edbb933..d20eb4ed86 100644 --- a/enterprise/coderd/workspacebuilds_test.go +++ b/enterprise/coderd/workspacebuilds_test.go @@ -16,54 +16,64 @@ import ( func TestWorkspaceBuild(t *testing.T) { t.Parallel() - t.Run("TemplateRequiresActiveVersion", func(t *testing.T) { - t.Parallel() - ctx := testutil.Context(t, testutil.WaitMedium) - ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ - Options: &coderdtest.Options{ - IncludeProvisionerDaemon: true, + // Only use this context for setup. Use a separate context for subtests! + setupCtx := testutil.Context(t, testutil.WaitMedium) + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAccessControl: 1, + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureAdvancedTemplateScheduling: 1, }, - LicenseOptions: &coderdenttest.LicenseOptions{ - Features: license.Features{ - codersdk.FeatureAccessControl: 1, - codersdk.FeatureTemplateRBAC: 1, - codersdk.FeatureAdvancedTemplateScheduling: 1, - }, - }, - }) + }, + }) - // Create an initial version. - oldVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) - // Create a template that mandates the promoted version. - // This should be enforced for everyone except template admins. - template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, oldVersion.ID) - coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, oldVersion.ID) - require.Equal(t, oldVersion.ID, template.ActiveVersionID) - template = coderdtest.UpdateTemplateMeta(t, ownerClient, template.ID, codersdk.UpdateTemplateMeta{ - RequireActiveVersion: true, - }) - require.True(t, template.RequireActiveVersion) + // For this test we create two templates: + // tplA will be used to test creation of new workspaces. + // tplB will be used to test builds on existing workspaces. + // This is done to enable parallelization of the sub-tests without them interfering with each other. + // Both templates mandate the promoted version. + // This should be enforced for everyone except template admins. + tplAv1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) + tplA := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, tplAv1.ID) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, tplAv1.ID) + require.Equal(t, tplAv1.ID, tplA.ActiveVersionID) + tplA = coderdtest.UpdateTemplateMeta(t, ownerClient, tplA.ID, codersdk.UpdateTemplateMeta{ + RequireActiveVersion: true, + }) + require.True(t, tplA.RequireActiveVersion) + tplAv2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = tplA.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, tplAv2.ID) + coderdtest.UpdateActiveTemplateVersion(t, ownerClient, tplA.ID, tplAv2.ID) - // Create a new version that we will promote. - activeVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { - ctvr.TemplateID = template.ID - }) - coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, activeVersion.ID) - coderdtest.UpdateActiveTemplateVersion(t, ownerClient, template.ID, activeVersion.ID) + tplBv1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) + tplB := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, tplBv1.ID) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, tplBv1.ID) + require.Equal(t, tplBv1.ID, tplB.ActiveVersionID) + tplB = coderdtest.UpdateTemplateMeta(t, ownerClient, tplB.ID, codersdk.UpdateTemplateMeta{ + RequireActiveVersion: true, + }) + require.True(t, tplB.RequireActiveVersion) - templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) - templateACLAdminClient, templateACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - templateGroupACLAdminClient, templateGroupACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) + templateACLAdminClient, templateACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + templateGroupACLAdminClient, templateGroupACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - // Create a group so we can also test group template admin ownership. - // Add the user who gains template admin via group membership. - group := coderdtest.CreateGroup(t, ownerClient, owner.OrganizationID, "test", templateGroupACLAdmin) + // Create a group so we can also test group template admin ownership. + // Add the user who gains template admin via group membership. + group := coderdtest.CreateGroup(t, ownerClient, owner.OrganizationID, "test", templateGroupACLAdmin) - // Update the template for both users and groups. - //nolint:gocritic // test setup - err := ownerClient.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{ + // Update the template for both users and groups. + //nolint:gocritic // test setup + for _, tpl := range []codersdk.Template{tplA, tplB} { + err := ownerClient.UpdateTemplateACL(setupCtx, tpl.ID, codersdk.UpdateTemplateACL{ UserPerms: map[string]codersdk.TemplateRole{ templateACLAdmin.ID.String(): codersdk.TemplateRoleAdmin, }, @@ -71,51 +81,78 @@ func TestWorkspaceBuild(t *testing.T) { group.ID.String(): codersdk.TemplateRoleAdmin, }, }) - require.NoError(t, err) + require.NoError(t, err, "updating template ACL for template %q", tpl.ID) + } - type testcase struct { - Name string - Client *codersdk.Client - ExpectedStatusCode int - } + type testcase struct { + Name string + Client *codersdk.Client + ExpectedStatusCode int + } - cases := []testcase{ - { - Name: "OwnerOK", - Client: ownerClient, - ExpectedStatusCode: http.StatusOK, - }, - { - Name: "TemplateAdminOK", - Client: templateAdminClient, - ExpectedStatusCode: http.StatusOK, - }, - { - Name: "TemplateACLAdminOK", - Client: templateACLAdminClient, - ExpectedStatusCode: http.StatusOK, - }, - { - Name: "TemplateGroupACLAdminOK", - Client: templateGroupACLAdminClient, - ExpectedStatusCode: http.StatusOK, - }, - { - Name: "MemberFails", - Client: memberClient, - ExpectedStatusCode: http.StatusForbidden, - }, - } + cases := []testcase{ + { + Name: "OwnerOK", + Client: ownerClient, + ExpectedStatusCode: http.StatusOK, + }, + { + Name: "TemplateAdminOK", + Client: templateAdminClient, + ExpectedStatusCode: http.StatusOK, + }, + { + Name: "TemplateACLAdminOK", + Client: templateACLAdminClient, + ExpectedStatusCode: http.StatusOK, + }, + { + Name: "TemplateGroupACLAdminOK", + Client: templateGroupACLAdminClient, + ExpectedStatusCode: http.StatusOK, + }, + { + Name: "MemberFailsToCreate", + Client: memberClient, + ExpectedStatusCode: http.StatusForbidden, + }, + } + + // Create pre-existing workspaces for each of the test cases. + var extantWorkspaces []codersdk.Workspace + for _, c := range cases { + extantWs, err := c.Client.CreateUserWorkspace(setupCtx, codersdk.Me, codersdk.CreateWorkspaceRequest{ + TemplateVersionID: tplB.ActiveVersionID, + Name: testutil.GetRandomNameHyphenated(t), + AutomaticUpdates: codersdk.AutomaticUpdatesNever, + }) + require.NoError(t, err, "setup workspace for case %q", c.Name) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, c.Client, extantWs.LatestBuild.ID) + extantWorkspaces = append(extantWorkspaces, extantWs) + } + + // Create a new version of template B and promote it to be the active version. + tplBv2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) { + ctvr.TemplateID = tplB.ID + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, tplBv2.ID) + coderdtest.UpdateActiveTemplateVersion(t, ownerClient, tplB.ID, tplBv2.ID) + + t.Run("NewWorkspace", func(t *testing.T) { + t.Parallel() for _, c := range cases { t.Run(c.Name, func(t *testing.T) { - _, err = c.Client.CreateUserWorkspace(ctx, codersdk.Me, codersdk.CreateWorkspaceRequest{ - TemplateVersionID: oldVersion.ID, - Name: "abc123", + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + ws, err := c.Client.CreateUserWorkspace(ctx, codersdk.Me, codersdk.CreateWorkspaceRequest{ + TemplateVersionID: tplAv1.ID, + Name: testutil.GetRandomNameHyphenated(t), AutomaticUpdates: codersdk.AutomaticUpdatesNever, }) if c.ExpectedStatusCode == http.StatusOK { require.NoError(t, err) + require.Equal(t, tplAv1.ID, ws.LatestBuild.TemplateVersionID, "workspace did not use expected version for case %q", c.Name) } else { require.Error(t, err) cerr, ok := codersdk.AsError(err) @@ -125,4 +162,37 @@ func TestWorkspaceBuild(t *testing.T) { }) } }) + + t.Run("ExistingWorkspace", func(t *testing.T) { + t.Parallel() + + for idx, c := range cases { + t.Run(c.Name, func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + // Stopping the workspace must always succeed. + wb, err := c.Client.CreateWorkspaceBuild(ctx, extantWorkspaces[idx].ID, codersdk.CreateWorkspaceBuildRequest{ + Transition: codersdk.WorkspaceTransitionStop, + }) + require.NoError(t, err, "stopping workspace for case %q", c.Name) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, c.Client, wb.ID) + + // Attempt to start the workspace with the given version. + wb, err = c.Client.CreateWorkspaceBuild(ctx, extantWorkspaces[idx].ID, codersdk.CreateWorkspaceBuildRequest{ + Transition: codersdk.WorkspaceTransitionStart, + TemplateVersionID: tplBv1.ID, + }) + if c.ExpectedStatusCode == http.StatusOK { + require.NoError(t, err, "starting workspace for case %q", c.Name) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, c.Client, wb.ID) + require.Equal(t, tplBv1.ID, wb.TemplateVersionID, "workspace did not use expected version for case %q", c.Name) + } else { + require.Error(t, err, "starting workspace for case %q", c.Name) + cerr, ok := codersdk.AsError(err) + require.True(t, ok) + require.Equal(t, c.ExpectedStatusCode, cerr.StatusCode()) + } + }) + } + }) }