From 967db2801b270491046e7227dee7f1140a186975 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Thu, 30 Nov 2023 19:33:04 -0600 Subject: [PATCH] chore: refactor ResolveAutostart tests to use dbfake (#10603) --- cli/agent_test.go | 26 +- cli/configssh_test.go | 18 +- cli/gitssh_test.go | 12 +- cli/list_test.go | 6 +- cli/portforward_test.go | 3 +- cli/schedule_test.go | 9 +- cli/ssh_test.go | 15 +- coderd/coderdtest/coderdtest.go | 2 + coderd/database/dbfake/dbfake.go | 344 ++++++++++++------ coderd/database/dbgen/dbgen.go | 66 +++- coderd/database/dbgen/dbgen_test.go | 16 + coderd/workspaceagents_test.go | 40 +- coderd/workspaces_test.go | 134 +++---- .../coderd/coderdenttest/coderdenttest.go | 6 + enterprise/coderd/workspaces_test.go | 73 ++-- 15 files changed, 475 insertions(+), 295 deletions(-) diff --git a/cli/agent_test.go b/cli/agent_test.go index f69426bd31..eb0140c446 100644 --- a/cli/agent_test.go +++ b/cli/agent_test.go @@ -31,11 +31,10 @@ func TestWorkspaceAgent(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db). - Seed(database.Workspace{ - OrganizationID: user.OrganizationID, - OwnerID: user.UserID, - }). + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OrganizationID: user.OrganizationID, + OwnerID: user.UserID, + }). WithAgent(). Do() logDir := t.TempDir() @@ -68,7 +67,7 @@ func TestWorkspaceAgent(t *testing.T) { AzureCertificates: certificates, }) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -105,7 +104,7 @@ func TestWorkspaceAgent(t *testing.T) { AWSCertificates: certificates, }) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -143,7 +142,7 @@ func TestWorkspaceAgent(t *testing.T) { }) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -195,13 +194,10 @@ func TestWorkspaceAgent(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db). - Seed(database.Workspace{ - OrganizationID: user.OrganizationID, - OwnerID: user.UserID, - }). - WithAgent(). - Do() + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OrganizationID: user.OrganizationID, + OwnerID: user.UserID, + }).WithAgent().Do() logDir := t.TempDir() inv, _ := clitest.New(t, diff --git a/cli/configssh_test.go b/cli/configssh_test.go index 704c74113e..d87d1fa702 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -77,13 +77,10 @@ func TestConfigSSH(t *testing.T) { }) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.Workspace(t, db). - Seed(database.Workspace{ - OrganizationID: owner.OrganizationID, - OwnerID: memberUser.ID, - }). - WithAgent(). - Do() + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OrganizationID: owner.OrganizationID, + OwnerID: memberUser.ID, + }).WithAgent().Do() _ = agenttest.New(t, client.URL, r.AgentToken) resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID) agentConn, err := client.DialWorkspaceAgent(context.Background(), resources[0].Agents[0].ID, nil) @@ -575,7 +572,7 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) if tt.hasAgent { - _ = dbfake.Workspace(t, db).Seed(database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -695,11 +692,10 @@ func TestConfigSSH_Hostnames(t *testing.T) { owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, - }).Do() - dbfake.WorkspaceBuild(t, db, r.Workspace).Resource(resources...).Do() + }).Resource(resources...).Do() sshConfigFile := sshConfigFileName(t) inv, root := clitest.New(t, "config-ssh", "--ssh-config-file", sshConfigFile) diff --git a/cli/gitssh_test.go b/cli/gitssh_test.go index 20cf1ecbec..83b873dec9 100644 --- a/cli/gitssh_test.go +++ b/cli/gitssh_test.go @@ -48,13 +48,11 @@ func prepareTestGitSSH(ctx context.Context, t *testing.T) (*agentsdk.Client, str require.NoError(t, err) // setup template - r := dbfake.Workspace(t, db). - Seed(database.Workspace{ - OrganizationID: user.OrganizationID, - OwnerID: user.UserID, - }). - WithAgent(). - Do() + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OrganizationID: user.OrganizationID, + OwnerID: user.UserID, + }).WithAgent().Do() + // start workspace agent agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(r.AgentToken) diff --git a/cli/list_test.go b/cli/list_test.go index 7836bde5d5..82d372bd35 100644 --- a/cli/list_test.go +++ b/cli/list_test.go @@ -25,10 +25,12 @@ func TestList(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + // setup template + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, }).WithAgent().Do() + inv, root := clitest.New(t, "ls") clitest.SetupConfig(t, member, root) pty := ptytest.New(t).Attach(inv) @@ -52,7 +54,7 @@ func TestList(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) owner := coderdtest.CreateFirstUser(t, client) member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) - dbfake.Workspace(t, db).Seed(database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: owner.OrganizationID, OwnerID: memberUser.ID, }).WithAgent().Do() diff --git a/cli/portforward_test.go b/cli/portforward_test.go index 80b1ef28c1..38971a0c89 100644 --- a/cli/portforward_test.go +++ b/cli/portforward_test.go @@ -305,10 +305,11 @@ func runAgent(t *testing.T, client *codersdk.Client, owner uuid.UUID, db databas require.NoError(t, err, "specified user does not exist") require.Greater(t, len(user.OrganizationIDs), 0, "user has no organizations") orgID := user.OrganizationIDs[0] - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: orgID, OwnerID: owner, }).WithAgent().Do() + _ = agenttest.New(t, client.URL, r.AgentToken, func(o *agent.Options) { o.SSHMaxTimeout = 60 * time.Second diff --git a/cli/schedule_test.go b/cli/schedule_test.go index 591ff2aa8d..9ed44de9e4 100644 --- a/cli/schedule_test.go +++ b/cli/schedule_test.go @@ -38,26 +38,27 @@ func setupTestSchedule(t *testing.T, sched *cron.Schedule) (ownerClient, memberC memberClient, memberUser := coderdtest.CreateAnotherUserMutators(t, ownerClient, owner.OrganizationID, nil, func(r *codersdk.CreateUserRequest) { r.Username = "testuser2" // ensure deterministic ordering }) - _ = dbfake.Workspace(t, db).Seed(database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ Name: "a-owner", OwnerID: owner.UserID, OrganizationID: owner.OrganizationID, AutostartSchedule: sql.NullString{String: sched.String(), Valid: true}, Ttl: sql.NullInt64{Int64: 8 * time.Hour.Nanoseconds(), Valid: true}, }).WithAgent().Do() - _ = dbfake.Workspace(t, db).Seed(database.Workspace{ + + _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ Name: "b-owner", OwnerID: owner.UserID, OrganizationID: owner.OrganizationID, AutostartSchedule: sql.NullString{String: sched.String(), Valid: true}, }).WithAgent().Do() - _ = dbfake.Workspace(t, db).Seed(database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ Name: "c-member", OwnerID: memberUser.ID, OrganizationID: owner.OrganizationID, Ttl: sql.NullInt64{Int64: 8 * time.Hour.Nanoseconds(), Valid: true}, }).WithAgent().Do() - _ = dbfake.Workspace(t, db).Seed(database.Workspace{ + _ = dbfake.WorkspaceBuild(t, db, database.Workspace{ Name: "d-member", OwnerID: memberUser.ID, OrganizationID: owner.OrganizationID, diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 7ee04b50cf..e60117ee6f 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -52,13 +52,10 @@ func setupWorkspaceForAgent(t *testing.T, mutations ...func([]*proto.Agent) []*p client.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)) first := coderdtest.CreateFirstUser(t, client) userClient, user := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - r := dbfake.Workspace(t, store). - Seed(database.Workspace{ - OrganizationID: first.OrganizationID, - OwnerID: user.ID, - }). - WithAgent(mutations...). - Do() + r := dbfake.WorkspaceBuild(t, store, database.Workspace{ + OrganizationID: first.OrganizationID, + OwnerID: user.ID, + }).WithAgent(mutations...).Do() return userClient, r.Workspace, r.AgentToken } @@ -130,7 +127,7 @@ func TestSSH(t *testing.T) { client.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)) first := coderdtest.CreateFirstUser(t, client) userClient, user := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - r := dbfake.Workspace(t, store).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.Workspace{ OrganizationID: first.OrganizationID, OwnerID: user.ID, }).WithAgent().Do() @@ -469,7 +466,7 @@ func TestSSH(t *testing.T) { client.SetLogger(slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug)) first := coderdtest.CreateFirstUser(t, client) userClient, user := coderdtest.CreateAnotherUser(t, client, first.OrganizationID) - r := dbfake.Workspace(t, store).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, store, database.Workspace{ OrganizationID: first.OrganizationID, OwnerID: user.ID, }).WithAgent().Do() diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index c5bb986c0c..f5ed26cfe9 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -753,6 +753,8 @@ func CreateWorkspaceBuild( transition database.WorkspaceTransition, mutators ...func(*codersdk.CreateWorkspaceBuildRequest), ) codersdk.WorkspaceBuild { + t.Helper() + req := codersdk.CreateWorkspaceBuildRequest{ Transition: codersdk.WorkspaceTransition(transition), } diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 25f0860b4f..4cac09d1dc 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -16,37 +16,71 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/provisionerdserver" + "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/telemetry" "github.com/coder/coder/v2/codersdk" sdkproto "github.com/coder/coder/v2/provisionersdk/proto" ) -type WorkspaceBuilder struct { - t testing.TB - db database.Store - seed database.Workspace - resources []*sdkproto.Resource - agentToken string -} +var ownerCtx = dbauthz.As(context.Background(), rbac.Subject{ + ID: "owner", + Roles: rbac.Roles(must(rbac.RoleNames{rbac.RoleOwner()}.Expand())), + Groups: []string{}, + Scope: rbac.ExpandableScope(rbac.ScopeAll), +}) type WorkspaceResponse struct { Workspace database.Workspace - Template database.Template Build database.WorkspaceBuild AgentToken string + TemplateVersionResponse } -func Workspace(t testing.TB, db database.Store) WorkspaceBuilder { - return WorkspaceBuilder{t: t, db: db} +// WorkspaceBuildBuilder generates workspace builds and associated +// resources. +type WorkspaceBuildBuilder struct { + t testing.TB + db database.Store + ps pubsub.Pubsub + ws database.Workspace + seed database.WorkspaceBuild + resources []*sdkproto.Resource + params []database.WorkspaceBuildParameter + agentToken string } -func (b WorkspaceBuilder) Seed(seed database.Workspace) WorkspaceBuilder { +// WorkspaceBuild generates a workspace build for the provided workspace. +// Pass a database.Workspace{} with a nil ID to also generate a new workspace. +// Omitting the template ID on a workspace will also generate a new template +// with a template version. +func WorkspaceBuild(t testing.TB, db database.Store, ws database.Workspace) WorkspaceBuildBuilder { + return WorkspaceBuildBuilder{t: t, db: db, ws: ws} +} + +func (b WorkspaceBuildBuilder) Pubsub(ps pubsub.Pubsub) WorkspaceBuildBuilder { + // nolint: revive // returns modified struct + b.ps = ps + return b +} + +func (b WorkspaceBuildBuilder) Seed(seed database.WorkspaceBuild) WorkspaceBuildBuilder { //nolint: revive // returns modified struct b.seed = seed return b } -func (b WorkspaceBuilder) WithAgent(mutations ...func([]*sdkproto.Agent) []*sdkproto.Agent) WorkspaceBuilder { +func (b WorkspaceBuildBuilder) Resource(resource ...*sdkproto.Resource) WorkspaceBuildBuilder { + //nolint: revive // returns modified struct + b.resources = append(b.resources, resource...) + return b +} + +func (b WorkspaceBuildBuilder) Params(params ...database.WorkspaceBuildParameter) WorkspaceBuildBuilder { + b.params = params + return b +} + +func (b WorkspaceBuildBuilder) WithAgent(mutations ...func([]*sdkproto.Agent) []*sdkproto.Agent) WorkspaceBuildBuilder { //nolint: revive // returns modified struct b.agentToken = uuid.NewString() agents := []*sdkproto.Agent{{ @@ -66,75 +100,57 @@ func (b WorkspaceBuilder) WithAgent(mutations ...func([]*sdkproto.Agent) []*sdkp return b } -func (b WorkspaceBuilder) Do() WorkspaceResponse { - var r WorkspaceResponse - // This intentionally fulfills the minimum requirements of the schema. - // Tests can provide a custom template ID if necessary. - if b.seed.TemplateID == uuid.Nil { - r.Template = dbgen.Template(b.t, b.db, database.Template{ - OrganizationID: b.seed.OrganizationID, - CreatedBy: b.seed.OwnerID, - }) - b.seed.TemplateID = r.Template.ID - b.seed.OwnerID = r.Template.CreatedBy - b.seed.OrganizationID = r.Template.OrganizationID - } - r.Workspace = dbgen.Workspace(b.t, b.db, b.seed) - if b.agentToken != "" { - r.AgentToken = b.agentToken - r.Build = WorkspaceBuild(b.t, b.db, r.Workspace). - Resource(b.resources...). - Do() - } - return r -} - -type WorkspaceBuildBuilder struct { - t testing.TB - db database.Store - ps pubsub.Pubsub - ws database.Workspace - seed database.WorkspaceBuild - resources []*sdkproto.Resource -} - -func WorkspaceBuild(t testing.TB, db database.Store, ws database.Workspace) WorkspaceBuildBuilder { - return WorkspaceBuildBuilder{t: t, db: db, ws: ws} -} - -func (b WorkspaceBuildBuilder) Pubsub(ps pubsub.Pubsub) WorkspaceBuildBuilder { - //nolint: revive // returns modified struct - b.ps = ps - return b -} - -func (b WorkspaceBuildBuilder) Seed(seed database.WorkspaceBuild) WorkspaceBuildBuilder { - //nolint: revive // returns modified struct - b.seed = seed - return b -} - -func (b WorkspaceBuildBuilder) Resource(resource ...*sdkproto.Resource) WorkspaceBuildBuilder { - //nolint: revive // returns modified struct - b.resources = append(b.resources, resource...) - return b -} - -func (b WorkspaceBuildBuilder) Do() database.WorkspaceBuild { +// Do generates all the resources associated with a workspace build. +// Template and TemplateVersion will be optionally populated if no +// TemplateID is set on the provided workspace. +// Workspace will be optionally populated if no ID is set on the provided +// workspace. +func (b WorkspaceBuildBuilder) Do() WorkspaceResponse { b.t.Helper() jobID := uuid.New() b.seed.ID = uuid.New() b.seed.JobID = jobID + + resp := WorkspaceResponse{ + AgentToken: b.agentToken, + } + if b.ws.TemplateID == uuid.Nil { + resp.TemplateVersionResponse = TemplateVersion(b.t, b.db). + Resources(b.resources...). + Pubsub(b.ps). + Seed(database.TemplateVersion{ + OrganizationID: b.ws.OrganizationID, + CreatedBy: b.ws.OwnerID, + }). + Do() + b.ws.TemplateID = resp.Template.ID + b.seed.TemplateVersionID = resp.TemplateVersion.ID + } + + // If no template version is set assume the active version. + if b.seed.TemplateVersionID == uuid.Nil { + template, err := b.db.GetTemplateByID(ownerCtx, b.ws.TemplateID) + require.NoError(b.t, err) + require.NotNil(b.t, template.ActiveVersionID, "active version ID unexpectedly nil") + b.seed.TemplateVersionID = template.ActiveVersionID + } + + // No ID on the workspace implies we should generate an entry. + if b.ws.ID == uuid.Nil { + // nolint: revive + b.ws = dbgen.Workspace(b.t, b.db, b.ws) + resp.Workspace = b.ws + } b.seed.WorkspaceID = b.ws.ID + b.seed.InitiatorID = takeFirst(b.seed.InitiatorID, b.ws.OwnerID) // Create a provisioner job for the build! payload, err := json.Marshal(provisionerdserver.WorkspaceProvisionJob{ WorkspaceBuildID: b.seed.ID, }) require.NoError(b.t, err) - //nolint:gocritic // This is only used by tests. - ctx := dbauthz.AsSystemRestricted(context.Background()) - job, err := b.db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ + + job, err := b.db.InsertProvisionerJob(ownerCtx, database.InsertProvisionerJobParams{ ID: jobID, CreatedAt: dbtime.Now(), UpdatedAt: dbtime.Now(), @@ -149,7 +165,8 @@ func (b WorkspaceBuildBuilder) Do() database.WorkspaceBuild { TraceMetadata: pqtype.NullRawMessage{}, }) require.NoError(b.t, err, "insert job") - err = b.db.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{ + + err = b.db.UpdateProvisionerJobWithCompleteByID(ownerCtx, database.UpdateProvisionerJobWithCompleteByIDParams{ ID: job.ID, UpdatedAt: dbtime.Now(), Error: sql.NullString{}, @@ -161,42 +178,20 @@ func (b WorkspaceBuildBuilder) Do() database.WorkspaceBuild { }) require.NoError(b.t, err, "complete job") - // This intentionally fulfills the minimum requirements of the schema. - // Tests can provide a custom version ID if necessary. - if b.seed.TemplateVersionID == uuid.Nil { - jobID := uuid.New() - templateVersion := dbgen.TemplateVersion(b.t, b.db, database.TemplateVersion{ - JobID: jobID, - OrganizationID: b.ws.OrganizationID, - CreatedBy: b.ws.OwnerID, - TemplateID: uuid.NullUUID{ - UUID: b.ws.TemplateID, - Valid: true, - }, - }) - payload, _ := json.Marshal(provisionerdserver.TemplateVersionImportJob{ - TemplateVersionID: templateVersion.ID, - }) - dbgen.ProvisionerJob(b.t, b.db, nil, database.ProvisionerJob{ - ID: jobID, - OrganizationID: b.ws.OrganizationID, - Input: payload, - Type: database.ProvisionerJobTypeTemplateVersionImport, - CompletedAt: sql.NullTime{ - Time: dbtime.Now(), - Valid: true, - }, - }) - ProvisionerJobResources(b.t, b.db, jobID, b.seed.Transition, b.resources...).Do() - b.seed.TemplateVersionID = templateVersion.ID - } - build := dbgen.WorkspaceBuild(b.t, b.db, b.seed) + resp.Build = dbgen.WorkspaceBuild(b.t, b.db, b.seed) ProvisionerJobResources(b.t, b.db, job.ID, b.seed.Transition, b.resources...).Do() + + for i := range b.params { + b.params[i].WorkspaceBuildID = resp.Build.ID + } + _ = dbgen.WorkspaceBuildParameters(b.t, b.db, b.params) + if b.ps != nil { - err = b.ps.Publish(codersdk.WorkspaceNotifyChannel(build.WorkspaceID), []byte{}) + err = b.ps.Publish(codersdk.WorkspaceNotifyChannel(resp.Build.WorkspaceID), []byte{}) require.NoError(b.t, err) } - return build + + return resp } type ProvisionerJobResourcesBuilder struct { @@ -229,7 +224,148 @@ func (b ProvisionerJobResourcesBuilder) Do() { } for _, resource := range b.resources { //nolint:gocritic // This is only used by tests. - err := provisionerdserver.InsertWorkspaceResource(dbauthz.AsSystemRestricted(context.Background()), b.db, b.jobID, transition, resource, &telemetry.Snapshot{}) + err := provisionerdserver.InsertWorkspaceResource(ownerCtx, b.db, b.jobID, transition, resource, &telemetry.Snapshot{}) require.NoError(b.t, err) } } + +type TemplateVersionResponse struct { + Template database.Template + TemplateVersion database.TemplateVersion +} + +type TemplateVersionBuilder struct { + t testing.TB + db database.Store + seed database.TemplateVersion + ps pubsub.Pubsub + resources []*sdkproto.Resource + params []database.TemplateVersionParameter + promote bool +} + +// TemplateVersion generates a template version and optionally a parent +// template if no template ID is set on the seed. +func TemplateVersion(t testing.TB, db database.Store) TemplateVersionBuilder { + return TemplateVersionBuilder{ + t: t, + db: db, + promote: true, + } +} + +func (t TemplateVersionBuilder) Seed(v database.TemplateVersion) TemplateVersionBuilder { + // nolint: revive // returns modified struct + t.seed = v + return t +} + +func (t TemplateVersionBuilder) Pubsub(ps pubsub.Pubsub) TemplateVersionBuilder { + // nolint: revive // returns modified struct + t.ps = ps + return t +} + +func (t TemplateVersionBuilder) Resources(rs ...*sdkproto.Resource) TemplateVersionBuilder { + // nolint: revive // returns modified struct + t.resources = rs + return t +} + +func (t TemplateVersionBuilder) Params(ps ...database.TemplateVersionParameter) TemplateVersionBuilder { + // nolint: revive // returns modified struct + t.params = ps + return t +} + +func (t TemplateVersionBuilder) Do() TemplateVersionResponse { + t.t.Helper() + + t.seed.OrganizationID = takeFirst(t.seed.OrganizationID, uuid.New()) + t.seed.ID = takeFirst(t.seed.ID, uuid.New()) + t.seed.CreatedBy = takeFirst(t.seed.CreatedBy, uuid.New()) + + var resp TemplateVersionResponse + if t.seed.TemplateID.UUID == uuid.Nil { + resp.Template = dbgen.Template(t.t, t.db, database.Template{ + ActiveVersionID: t.seed.ID, + OrganizationID: t.seed.OrganizationID, + CreatedBy: t.seed.CreatedBy, + }) + t.seed.TemplateID = uuid.NullUUID{ + Valid: true, + UUID: resp.Template.ID, + } + } + + version := dbgen.TemplateVersion(t.t, t.db, t.seed) + + // Always make this version the active version. We can easily + // add a conditional to the builder to opt out of this when + // necessary. + err := t.db.UpdateTemplateActiveVersionByID(ownerCtx, database.UpdateTemplateActiveVersionByIDParams{ + ID: t.seed.TemplateID.UUID, + ActiveVersionID: t.seed.ID, + UpdatedAt: dbtime.Now(), + }) + require.NoError(t.t, err) + + payload, err := json.Marshal(provisionerdserver.TemplateVersionImportJob{ + TemplateVersionID: t.seed.ID, + }) + require.NoError(t.t, err) + + job := dbgen.ProvisionerJob(t.t, t.db, t.ps, database.ProvisionerJob{ + ID: version.JobID, + OrganizationID: t.seed.OrganizationID, + InitiatorID: t.seed.CreatedBy, + Type: database.ProvisionerJobTypeTemplateVersionImport, + Input: payload, + CompletedAt: sql.NullTime{ + Time: dbtime.Now(), + Valid: true, + }, + }) + + t.seed.JobID = job.ID + + ProvisionerJobResources(t.t, t.db, job.ID, "", t.resources...).Do() + + for i, param := range t.params { + param.TemplateVersionID = version.ID + t.params[i] = dbgen.TemplateVersionParameter(t.t, t.db, param) + } + + resp.TemplateVersion = version + return resp +} + +func must[V any](v V, err error) V { + if err != nil { + panic(err) + } + return v +} + +// takeFirstF takes the first value that returns true +func takeFirstF[Value any](values []Value, take func(v Value) bool) Value { + for _, v := range values { + if take(v) { + return v + } + } + // If all empty, return the last element + if len(values) > 0 { + return values[len(values)-1] + } + var empty Value + return empty +} + +// takeFirst will take the first non-empty value. +func takeFirst[Value comparable](values ...Value) Value { + var empty Value + return takeFirstF(values, func(v Value) bool { + return v != empty + }) +} diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index dc630e048b..1848dc6615 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -167,6 +167,8 @@ func WorkspaceAgent(t testing.TB, db database.Store, orig database.WorkspaceAgen } func Workspace(t testing.TB, db database.Store, orig database.Workspace) database.Workspace { + t.Helper() + workspace, err := db.InsertWorkspace(genCtx, database.InsertWorkspaceParams{ ID: takeFirst(orig.ID, uuid.New()), OwnerID: takeFirst(orig.OwnerID, uuid.New()), @@ -197,6 +199,8 @@ func WorkspaceAgentLogSource(t testing.TB, db database.Store, orig database.Work } func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuild) database.WorkspaceBuild { + t.Helper() + buildID := takeFirst(orig.ID, uuid.New()) var build database.WorkspaceBuild err := db.InTx(func(db database.Store) error { @@ -229,6 +233,38 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil return build } +func WorkspaceBuildParameters(t testing.TB, db database.Store, orig []database.WorkspaceBuildParameter) []database.WorkspaceBuildParameter { + if len(orig) == 0 { + return nil + } + + var ( + names = make([]string, 0, len(orig)) + values = make([]string, 0, len(orig)) + params []database.WorkspaceBuildParameter + ) + for _, param := range orig { + names = append(names, param.Name) + values = append(values, param.Value) + } + err := db.InTx(func(tx database.Store) error { + id := takeFirst(orig[0].WorkspaceBuildID, uuid.New()) + err := tx.InsertWorkspaceBuildParameters(genCtx, database.InsertWorkspaceBuildParametersParams{ + WorkspaceBuildID: id, + Name: names, + Value: values, + }) + if err != nil { + return err + } + + params, err = tx.GetWorkspaceBuildParameters(genCtx, id) + return err + }, nil) + require.NoError(t, err) + return params +} + func User(t testing.TB, db database.Store, orig database.User) database.User { user, err := db.InsertUser(genCtx, database.InsertUserParams{ ID: takeFirst(orig.ID, uuid.New()), @@ -335,6 +371,8 @@ func GroupMember(t testing.TB, db database.Store, orig database.GroupMember) dat // ProvisionerJob is a bit more involved to get the values such as "completedAt", "startedAt", "cancelledAt" set. ps // can be set to nil if you are SURE that you don't require a provisionerdaemon to acquire the job in your test. func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig database.ProvisionerJob) database.ProvisionerJob { + t.Helper() + jobID := takeFirst(orig.ID, uuid.New()) // Always set some tags to prevent Acquire from grabbing jobs it should not. if !orig.StartedAt.Time.IsZero() { @@ -545,7 +583,7 @@ func TemplateVersion(t testing.TB, db database.Store, orig database.TemplateVers versionID := takeFirst(orig.ID, uuid.New()) err := db.InsertTemplateVersion(genCtx, database.InsertTemplateVersionParams{ ID: versionID, - TemplateID: orig.TemplateID, + TemplateID: takeFirst(orig.TemplateID, uuid.NullUUID{}), OrganizationID: takeFirst(orig.OrganizationID, uuid.New()), CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()), UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()), @@ -585,6 +623,32 @@ func TemplateVersionVariable(t testing.TB, db database.Store, orig database.Temp return version } +func TemplateVersionParameter(t testing.TB, db database.Store, orig database.TemplateVersionParameter) database.TemplateVersionParameter { + t.Helper() + + version, err := db.InsertTemplateVersionParameter(genCtx, database.InsertTemplateVersionParameterParams{ + TemplateVersionID: takeFirst(orig.TemplateVersionID, uuid.New()), + Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)), + Description: takeFirst(orig.Description, namesgenerator.GetRandomName(1)), + Type: takeFirst(orig.Type, "string"), + Mutable: takeFirst(orig.Mutable, false), + DefaultValue: takeFirst(orig.DefaultValue, namesgenerator.GetRandomName(1)), + Icon: takeFirst(orig.Icon, namesgenerator.GetRandomName(1)), + Options: takeFirstSlice(orig.Options, []byte("[]")), + ValidationRegex: takeFirst(orig.ValidationRegex, ""), + ValidationMin: takeFirst(orig.ValidationMin, sql.NullInt32{}), + ValidationMax: takeFirst(orig.ValidationMax, sql.NullInt32{}), + ValidationError: takeFirst(orig.ValidationError, ""), + ValidationMonotonic: takeFirst(orig.ValidationMonotonic, ""), + Required: takeFirst(orig.Required, false), + DisplayName: takeFirst(orig.DisplayName, namesgenerator.GetRandomName(1)), + DisplayOrder: takeFirst(orig.DisplayOrder, 0), + Ephemeral: takeFirst(orig.Ephemeral, false), + }) + require.NoError(t, err, "insert template version parameter") + return version +} + func WorkspaceAgentStat(t testing.TB, db database.Store, orig database.WorkspaceAgentStat) database.WorkspaceAgentStat { if orig.ConnectionsByProto == nil { orig.ConnectionsByProto = json.RawMessage([]byte("{}")) diff --git a/coderd/database/dbgen/dbgen_test.go b/coderd/database/dbgen/dbgen_test.go index 531c8bb25c..eaf5a0e764 100644 --- a/coderd/database/dbgen/dbgen_test.go +++ b/coderd/database/dbgen/dbgen_test.go @@ -173,6 +173,22 @@ func TestGenerator(t *testing.T) { exp := dbgen.GitSSHKey(t, db, database.GitSSHKey{}) require.Equal(t, exp, must(db.GetGitSSHKey(context.Background(), exp.UserID))) }) + + t.Run("WorkspaceBuildParameters", func(t *testing.T) { + t.Parallel() + db := dbmem.New() + exp := dbgen.WorkspaceBuildParameters(t, db, []database.WorkspaceBuildParameter{{}, {}, {}}) + require.Equal(t, exp, must(db.GetWorkspaceBuildParameters(context.Background(), exp[0].WorkspaceBuildID))) + }) + + t.Run("TemplateVersionParameter", func(t *testing.T) { + t.Parallel() + db := dbmem.New() + exp := dbgen.TemplateVersionParameter(t, db, database.TemplateVersionParameter{}) + actual := must(db.GetTemplateVersionParameters(context.Background(), exp.TemplateVersionID)) + require.Len(t, actual, 1) + require.Equal(t, exp, actual[0]) + }) } func must[T any](value T, err error) T { diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 24c6e94fb8..0b6d6316b0 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -47,7 +47,7 @@ func TestWorkspaceAgent(t *testing.T) { tmpDir := t.TempDir() anotherClient, anotherUser := coderdtest.CreateAnotherUser(t, client, user.OrganizationID) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: anotherUser.ID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -69,7 +69,7 @@ func TestWorkspaceAgent(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) tmpDir := t.TempDir() - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -97,7 +97,7 @@ func TestWorkspaceAgent(t *testing.T) { wantTroubleshootingURL := "https://example.com/troubleshoot" - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -138,7 +138,7 @@ func TestWorkspaceAgent(t *testing.T) { PortForwardingHelper: true, SshHelper: true, } - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -171,7 +171,7 @@ func TestWorkspaceAgent(t *testing.T) { apps.WebTerminal = false // Creating another workspace is easier - r = dbfake.Workspace(t, db).Seed(database.Workspace{ + r = dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -195,7 +195,7 @@ func TestWorkspaceAgentLogs(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -237,7 +237,7 @@ func TestWorkspaceAgentLogs(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -279,7 +279,7 @@ func TestWorkspaceAgentLogs(t *testing.T) { ctx := testutil.Context(t, testutil.WaitMedium) client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -322,7 +322,7 @@ func TestWorkspaceAgentListen(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -408,7 +408,7 @@ func TestWorkspaceAgentTailnet(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -454,7 +454,7 @@ func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) { DeploymentValues: dv, }) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -517,7 +517,7 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) { require.NoError(t, err) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -752,7 +752,7 @@ func TestWorkspaceAgentAppHealth(t *testing.T) { }, }, } - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -819,7 +819,7 @@ func TestWorkspaceAgentReportStats(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -886,7 +886,7 @@ func TestWorkspaceAgent_LifecycleState(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -951,7 +951,7 @@ func TestWorkspaceAgent_Metadata(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -1129,7 +1129,7 @@ func TestWorkspaceAgent_Metadata_CatchMemoryLeak(t *testing.T) { Logger: &logger, }) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent(func(agents []*proto.Agent) []*proto.Agent { @@ -1258,7 +1258,7 @@ func TestWorkspaceAgent_Startup(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -1304,7 +1304,7 @@ func TestWorkspaceAgent_Startup(t *testing.T) { client, db := coderdtest.NewWithDatabase(t, nil) user := coderdtest.CreateFirstUser(t, client) - r := dbfake.Workspace(t, db).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() @@ -1352,7 +1352,7 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) { api.DERPMapper.Store(&derpMapFn) // Start workspace a workspace agent. - r := dbfake.Workspace(t, api.Database).Seed(database.Workspace{ + r := dbfake.WorkspaceBuild(t, api.Database, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, }).WithAgent().Do() diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 002c8a7c00..71feaf5fc3 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -23,6 +23,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/database/dbtime" @@ -343,94 +344,71 @@ func TestWorkspace(t *testing.T) { func TestResolveAutostart(t *testing.T) { t.Parallel() - t.Run("OK", func(t *testing.T) { - t.Parallel() - ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) - owner := coderdtest.CreateFirstUser(t, ownerClient) - version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) - coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version1.ID) - template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, version1.ID) + ownerClient, db := coderdtest.NewWithDatabase(t, nil) + owner := coderdtest.CreateFirstUser(t, ownerClient) - params := &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionPlan: []*proto.Response{ - { - Type: &proto.Response_Plan{ - Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{ - { - Name: "param", - Description: "param", - Required: true, - Mutable: true, - }, - }, - }, - }, - }, - }, - ProvisionApply: echo.ApplyComplete, - } - version2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, params, func(ctvr *codersdk.CreateTemplateVersionRequest) { - ctvr.TemplateID = template.ID - }) - coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version2.ID) + param := database.TemplateVersionParameter{ + Name: "param", + DefaultValue: "", + Required: true, + } - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() - client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) { - cwr.AutomaticUpdates = codersdk.AutomaticUpdatesAlways - }) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + client, member := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + resp := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OwnerID: member.ID, + OrganizationID: owner.OrganizationID, + AutomaticUpdates: database.AutomaticUpdatesAlways, + }).Seed(database.WorkspaceBuild{ + InitiatorID: member.ID, + }).Do() - err := ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ - ID: version2.ID, - }) - require.NoError(t, err) + workspace := resp.Workspace + version1 := resp.TemplateVersion - // Autostart shouldn't be possible if parameters do not match. - resp, err := client.ResolveAutostart(ctx, workspace.ID.String()) - require.NoError(t, err) - require.True(t, resp.ParameterMismatch) + version2 := dbfake.TemplateVersion(t, db). + Seed(database.TemplateVersion{ + CreatedBy: owner.UserID, + OrganizationID: owner.OrganizationID, + TemplateID: version1.TemplateID, + }). + Params(param).Do() - update, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{ - TemplateVersionID: version2.ID, - Transition: codersdk.WorkspaceTransitionStart, - RichParameterValues: []codersdk.WorkspaceBuildParameter{ - { - Name: "param", - Value: "Hello", - }, - }, - }) - require.NoError(t, err) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, update.ID) + // Autostart shouldn't be possible if parameters do not match. + resolveResp, err := client.ResolveAutostart(ctx, workspace.ID.String()) + require.NoError(t, err) + require.True(t, resolveResp.ParameterMismatch) - // We should be able to autostart since parameters are updated. - resp, err = client.ResolveAutostart(ctx, workspace.ID.String()) - require.NoError(t, err) - require.False(t, resp.ParameterMismatch) + _ = dbfake.WorkspaceBuild(t, db, workspace). + Seed(database.WorkspaceBuild{ + BuildNumber: 2, + TemplateVersionID: version2.TemplateVersion.ID, + }). + Params(database.WorkspaceBuildParameter{ + Name: "param", + Value: "hello", + }).Do() - // Create one last version where the parameters are the same as the previous - // version. - version3 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, params, func(ctvr *codersdk.CreateTemplateVersionRequest) { - ctvr.TemplateID = template.ID - }) - coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version3.ID) + // We should be able to autostart since parameters are updated. + resolveResp, err = client.ResolveAutostart(ctx, workspace.ID.String()) + require.NoError(t, err) + require.False(t, resolveResp.ParameterMismatch) - err = ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ - ID: version3.ID, - }) - require.NoError(t, err) + // Create another version that has the same parameters as version2. + // We should be able to update without issue. + _ = dbfake.TemplateVersion(t, db).Seed(database.TemplateVersion{ + CreatedBy: owner.UserID, + OrganizationID: owner.OrganizationID, + TemplateID: version1.TemplateID, + }).Params(param).Do() - // Even though we're out of date we should still be able to autostart - // since parameters resolve. - resp, err = client.ResolveAutostart(ctx, workspace.ID.String()) - require.NoError(t, err) - require.False(t, resp.ParameterMismatch) - }) + // Even though we're out of date we should still be able to autostart + // since parameters resolve. + resolveResp, err = client.ResolveAutostart(ctx, workspace.ID.String()) + require.NoError(t, err) + require.False(t, resolveResp.ParameterMismatch) } func TestAdminViewAllWorkspaces(t *testing.T) { diff --git a/enterprise/coderd/coderdenttest/coderdenttest.go b/enterprise/coderd/coderdenttest/coderdenttest.go index 26e3bfaef2..3ec1f87a6d 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest.go +++ b/enterprise/coderd/coderdenttest/coderdenttest.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbmem" "github.com/coder/coder/v2/coderd/database/pubsub" @@ -67,6 +68,11 @@ func New(t *testing.T, options *Options) (*codersdk.Client, codersdk.CreateFirst return client, user } +func NewWithDatabase(t *testing.T, options *Options) (*codersdk.Client, database.Store, codersdk.CreateFirstUserResponse) { + client, _, api, user := NewWithAPI(t, options) + return client, api.Database, user +} + func NewWithAPI(t *testing.T, options *Options) ( *codersdk.Client, io.Closer, *coderd.API, codersdk.CreateFirstUserResponse, ) { diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 50f3db873a..f168e148a2 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -16,6 +16,7 @@ import ( "github.com/coder/coder/v2/coderd/autobuild" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/rbac" agplschedule "github.com/coder/coder/v2/coderd/schedule" @@ -28,7 +29,6 @@ import ( "github.com/coder/coder/v2/enterprise/coderd/license" "github.com/coder/coder/v2/enterprise/coderd/schedule" "github.com/coder/coder/v2/provisioner/echo" - "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/testutil" ) @@ -1169,10 +1169,9 @@ func TestWorkspaceLock(t *testing.T) { func TestResolveAutostart(t *testing.T) { t.Parallel() - ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{ Options: &coderdtest.Options{ - IncludeProvisionerDaemon: true, - TemplateScheduleStore: &schedule.EnterpriseTemplateScheduleStore{}, + TemplateScheduleStore: &schedule.EnterpriseTemplateScheduleStore{}, }, LicenseOptions: &coderdenttest.LicenseOptions{ Features: license.Features{ @@ -1181,52 +1180,40 @@ func TestResolveAutostart(t *testing.T) { }, }) - version1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil) - coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version1.ID) - template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, version1.ID, func(ctr *codersdk.CreateTemplateRequest) { - ctr.RequireActiveVersion = true - }) - - params := &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionPlan: []*proto.Response{ - { - Type: &proto.Response_Plan{ - Plan: &proto.PlanComplete{ - Parameters: []*proto.RichParameter{ - { - Name: "param", - Description: "param", - Required: true, - Mutable: true, - }, - }, - }, - }, - }, - }, - ProvisionApply: echo.ApplyComplete, - } - version2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, params, func(ctvr *codersdk.CreateTemplateVersionRequest) { - ctvr.TemplateID = template.ID - }) - coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, version2.ID) + version1 := dbfake.TemplateVersion(t, db). + Seed(database.TemplateVersion{ + CreatedBy: owner.UserID, + OrganizationID: owner.OrganizationID, + }).Do() ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) - workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, template.ID) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) - - //nolint:gocritic - err := ownerClient.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{ - ID: version2.ID, + _, err := ownerClient.UpdateTemplateMeta(ctx, version1.Template.ID, codersdk.UpdateTemplateMeta{ + RequireActiveVersion: true, }) require.NoError(t, err) - // Autostart shouldn't be possible since the template requires automatic - // updates. + client, member := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + + workspace := dbfake.WorkspaceBuild(t, db, database.Workspace{ + OwnerID: member.ID, + OrganizationID: owner.OrganizationID, + TemplateID: version1.Template.ID, + }).Seed(database.WorkspaceBuild{ + TemplateVersionID: version1.TemplateVersion.ID, + }).Do().Workspace + + _ = dbfake.TemplateVersion(t, db).Seed(database.TemplateVersion{ + CreatedBy: owner.UserID, + OrganizationID: owner.OrganizationID, + TemplateID: version1.TemplateVersion.TemplateID, + }).Params(database.TemplateVersionParameter{ + Name: "param", + Required: true, + }).Do() + + // Autostart shouldn't be possible if parameters do not match. resp, err := client.ResolveAutostart(ctx, workspace.ID.String()) require.NoError(t, err) require.True(t, resp.ParameterMismatch)