// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package command import ( "flag" "os" "reflect" "sort" "testing" "github.com/creack/pty" "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper/pointer" "github.com/mitchellh/cli" "github.com/shoenig/test/must" "github.com/stretchr/testify/require" ) func TestMeta_FlagSet(t *testing.T) { ci.Parallel(t) cases := []struct { Flags FlagSetFlags Expected []string }{ { FlagSetNone, []string{}, }, { FlagSetClient, []string{ "address", "no-color", "force-color", "region", "namespace", "ca-cert", "ca-path", "client-cert", "client-key", "insecure", "tls-server-name", "tls-skip-verify", "token", }, }, } for i, tc := range cases { var m Meta fs := m.FlagSet("foo", tc.Flags) actual := make([]string, 0, 0) fs.VisitAll(func(f *flag.Flag) { actual = append(actual, f.Name) }) sort.Strings(actual) sort.Strings(tc.Expected) if !reflect.DeepEqual(actual, tc.Expected) { t.Fatalf("%d: flags: %#v\n\nExpected: %#v\nGot: %#v", i, tc.Flags, tc.Expected, actual) } } } func TestMeta_Colorize(t *testing.T) { type testCaseSetupFn func(*testing.T, *Meta) cases := []struct { Name string SetupFn testCaseSetupFn ExpectColor bool }{ { Name: "disable colors if UI is not colored", ExpectColor: false, }, { Name: "colors if UI is colored", SetupFn: func(t *testing.T, m *Meta) { m.Ui = &cli.ColoredUi{} }, ExpectColor: true, }, { Name: "disable colors via CLI flag", SetupFn: func(t *testing.T, m *Meta) { m.SetupUi([]string{"-no-color"}) }, ExpectColor: false, }, { Name: "disable colors via env var", SetupFn: func(t *testing.T, m *Meta) { t.Setenv(EnvNomadCLINoColor, "1") m.SetupUi([]string{}) }, ExpectColor: false, }, { Name: "force colors via CLI flag", SetupFn: func(t *testing.T, m *Meta) { m.SetupUi([]string{"-force-color"}) }, ExpectColor: true, }, { Name: "force colors via env var", SetupFn: func(t *testing.T, m *Meta) { t.Setenv(EnvNomadCLIForceColor, "1") m.SetupUi([]string{}) }, ExpectColor: true, }, { Name: "no color take predecence over force color via CLI flag", SetupFn: func(t *testing.T, m *Meta) { m.SetupUi([]string{"-no-color", "-force-color"}) }, ExpectColor: false, }, { Name: "no color take predecence over force color via env var", SetupFn: func(t *testing.T, m *Meta) { t.Setenv(EnvNomadCLINoColor, "1") m.SetupUi([]string{"-force-color"}) }, ExpectColor: false, }, } for _, tc := range cases { t.Run(tc.Name, func(t *testing.T) { // Create fake test terminal. _, tty, err := pty.Open() require.NoError(t, err) defer tty.Close() oldStdout := os.Stdout defer func() { os.Stdout = oldStdout }() os.Stdout = tty // Make sure color related environment variables are clean. t.Setenv(EnvNomadCLIForceColor, "") t.Setenv(EnvNomadCLINoColor, "") // Run test case. m := &Meta{} if tc.SetupFn != nil { tc.SetupFn(t, m) } require.Equal(t, !tc.ExpectColor, m.Colorize().Disable) }) } } func TestMeta_JobByPrefix(t *testing.T) { ci.Parallel(t) srv, client, _ := testServer(t, true, nil) defer srv.Shutdown() // Wait for a node to be ready waitForNodes(t, client) ui := cli.NewMockUi() meta := &Meta{Ui: ui, namespace: api.AllNamespacesNamespace} client.SetNamespace(api.AllNamespacesNamespace) jobs := []struct { namespace string id string }{ {namespace: "default", id: "example"}, {namespace: "default", id: "job"}, {namespace: "default", id: "job-1"}, {namespace: "default", id: "job-2"}, {namespace: "prod", id: "job-1"}, } for _, j := range jobs { job := testJob(j.id) job.Namespace = pointer.Of(j.namespace) _, err := client.Namespaces().Register(&api.Namespace{Name: j.namespace}, nil) must.NoError(t, err) w := &api.WriteOptions{Namespace: j.namespace} resp, _, err := client.Jobs().Register(job, w) must.NoError(t, err) code := waitForSuccess(ui, client, fullId, t, resp.EvalID) must.Zero(t, code) } testCases := []struct { name string prefix string filterFunc JobByPrefixFilterFunc expectedError string }{ { name: "exact match", prefix: "job", }, { name: "partial match", prefix: "exam", }, { name: "match with filter", prefix: "job-", filterFunc: func(j *api.JobListStub) bool { // Filter out jobs with "job-" so that only "job-2" matches. return j.ID == "job-2" }, }, { name: "multiple matches", prefix: "job-", expectedError: "matched multiple jobs", }, { name: "no match", prefix: "not-found", expectedError: "No job(s) with prefix or ID", }, { name: "multiple matches across namespaces", prefix: "job-1", expectedError: "matched multiple jobs", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { job, err := meta.JobByPrefix(client, tc.prefix, tc.filterFunc) if tc.expectedError != "" { must.Nil(t, job) must.ErrorContains(t, err, tc.expectedError) } else { must.NoError(t, err) must.NotNil(t, job) must.StrContains(t, *job.ID, tc.prefix) } }) } }