Ctrl K
ComunidadGuías…

cli/templateedit_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package cli_test

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"net/url"
	"strconv"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/coder/coder/v2/cli/clitest"
	"github.com/coder/coder/v2/coderd/coderdtest"
	"github.com/coder/coder/v2/coderd/httpapi"
	"github.com/coder/coder/v2/coderd/rbac"
	"github.com/coder/coder/v2/codersdk"
	"github.com/coder/coder/v2/testutil"
)

func TestTemplateEdit(t *testing.T) {
	t.Parallel()

	t.Run("FirstEmptyThenModified", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		owner := coderdtest.CreateFirstUser(t, client)
		templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
		version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
		template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)

		// Test the cli command.
		name := "new-template-name"
		displayName := "New Display Name 789"
		desc := "lorem ipsum dolor sit amet et cetera"
		icon := "/icon/new-icon.png"
		defaultTTL := 12 * time.Hour
		timeTilAutostopNotify := 5 * time.Minute
		allowUserCancelWorkspaceJobs := false

		cmdArgs := []string{
			"templates",
			"edit",
			template.Name,
			"--name", name,
			"--display-name", displayName,
			"--description", desc,
			"--icon", icon,
			"--default-ttl", defaultTTL.String(),
			"--autostop-reminder", timeTilAutostopNotify.String(),
			"--allow-user-cancel-workspace-jobs=" + strconv.FormatBool(allowUserCancelWorkspaceJobs),
		}
		inv, root := clitest.New(t, cmdArgs...)
		clitest.SetupConfig(t, templateAdmin, root)

		ctx := testutil.Context(t, testutil.WaitLong)
		err := inv.WithContext(ctx).Run()

		require.NoError(t, err)

		// Assert that the template metadata changed.
		updated, err := client.Template(context.Background(), template.ID)
		require.NoError(t, err)
		assert.Equal(t, name, updated.Name)
		assert.Equal(t, displayName, updated.DisplayName)
		assert.Equal(t, desc, updated.Description)
		assert.Equal(t, icon, updated.Icon)
		assert.Equal(t, defaultTTL.Milliseconds(), updated.DefaultTTLMillis)
		assert.Equal(t, timeTilAutostopNotify.Milliseconds(), updated.TimeTilAutostopNotifyMillis)
		assert.Equal(t, allowUserCancelWorkspaceJobs, updated.AllowUserCancelWorkspaceJobs)
	})
	t.Run("FirstEmptyThenNotModified", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		owner := coderdtest.CreateFirstUser(t, client)
		templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
		version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
		template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)

		// Test the cli command.
		cmdArgs := []string{
			"templates",
			"edit",
			template.Name,
			"--name", template.Name,
			"--description", template.Description,
			"--icon", template.Icon,
			"--default-ttl", (time.Duration(template.DefaultTTLMillis) * time.Millisecond).String(),
			"--activity-bump", (time.Duration(template.ActivityBumpMillis) * time.Millisecond).String(),
			"--allow-user-cancel-workspace-jobs=" + strconv.FormatBool(template.AllowUserCancelWorkspaceJobs),
		}
		inv, root := clitest.New(t, cmdArgs...)
		clitest.SetupConfig(t, templateAdmin, root)

		ctx := testutil.Context(t, testutil.WaitLong)
		err := inv.WithContext(ctx).Run()
		require.NoError(t, err)

		// Assert that the template metadata did not change.
		updated, err := client.Template(context.Background(), template.ID)
		require.NoError(t, err)
		assert.Equal(t, template.Name, updated.Name)
		assert.Equal(t, template.Description, updated.Description)
		assert.Equal(t, template.Icon, updated.Icon)
		assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
		assert.Equal(t, template.AllowUserCancelWorkspaceJobs, updated.AllowUserCancelWorkspaceJobs)
	})
	t.Run("AgentsAllowed", func(t *testing.T) {
		t.Parallel()

		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		owner := coderdtest.CreateFirstUser(t, client)
		templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())

		for _, tt := range []struct {
			name                 string
			initialAgentsAllowed bool
			flag                 string
			description          string
			wantAgentsAllowed    bool
		}{
			{
				name:                 "ExplicitTrue",
				initialAgentsAllowed: false,
				flag:                 "--agents-allowed=true",
				wantAgentsAllowed:    true,
			},
			{
				name:                 "ExplicitFalse",
				initialAgentsAllowed: true,
				flag:                 "--agents-allowed=false",
				wantAgentsAllowed:    false,
			},
			{
				name:                 "OmittedPreservesTrue",
				initialAgentsAllowed: true,
				description:          "updated description",
				wantAgentsAllowed:    true,
			},
			{
				name:                 "OmittedPreservesFalse",
				initialAgentsAllowed: false,
				description:          "updated description",
				wantAgentsAllowed:    false,
			},
		} {
			t.Run(tt.name, func(t *testing.T) {
				t.Parallel()

				version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
				_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
				template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(req *codersdk.CreateTemplateRequest) {
					req.AgentsAllowed = &tt.initialAgentsAllowed
				})

				cmdArgs := []string{"templates", "edit", template.Name}
				if tt.flag != "" {
					cmdArgs = append(cmdArgs, tt.flag)
				}
				if tt.description != "" {
					cmdArgs = append(cmdArgs, "--description", tt.description)
				}
				inv, root := clitest.New(t, cmdArgs...)
				clitest.SetupConfig(t, templateAdmin, root)

				require.NoError(t, inv.Run())

				updated, err := client.Template(t.Context(), template.ID)
				require.NoError(t, err)
				require.Equal(t, tt.wantAgentsAllowed, updated.AgentsAllowed)
				if tt.description != "" {
					require.Equal(t, tt.description, updated.Description)
				}
			})
		}
	})

	t.Run("InvalidDisplayName", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		owner := coderdtest.CreateFirstUser(t, client)
		templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
		version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
		template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)

		// Test the cli command.
		cmdArgs := []string{
			"templates",
			"edit",
			template.Name,
			"--name", template.Name,
			"--display-name", " a-b-c",
		}
		inv, root := clitest.New(t, cmdArgs...)
		clitest.SetupConfig(t, templateAdmin, root)

		ctx := testutil.Context(t, testutil.WaitLong)
		err := inv.WithContext(ctx).Run()

		require.Error(t, err, "client call must fail")
		_, isSdkError := codersdk.AsError(err)
		require.True(t, isSdkError, "sdk error is expected")

		// Assert that the template metadata did not change.
		updated, err := client.Template(context.Background(), template.ID)
		require.NoError(t, err)
		assert.Equal(t, template.Name, updated.Name)
		assert.Equal(t, "", template.DisplayName)
	})
	t.Run("WithPropertiesThenModified", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		owner := coderdtest.CreateFirstUser(t, client)
		templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
		version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)

		initialDisplayName := "This is a template"
		initialDescription := "This is description"
		initialIcon := "/img/icon.png"

		template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
			ctr.DisplayName = initialDisplayName
			ctr.Description = initialDescription
			ctr.Icon = initialIcon
		})

		// Test created template
		created, err := client.Template(context.Background(), template.ID)
		require.NoError(t, err)
		assert.Equal(t, initialDisplayName, created.DisplayName)
		assert.Equal(t, initialDescription, created.Description)
		assert.Equal(t, initialIcon, created.Icon)

		// Test the cli command.
		displayName := "New Display Name 789"
		description := "New Description ABC"
		icon := "/icon/new-icon.png"
		cmdArgs := []string{
			"templates",
			"edit",
			template.Name,
			"--description", description,
			"--display-name", displayName,
			"--icon", icon,
		}
		inv, root := clitest.New(t, cmdArgs...)
		clitest.SetupConfig(t, templateAdmin, root)

		ctx := testutil.Context(t, testutil.WaitLong)
		err = inv.WithContext(ctx).Run()

		require.NoError(t, err)

		// Assert that the template metadata changed.
		updated, err := client.Template(context.Background(), template.ID)
		require.NoError(t, err)
		assert.Equal(t, template.Name, updated.Name) // doesn't change
		assert.Equal(t, description, updated.Description)
		assert.Equal(t, displayName, updated.DisplayName)
		assert.Equal(t, icon, updated.Icon)
	})
	t.Run("WithPropertiesThenEmptyEdit", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		owner := coderdtest.CreateFirstUser(t, client)
		templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
		version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)

		initialDisplayName := "This is a template"
		initialDescription := "This is description"
		initialIcon := "/img/icon.png"

		template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
			ctr.DisplayName = initialDisplayName
			ctr.Description = initialDescription
			ctr.Icon = initialIcon
		})

		// Test created template
		created, err := client.Template(context.Background(), template.ID)
		require.NoError(t, err)
		assert.Equal(t, initialDisplayName, created.DisplayName)
		assert.Equal(t, initialDescription, created.Description)
		assert.Equal(t, initialIcon, created.Icon)

		// Test the cli command.
		cmdArgs := []string{
			"templates",
			"edit",
			template.Name,
			"--description", "",
			"--display-name", "",
			"--icon", "",
		}
		inv, root := clitest.New(t, cmdArgs...)
		clitest.SetupConfig(t, templateAdmin, root)

		ctx := testutil.Context(t, testutil.WaitLong)
		err = inv.WithContext(ctx).Run()

		require.NoError(t, err)

		// Assert that the template metadata changed.
		updated, err := client.Template(context.Background(), template.ID)
		require.NoError(t, err)
		// Properties don't change
		assert.Equal(t, template.Name, updated.Name)
		// These properties are removed, as the API considers it as "delete" request
		// See: https://github.com/coder/coder/issues/5066
		assert.Equal(t, "", updated.Description)
		assert.Equal(t, "", updated.Icon)
		assert.Equal(t, "", updated.DisplayName)
	})
	t.Run("Autostop/startRequirement", func(t *testing.T) {
		t.Parallel()
		t.Run("BlockedAGPL", func(t *testing.T) {
			t.Parallel()
			client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
			owner := coderdtest.CreateFirstUser(t, client)
			templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
			version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
			_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
			template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
				ctr.DefaultTTLMillis = nil
				ctr.AutostopRequirement = nil
			})

			cases := []struct {
				name  string
				flags []string
				ok    bool
			}{
				{
					name: "Weekdays",
					flags: []string{
						"--autostop-requirement-weekdays", "monday",
					},
				},
				{
					name: "WeekdaysNoneAllowed",
					flags: []string{
						"--autostop-requirement-weekdays", "none",
					},
					ok: true,
				},
				{
					name: "Weeks",
					flags: []string{
						"--autostop-requirement-weeks", "1",
					},
				},
				{
					name: "AutostartDays",
					flags: []string{
						"--autostart-requirement-weekdays", "monday",
					},
				},
			}

			for _, c := range cases {
				t.Run(c.name, func(t *testing.T) {
					t.Parallel()

					cmdArgs := []string{
						"templates",
						"edit",
						template.Name,
					}
					cmdArgs = append(cmdArgs, c.flags...)
					inv, root := clitest.New(t, cmdArgs...)
					clitest.SetupConfig(t, templateAdmin, root)

					ctx := testutil.Context(t, testutil.WaitLong)
					err := inv.WithContext(ctx).Run()
					if c.ok {
						require.NoError(t, err)
					} else {
						require.Error(t, err)
						require.ErrorContains(t, err, "appears to be an AGPL deployment")
					}

					// Assert that the template metadata did not change.
					updated, err := client.Template(context.Background(), template.ID)
					require.NoError(t, err)
					assert.Equal(t, template.Name, updated.Name)
					assert.Equal(t, template.Description, updated.Description)
					assert.Equal(t, template.Icon, updated.Icon)
					assert.Equal(t, template.DisplayName, updated.DisplayName)
					assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
					assert.Equal(t, template.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
					assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
					assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
					assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
				})
			}
		})

		t.Run("BlockedNotEntitled", func(t *testing.T) {
			t.Parallel()
			client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
			owner := coderdtest.CreateFirstUser(t, client)
			templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
			version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
			_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
			template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
				ctr.DefaultTTLMillis = nil
				ctr.AutostopRequirement = nil
			})

			// Make a proxy server that will return a valid entitlements
			// response, but without advanced scheduling entitlement.
			proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.URL.Path == "/api/v2/entitlements" {
					res := codersdk.Entitlements{
						Features:         map[codersdk.FeatureName]codersdk.Feature{},
						Warnings:         []string{},
						Errors:           []string{},
						HasLicense:       true,
						Trial:            true,
						RequireTelemetry: false,
					}
					for _, feature := range codersdk.FeatureNames {
						res.Features[feature] = codersdk.Feature{
							Entitlement: codersdk.EntitlementNotEntitled,
							Enabled:     false,
							Limit:       nil,
							Actual:      nil,
						}
					}
					httpapi.Write(r.Context(), w, http.StatusOK, res)
					return
				}

				// Otherwise, proxy the request to the real API server.
				rp := httputil.NewSingleHostReverseProxy(client.URL)
				tp := &http.Transport{}
				defer tp.CloseIdleConnections()
				rp.Transport = tp
				rp.ServeHTTP(w, r)
			}))
			t.Cleanup(proxy.Close)

			// Create a new client that uses the proxy server.
			proxyURL, err := url.Parse(proxy.URL)
			require.NoError(t, err)
			proxyClient := codersdk.New(proxyURL, codersdk.WithHTTPClient(coderdtest.NewIsolatedHTTPClient(proxyURL)))
			proxyClient.SetSessionToken(templateAdmin.SessionToken())
			t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)

			cases := []struct {
				name  string
				flags []string
				ok    bool
			}{
				{
					name: "Weekdays",
					flags: []string{
						"--autostop-requirement-weekdays", "monday",
					},
				},
				{
					name: "WeekdaysNoneAllowed",
					flags: []string{
						"--autostop-requirement-weekdays", "none",
					},
					ok: true,
				},
				{
					name: "Weeks",
					flags: []string{
						"--autostop-requirement-weeks", "1",
					},
				},
			}

			for _, c := range cases {
				t.Run(c.name, func(t *testing.T) {
					t.Parallel()

					cmdArgs := []string{
						"templates",
						"edit",
						template.Name,
					}
					cmdArgs = append(cmdArgs, c.flags...)
					inv, root := clitest.New(t, cmdArgs...)
					clitest.SetupConfig(t, proxyClient, root)

					ctx := testutil.Context(t, testutil.WaitLong)
					err := inv.WithContext(ctx).Run()
					if c.ok {
						require.NoError(t, err)
					} else {
						require.Error(t, err)
						require.ErrorContains(t, err, "license is not entitled")
					}

					// Assert that the template metadata did not change.
					updated, err := client.Template(context.Background(), template.ID)
					require.NoError(t, err)
					assert.Equal(t, template.Name, updated.Name)
					assert.Equal(t, template.Description, updated.Description)
					assert.Equal(t, template.Icon, updated.Icon)
					assert.Equal(t, template.DisplayName, updated.DisplayName)
					assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
					assert.Equal(t, template.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
					assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
					assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
				})
			}
		})
		t.Run("Entitled", func(t *testing.T) {
			t.Parallel()
			client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
			owner := coderdtest.CreateFirstUser(t, client)
			templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
			version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
			_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
			template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
				ctr.DefaultTTLMillis = nil
				ctr.AutostopRequirement = nil
			})

			// Make a proxy server that will return a valid entitlements
			// response, including a valid advanced scheduling entitlement.
			var updateTemplateCalled atomic.Int64
			proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.URL.Path == "/api/v2/entitlements" {
					res := codersdk.Entitlements{
						Features:         map[codersdk.FeatureName]codersdk.Feature{},
						Warnings:         []string{},
						Errors:           []string{},
						HasLicense:       true,
						Trial:            true,
						RequireTelemetry: false,
					}
					for _, feature := range codersdk.FeatureNames {
						var one int64 = 1
						res.Features[feature] = codersdk.Feature{
							Entitlement: codersdk.EntitlementNotEntitled,
							Enabled:     true,
							Limit:       &one,
							Actual:      &one,
						}
					}
					httpapi.Write(r.Context(), w, http.StatusOK, res)
					return
				}
				if strings.HasPrefix(r.URL.Path, "/api/v2/templates/") {
					body, err := io.ReadAll(r.Body)
					require.NoError(t, err)
					_ = r.Body.Close()

					var req codersdk.UpdateTemplateMeta
					err = json.Unmarshal(body, &req)
					require.NoError(t, err)
					assert.Equal(t, req.AutostopRequirement.DaysOfWeek, []string{"monday", "tuesday"})
					assert.EqualValues(t, req.AutostopRequirement.Weeks, 3)

					r.Body = io.NopCloser(bytes.NewReader(body))
					updateTemplateCalled.Add(1)
					// We still want to call the real route.
				}

				// Otherwise, proxy the request to the real API server.
				rp := httputil.NewSingleHostReverseProxy(client.URL)
				tp := &http.Transport{}
				defer tp.CloseIdleConnections()
				rp.Transport = tp
				rp.ServeHTTP(w, r)
			}))
			defer proxy.Close()

			// Create a new client that uses the proxy server.
			proxyURL, err := url.Parse(proxy.URL)
			require.NoError(t, err)
			proxyClient := codersdk.New(proxyURL, codersdk.WithHTTPClient(coderdtest.NewIsolatedHTTPClient(proxyURL)))
			proxyClient.SetSessionToken(templateAdmin.SessionToken())
			t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)

			// Test the cli command.
			cmdArgs := []string{
				"templates",
				"edit",
				template.Name,
				"--autostop-requirement-weekdays", "monday,tuesday",
				"--autostop-requirement-weeks", "3",
			}
			inv, root := clitest.New(t, cmdArgs...)
			clitest.SetupConfig(t, proxyClient, root)

			ctx := testutil.Context(t, testutil.WaitLong)
			err = inv.WithContext(ctx).Run()
			require.NoError(t, err)

			require.EqualValues(t, 1, updateTemplateCalled.Load())

			// Assert that the template metadata did not change. We verify the
			// correct request gets sent to the server already.
			updated, err := client.Template(context.Background(), template.ID)
			require.NoError(t, err)
			assert.Equal(t, template.Name, updated.Name)
			assert.Equal(t, template.Description, updated.Description)
			assert.Equal(t, template.Icon, updated.Icon)
			assert.Equal(t, template.DisplayName, updated.DisplayName)
			assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
			assert.Equal(t, template.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
			assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
			assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
		})
	})

	t.Run("AllowUserScheduling", func(t *testing.T) {
		t.Parallel()
		t.Run("BlockedAGPL", func(t *testing.T) {
			t.Parallel()
			client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
			owner := coderdtest.CreateFirstUser(t, client)
			templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
			version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
			_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
			template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
				ctr.DefaultTTLMillis = nil
				ctr.AutostopRequirement = nil
				ctr.FailureTTLMillis = nil
				ctr.TimeTilDormantMillis = nil
			})

			// Test the cli command with --allow-user-autostart.
			cmdArgs := []string{
				"templates",
				"edit",
				template.Name,
				"--allow-user-autostart=false",
			}
			inv, root := clitest.New(t, cmdArgs...)
			clitest.SetupConfig(t, templateAdmin, root)

			ctx := testutil.Context(t, testutil.WaitLong)
			err := inv.WithContext(ctx).Run()
			require.Error(t, err)
			require.ErrorContains(t, err, "appears to be an AGPL deployment")

			// Test the cli command with --allow-user-autostop.
			cmdArgs = []string{
				"templates",
				"edit",
				template.Name,
				"--allow-user-autostop=false",
			}
			inv, root = clitest.New(t, cmdArgs...)
			clitest.SetupConfig(t, templateAdmin, root)

			ctx = testutil.Context(t, testutil.WaitLong)
			err = inv.WithContext(ctx).Run()
			require.Error(t, err)
			require.ErrorContains(t, err, "appears to be an AGPL deployment")

			// Assert that the template metadata did not change.
			updated, err := client.Template(context.Background(), template.ID)
			require.NoError(t, err)
			assert.Equal(t, template.Name, updated.Name)
			assert.Equal(t, template.Description, updated.Description)
			assert.Equal(t, template.Icon, updated.Icon)
			assert.Equal(t, template.DisplayName, updated.DisplayName)
			assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
			assert.Equal(t, template.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
			assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
			assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
			assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
			assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
			assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
			assert.Equal(t, template.TimeTilDormantMillis, updated.TimeTilDormantMillis)
		})

		t.Run("BlockedNotEntitled", func(t *testing.T) {
			t.Parallel()
			client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
			owner := coderdtest.CreateFirstUser(t, client)
			templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
			version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
			_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
			template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)

			// Make a proxy server that will return a valid entitlements
			// response, but without advanced scheduling entitlement.
			proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.URL.Path == "/api/v2/entitlements" {
					res := codersdk.Entitlements{
						Features:         map[codersdk.FeatureName]codersdk.Feature{},
						Warnings:         []string{},
						Errors:           []string{},
						HasLicense:       true,
						Trial:            true,
						RequireTelemetry: false,
					}
					for _, feature := range codersdk.FeatureNames {
						res.Features[feature] = codersdk.Feature{
							Entitlement: codersdk.EntitlementNotEntitled,
							Enabled:     false,
							Limit:       nil,
							Actual:      nil,
						}
					}
					httpapi.Write(r.Context(), w, http.StatusOK, res)
					return
				}

				// Otherwise, proxy the request to the real API server.
				rp := httputil.NewSingleHostReverseProxy(client.URL)
				tp := &http.Transport{}
				defer tp.CloseIdleConnections()
				rp.Transport = tp
				rp.ServeHTTP(w, r)
			}))
			defer proxy.Close()

			// Create a new client that uses the proxy server.
			proxyURL, err := url.Parse(proxy.URL)
			require.NoError(t, err)
			proxyClient := codersdk.New(proxyURL, codersdk.WithHTTPClient(coderdtest.NewIsolatedHTTPClient(proxyURL)))
			proxyClient.SetSessionToken(templateAdmin.SessionToken())
			t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)

			// Test the cli command with --allow-user-autostart.
			cmdArgs := []string{
				"templates",
				"edit",
				template.Name,
				"--allow-user-autostart=false",
			}
			inv, root := clitest.New(t, cmdArgs...)
			clitest.SetupConfig(t, proxyClient, root)

			ctx := testutil.Context(t, testutil.WaitLong)
			err = inv.WithContext(ctx).Run()
			require.Error(t, err)
			require.ErrorContains(t, err, "license is not entitled")

			// Test the cli command with --allow-user-autostop.
			cmdArgs = []string{
				"templates",
				"edit",
				template.Name,
				"--allow-user-autostop=false",
			}
			inv, root = clitest.New(t, cmdArgs...)
			clitest.SetupConfig(t, proxyClient, root)

			ctx = testutil.Context(t, testutil.WaitLong)
			err = inv.WithContext(ctx).Run()
			require.Error(t, err)
			require.ErrorContains(t, err, "license is not entitled")

			// Assert that the template metadata did not change.
			updated, err := client.Template(context.Background(), template.ID)
			require.NoError(t, err)
			assert.Equal(t, template.Name, updated.Name)
			assert.Equal(t, template.Description, updated.Description)
			assert.Equal(t, template.Icon, updated.Icon)
			assert.Equal(t, template.DisplayName, updated.DisplayName)
			assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
			assert.Equal(t, template.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
			assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
			assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
			assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
			assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
			assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
			assert.Equal(t, template.TimeTilDormantMillis, updated.TimeTilDormantMillis)
		})
		t.Run("Entitled", func(t *testing.T) {
			t.Parallel()
			client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
			owner := coderdtest.CreateFirstUser(t, client)
			templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
			version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
			_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
			template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)

			// Make a proxy server that will return a valid entitlements
			// response, including a valid advanced scheduling entitlement.
			var updateTemplateCalled atomic.Int64
			proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.URL.Path == "/api/v2/entitlements" {
					res := codersdk.Entitlements{
						Features:         map[codersdk.FeatureName]codersdk.Feature{},
						Warnings:         []string{},
						Errors:           []string{},
						HasLicense:       true,
						Trial:            true,
						RequireTelemetry: false,
					}
					for _, feature := range codersdk.FeatureNames {
						var one int64 = 1
						res.Features[feature] = codersdk.Feature{
							Entitlement: codersdk.EntitlementNotEntitled,
							Enabled:     true,
							Limit:       &one,
							Actual:      &one,
						}
					}
					httpapi.Write(r.Context(), w, http.StatusOK, res)
					return
				}
				if strings.HasPrefix(r.URL.Path, "/api/v2/templates/") {
					body, err := io.ReadAll(r.Body)
					require.NoError(t, err)
					_ = r.Body.Close()

					var req codersdk.UpdateTemplateMeta
					err = json.Unmarshal(body, &req)
					require.NoError(t, err)
					require.NotNil(t, req.AllowUserAutostart)
					assert.False(t, *req.AllowUserAutostart)
					require.NotNil(t, req.AllowUserAutostop)
					assert.False(t, *req.AllowUserAutostop)

					r.Body = io.NopCloser(bytes.NewReader(body))
					updateTemplateCalled.Add(1)
					// We still want to call the real route.
				}

				// Otherwise, proxy the request to the real API server.
				rp := httputil.NewSingleHostReverseProxy(client.URL)
				tp := &http.Transport{}
				defer tp.CloseIdleConnections()
				rp.Transport = tp
				rp.ServeHTTP(w, r)
			}))
			defer proxy.Close()

			// Create a new client that uses the proxy server.
			proxyURL, err := url.Parse(proxy.URL)
			require.NoError(t, err)
			proxyClient := codersdk.New(proxyURL, codersdk.WithHTTPClient(coderdtest.NewIsolatedHTTPClient(proxyURL)))
			proxyClient.SetSessionToken(templateAdmin.SessionToken())
			t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)

			// Test the cli command.
			cmdArgs := []string{
				"templates",
				"edit",
				template.Name,
				"--allow-user-autostart=false",
				"--allow-user-autostop=false",
			}
			inv, root := clitest.New(t, cmdArgs...)
			clitest.SetupConfig(t, proxyClient, root)

			ctx := testutil.Context(t, testutil.WaitLong)
			err = inv.WithContext(ctx).Run()
			require.NoError(t, err)

			require.EqualValues(t, 1, updateTemplateCalled.Load())

			// Assert that the template metadata did not change. We verify the
			// correct request gets sent to the server already.
			updated, err := client.Template(context.Background(), template.ID)
			require.NoError(t, err)
			assert.Equal(t, template.Name, updated.Name)
			assert.Equal(t, template.Description, updated.Description)
			assert.Equal(t, template.Icon, updated.Icon)
			assert.Equal(t, template.DisplayName, updated.DisplayName)
			assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
			assert.Equal(t, template.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
			assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
			assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
			assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
			assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
			assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
			assert.Equal(t, template.TimeTilDormantMillis, updated.TimeTilDormantMillis)
		})
	})

	t.Run("RequireActiveVersion", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		owner := coderdtest.CreateFirstUser(t, client)

		version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
		template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {})

		// Test the cli command with --allow-user-autostart.
		cmdArgs := []string{
			"templates",
			"edit",
			template.Name,
			"--require-active-version",
		}
		inv, root := clitest.New(t, cmdArgs...)
		//nolint:gocritic // Using owner client is required for template editing.
		clitest.SetupConfig(t, client, root)

		ctx := testutil.Context(t, testutil.WaitLong)
		err := inv.WithContext(ctx).Run()
		require.Error(t, err)
		require.ErrorContains(t, err, "appears to be an AGPL deployment")
	})
	t.Run("DefaultValues", func(t *testing.T) {
		t.Parallel()
		client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
		owner := coderdtest.CreateFirstUser(t, client)

		version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
		_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
		template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
			ctr.Name = "random"
			ctr.Icon = "/icon/foobar.png"
			ctr.DisplayName = "Foobar"
			ctr.Description = "Some description"
		})

		// We need to change some field to get a db write.
		cmdArgs := []string{
			"templates",
			"edit",
			template.Name,
			"--name", "something-new",
		}
		inv, root := clitest.New(t, cmdArgs...)
		//nolint:gocritic // Using owner client is required for template editing.
		clitest.SetupConfig(t, client, root)

		ctx := testutil.Context(t, testutil.WaitLong)
		err := inv.WithContext(ctx).Run()
		require.NoError(t, err)

		updated, err := client.Template(context.Background(), template.ID)
		require.NoError(t, err)
		assert.Equal(t, "something-new", updated.Name)
		assert.Equal(t, template.Icon, updated.Icon)
		assert.Equal(t, template.DisplayName, updated.DisplayName)
		assert.Equal(t, template.Description, updated.Description)
		assert.Equal(t, template.DeprecationMessage, updated.DeprecationMessage)
	})
}