mirror of https://github.com/coder/coder.git
392 lines
9.6 KiB
Go
392 lines
9.6 KiB
Go
package clibase_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/cli/clibase"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
func TestOptionSet_ParseFlags(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("SimpleString", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var workspaceName clibase.String
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "Workspace Name",
|
|
Value: &workspaceName,
|
|
Flag: "workspace-name",
|
|
FlagShorthand: "n",
|
|
},
|
|
}
|
|
|
|
var err error
|
|
err = os.FlagSet().Parse([]string{"--workspace-name", "foo"})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, "foo", workspaceName)
|
|
|
|
err = os.FlagSet().Parse([]string{"-n", "f"})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, "f", workspaceName)
|
|
})
|
|
|
|
t.Run("StringArray", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var names clibase.StringArray
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "name",
|
|
Value: &names,
|
|
Flag: "name",
|
|
FlagShorthand: "n",
|
|
},
|
|
}
|
|
|
|
err := os.SetDefaults()
|
|
require.NoError(t, err)
|
|
|
|
err = os.FlagSet().Parse([]string{"--name", "foo", "--name", "bar"})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, []string{"foo", "bar"}, names)
|
|
})
|
|
|
|
t.Run("ExtraFlags", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var workspaceName clibase.String
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "Workspace Name",
|
|
Value: &workspaceName,
|
|
},
|
|
}
|
|
|
|
err := os.FlagSet().Parse([]string{"--some-unknown", "foo"})
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("RegexValid", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var regexpString clibase.Regexp
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "RegexpString",
|
|
Value: ®expString,
|
|
Flag: "regexp-string",
|
|
},
|
|
}
|
|
|
|
err := os.FlagSet().Parse([]string{"--regexp-string", "$test^"})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("RegexInvalid", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var regexpString clibase.Regexp
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "RegexpString",
|
|
Value: ®expString,
|
|
Flag: "regexp-string",
|
|
},
|
|
}
|
|
|
|
err := os.FlagSet().Parse([]string{"--regexp-string", "(("})
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestOptionSet_ParseEnv(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("SimpleString", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var workspaceName clibase.String
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "Workspace Name",
|
|
Value: &workspaceName,
|
|
Env: "WORKSPACE_NAME",
|
|
},
|
|
}
|
|
|
|
err := os.ParseEnv([]clibase.EnvVar{
|
|
{Name: "WORKSPACE_NAME", Value: "foo"},
|
|
})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, "foo", workspaceName)
|
|
})
|
|
|
|
t.Run("EmptyValue", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var workspaceName clibase.String
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "Workspace Name",
|
|
Value: &workspaceName,
|
|
Default: "defname",
|
|
Env: "WORKSPACE_NAME",
|
|
},
|
|
}
|
|
|
|
err := os.SetDefaults()
|
|
require.NoError(t, err)
|
|
|
|
err = os.ParseEnv(clibase.ParseEnviron([]string{"CODER_WORKSPACE_NAME="}, "CODER_"))
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, "defname", workspaceName)
|
|
})
|
|
|
|
t.Run("StringSlice", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var actual clibase.StringArray
|
|
expected := []string{"foo", "bar", "baz"}
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "name",
|
|
Value: &actual,
|
|
Env: "NAMES",
|
|
},
|
|
}
|
|
|
|
err := os.SetDefaults()
|
|
require.NoError(t, err)
|
|
|
|
err = os.ParseEnv([]clibase.EnvVar{
|
|
{Name: "NAMES", Value: "foo,bar,baz"},
|
|
})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, expected, actual)
|
|
})
|
|
|
|
t.Run("StructMapStringString", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var actual clibase.Struct[map[string]string]
|
|
expected := map[string]string{"foo": "bar", "baz": "zap"}
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "labels",
|
|
Value: &actual,
|
|
Env: "LABELS",
|
|
},
|
|
}
|
|
|
|
err := os.SetDefaults()
|
|
require.NoError(t, err)
|
|
|
|
err = os.ParseEnv([]clibase.EnvVar{
|
|
{Name: "LABELS", Value: `{"foo":"bar","baz":"zap"}`},
|
|
})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, expected, actual.Value)
|
|
})
|
|
|
|
t.Run("Homebrew", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var agentToken clibase.String
|
|
|
|
os := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "Agent Token",
|
|
Value: &agentToken,
|
|
Env: "AGENT_TOKEN",
|
|
},
|
|
}
|
|
|
|
err := os.ParseEnv([]clibase.EnvVar{
|
|
{Name: "HOMEBREW_AGENT_TOKEN", Value: "foo"},
|
|
})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, "foo", agentToken)
|
|
})
|
|
}
|
|
|
|
func TestOptionSet_JsonMarshal(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// This unit test ensures if the source optionset is missing the option
|
|
// and cannot determine the type, it will not panic. The unmarshal will
|
|
// succeed with a best effort.
|
|
t.Run("MissingSrcOption", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var str clibase.String = "something"
|
|
var arr clibase.StringArray = []string{"foo", "bar"}
|
|
opts := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "StringOpt",
|
|
Value: &str,
|
|
},
|
|
clibase.Option{
|
|
Name: "ArrayOpt",
|
|
Value: &arr,
|
|
},
|
|
}
|
|
data, err := json.Marshal(opts)
|
|
require.NoError(t, err, "marshal option set")
|
|
|
|
tgt := clibase.OptionSet{}
|
|
err = json.Unmarshal(data, &tgt)
|
|
require.NoError(t, err, "unmarshal option set")
|
|
for i := range opts {
|
|
compareOptionsExceptValues(t, opts[i], tgt[i])
|
|
require.Empty(t, tgt[i].Value.String(), "unknown value types are empty")
|
|
}
|
|
})
|
|
|
|
t.Run("RegexCase", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
val := clibase.Regexp(*regexp.MustCompile(".*"))
|
|
opts := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "Regex",
|
|
Value: &val,
|
|
Default: ".*",
|
|
},
|
|
}
|
|
data, err := json.Marshal(opts)
|
|
require.NoError(t, err, "marshal option set")
|
|
|
|
var foundVal clibase.Regexp
|
|
newOpts := clibase.OptionSet{
|
|
clibase.Option{
|
|
Name: "Regex",
|
|
Value: &foundVal,
|
|
},
|
|
}
|
|
err = json.Unmarshal(data, &newOpts)
|
|
require.NoError(t, err, "unmarshal option set")
|
|
|
|
require.EqualValues(t, opts[0].Value.String(), newOpts[0].Value.String())
|
|
})
|
|
|
|
t.Run("AllValues", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
vals := coderdtest.DeploymentValues(t)
|
|
opts := vals.Options()
|
|
sources := []clibase.ValueSource{
|
|
clibase.ValueSourceNone,
|
|
clibase.ValueSourceFlag,
|
|
clibase.ValueSourceEnv,
|
|
clibase.ValueSourceYAML,
|
|
clibase.ValueSourceDefault,
|
|
}
|
|
for i := range opts {
|
|
opts[i].ValueSource = sources[i%len(sources)]
|
|
}
|
|
|
|
data, err := json.Marshal(opts)
|
|
require.NoError(t, err, "marshal option set")
|
|
|
|
newOpts := (&codersdk.DeploymentValues{}).Options()
|
|
err = json.Unmarshal(data, &newOpts)
|
|
require.NoError(t, err, "unmarshal option set")
|
|
|
|
for i := range opts {
|
|
exp := opts[i]
|
|
found := newOpts[i]
|
|
|
|
compareOptionsExceptValues(t, exp, found)
|
|
compareValues(t, exp, found)
|
|
}
|
|
|
|
thirdOpts := (&codersdk.DeploymentValues{}).Options()
|
|
data, err = json.Marshal(newOpts)
|
|
require.NoError(t, err, "marshal option set")
|
|
|
|
err = json.Unmarshal(data, &thirdOpts)
|
|
require.NoError(t, err, "unmarshal option set")
|
|
// Compare to the original opts again
|
|
for i := range opts {
|
|
exp := opts[i]
|
|
found := thirdOpts[i]
|
|
|
|
compareOptionsExceptValues(t, exp, found)
|
|
compareValues(t, exp, found)
|
|
}
|
|
})
|
|
}
|
|
|
|
func compareOptionsExceptValues(t *testing.T, exp, found clibase.Option) {
|
|
t.Helper()
|
|
|
|
require.Equalf(t, exp.Name, found.Name, "option name %q", exp.Name)
|
|
require.Equalf(t, exp.Description, found.Description, "option description %q", exp.Name)
|
|
require.Equalf(t, exp.Required, found.Required, "option required %q", exp.Name)
|
|
require.Equalf(t, exp.Flag, found.Flag, "option flag %q", exp.Name)
|
|
require.Equalf(t, exp.FlagShorthand, found.FlagShorthand, "option flag shorthand %q", exp.Name)
|
|
require.Equalf(t, exp.Env, found.Env, "option env %q", exp.Name)
|
|
require.Equalf(t, exp.YAML, found.YAML, "option yaml %q", exp.Name)
|
|
require.Equalf(t, exp.Default, found.Default, "option default %q", exp.Name)
|
|
require.Equalf(t, exp.ValueSource, found.ValueSource, "option value source %q", exp.Name)
|
|
require.Equalf(t, exp.Hidden, found.Hidden, "option hidden %q", exp.Name)
|
|
require.Equalf(t, exp.Annotations, found.Annotations, "option annotations %q", exp.Name)
|
|
require.Equalf(t, exp.Group, found.Group, "option group %q", exp.Name)
|
|
// UseInstead is the same comparison problem, just check the length
|
|
require.Equalf(t, len(exp.UseInstead), len(found.UseInstead), "option use instead %q", exp.Name)
|
|
}
|
|
|
|
func compareValues(t *testing.T, exp, found clibase.Option) {
|
|
t.Helper()
|
|
|
|
if (exp.Value == nil || found.Value == nil) || (exp.Value.String() != found.Value.String() && found.Value.String() == "") {
|
|
// If the string values are different, this can be a "nil" issue.
|
|
// So only run this case if the found string is the empty string.
|
|
// We use MarshalYAML for struct strings, and it will return an
|
|
// empty string '""' for nil slices/maps/etc.
|
|
// So use json to compare.
|
|
|
|
expJSON, err := json.Marshal(exp.Value)
|
|
require.NoError(t, err, "marshal")
|
|
foundJSON, err := json.Marshal(found.Value)
|
|
require.NoError(t, err, "marshal")
|
|
|
|
expJSON = normalizeJSON(expJSON)
|
|
foundJSON = normalizeJSON(foundJSON)
|
|
assert.Equalf(t, string(expJSON), string(foundJSON), "option value %q", exp.Name)
|
|
} else {
|
|
assert.Equal(t,
|
|
exp.Value.String(),
|
|
found.Value.String(),
|
|
"option value %q", exp.Name)
|
|
}
|
|
}
|
|
|
|
// normalizeJSON handles the fact that an empty map/slice is not the same
|
|
// as a nil empty/slice. For our purposes, they are the same.
|
|
func normalizeJSON(data []byte) []byte {
|
|
if bytes.Equal(data, []byte("[]")) || bytes.Equal(data, []byte("{}")) {
|
|
return []byte("null")
|
|
}
|
|
return data
|
|
}
|