github.com/hernad/nomad@v1.6.112/command/var_put_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"regexp"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/hernad/nomad/api"
    14  	"github.com/hernad/nomad/ci"
    15  	"github.com/mitchellh/cli"
    16  	"github.com/posener/complete"
    17  	"github.com/shoenig/test/must"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestVarPutCommand_Implements(t *testing.T) {
    22  	ci.Parallel(t)
    23  	var _ cli.Command = &VarPutCommand{}
    24  }
    25  func TestVarPutCommand_Fails(t *testing.T) {
    26  	ci.Parallel(t)
    27  	t.Run("bad_args", func(t *testing.T) {
    28  		ci.Parallel(t)
    29  		ui := cli.NewMockUi()
    30  		cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
    31  		code := cmd.Run([]string{"-bad-flag"})
    32  		out := ui.ErrorWriter.String()
    33  		require.Equal(t, 1, code, "expected exit code 1, got: %d")
    34  		require.Contains(t, out, commandErrorText(cmd), "expected help output, got: %s", out)
    35  	})
    36  	t.Run("bad_address", func(t *testing.T) {
    37  		ci.Parallel(t)
    38  		ui := cli.NewMockUi()
    39  		cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
    40  		code := cmd.Run([]string{"-address=nope", "foo", "-"})
    41  		out := ui.ErrorWriter.String()
    42  		require.Equal(t, 1, code, "expected exit code 1, got: %d")
    43  		require.Contains(t, out, "Error creating variable", "expected error creating variable, got: %s", out)
    44  	})
    45  	t.Run("missing_template", func(t *testing.T) {
    46  		ci.Parallel(t)
    47  		ui := cli.NewMockUi()
    48  		cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
    49  		code := cmd.Run([]string{`-out=go-template`, "foo", "-"})
    50  		out := strings.TrimSpace(ui.ErrorWriter.String())
    51  		require.Equal(t, 1, code, "expected exit code 1, got: %d", code)
    52  		require.Equal(t, errMissingTemplate+"\n"+commandErrorText(cmd), out)
    53  	})
    54  	t.Run("unexpected_template", func(t *testing.T) {
    55  		ci.Parallel(t)
    56  		ui := cli.NewMockUi()
    57  		cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
    58  		code := cmd.Run([]string{`-out=json`, `-template="bad"`, "foo", "-"})
    59  		out := strings.TrimSpace(ui.ErrorWriter.String())
    60  		require.Equal(t, 1, code, "expected exit code 1, got: %d", code)
    61  		require.Equal(t, errUnexpectedTemplate+"\n"+commandErrorText(cmd), out)
    62  	})
    63  	t.Run("bad_in", func(t *testing.T) {
    64  		ci.Parallel(t)
    65  		ui := cli.NewMockUi()
    66  		cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
    67  		code := cmd.Run([]string{`-in=bad`, "foo", "-"})
    68  		out := strings.TrimSpace(ui.ErrorWriter.String())
    69  		require.Equal(t, 1, code, "expected exit code 1, got: %d", code)
    70  		require.Equal(t, errInvalidInFormat+"\n"+commandErrorText(cmd), out)
    71  	})
    72  	t.Run("wildcard_namespace", func(t *testing.T) {
    73  		ci.Parallel(t)
    74  		ui := cli.NewMockUi()
    75  		cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
    76  		code := cmd.Run([]string{`-namespace=*`, "foo", "-"})
    77  		out := strings.TrimSpace(ui.ErrorWriter.String())
    78  		require.Equal(t, 1, code, "expected exit code 1, got: %d", code)
    79  		require.Equal(t, errWildcardNamespaceNotAllowed, out)
    80  	})
    81  }
    82  
    83  func TestVarPutCommand_GoodJson(t *testing.T) {
    84  	ci.Parallel(t)
    85  
    86  	// Create a server
    87  	srv, client, url := testServer(t, true, nil)
    88  	defer srv.Shutdown()
    89  
    90  	ui := cli.NewMockUi()
    91  	cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
    92  
    93  	// Get the variable
    94  	code := cmd.Run([]string{"-address=" + url, "-out=json", "test/var", "k1=v1", "k2=v2"})
    95  	require.Equal(t, 0, code, "expected exit 0, got: %d; %v", code, ui.ErrorWriter.String())
    96  
    97  	t.Cleanup(func() {
    98  		_, _ = client.Variables().Delete("test/var", nil)
    99  	})
   100  
   101  	var outVar api.Variable
   102  	b := ui.OutputWriter.Bytes()
   103  	err := json.Unmarshal(b, &outVar)
   104  	require.NoError(t, err, "error unmarshaling json: %v\nb: %s", err, b)
   105  	require.Equal(t, "default", outVar.Namespace)
   106  	require.Equal(t, "test/var", outVar.Path)
   107  	require.Equal(t, api.VariableItems{"k1": "v1", "k2": "v2"}, outVar.Items)
   108  }
   109  
   110  func TestVarPutCommand_AutocompleteArgs(t *testing.T) {
   111  	ci.Parallel(t)
   112  	srv, client, url := testServer(t, true, nil)
   113  	defer srv.Shutdown()
   114  
   115  	ui := cli.NewMockUi()
   116  	cmd := &VarPutCommand{Meta: Meta{Ui: ui, flagAddress: url}}
   117  
   118  	// Create a var
   119  	sv := testVariable()
   120  	_, _, err := client.Variables().Create(sv, nil)
   121  	require.NoError(t, err)
   122  
   123  	args := complete.Args{Last: "t"}
   124  	predictor := cmd.AutocompleteArgs()
   125  
   126  	res := predictor.Predict(args)
   127  	require.Equal(t, 1, len(res))
   128  	require.Equal(t, sv.Path, res[0])
   129  }
   130  
   131  func TestVarPutCommand_KeyWarning(t *testing.T) {
   132  	// Extract invalid characters from warning message.
   133  	r := regexp.MustCompile(`contains characters \[(.*)\]`)
   134  
   135  	tcs := []struct {
   136  		name     string
   137  		goodKeys []string
   138  		badKeys  []string
   139  		badChars []string
   140  	}{
   141  		{
   142  			name:     "simple",
   143  			goodKeys: []string{"simple"},
   144  		},
   145  		{
   146  			name:     "hasDot",
   147  			badKeys:  []string{"has.Dot"},
   148  			badChars: []string{`"."`},
   149  		},
   150  		{
   151  			name:     "unicode_letters",
   152  			goodKeys: []string{"世界"},
   153  		},
   154  		{
   155  			name:     "unicode_numbers",
   156  			goodKeys: []string{"٣٢١"},
   157  		},
   158  		{
   159  			name:     "two_good",
   160  			goodKeys: []string{"aardvark", "beagle"},
   161  		},
   162  		{
   163  			name:     "one_good_one_bad",
   164  			goodKeys: []string{"aardvark"},
   165  			badKeys:  []string{"bad.key"},
   166  			badChars: []string{`"."`},
   167  		},
   168  		{
   169  			name:     "one_good_two_bad",
   170  			goodKeys: []string{"aardvark"},
   171  			badKeys:  []string{"bad.key", "also-bad"},
   172  			badChars: []string{`"."`, `"-"`},
   173  		},
   174  		{
   175  			name:     "repeated_bad_char",
   176  			goodKeys: []string{"aardvark"},
   177  			badKeys:  []string{"bad.key", "also.bad"},
   178  			badChars: []string{`"."`, `"."`},
   179  		},
   180  		{
   181  			name:     "repeated_bad_char_same_key",
   182  			goodKeys: []string{"aardvark"},
   183  			badKeys:  []string{"bad.key."},
   184  			badChars: []string{`"."`},
   185  		},
   186  		{
   187  			name:     "dont_escape",
   188  			goodKeys: []string{"aardvark"},
   189  			badKeys:  []string{"bad\\key"},
   190  			badChars: []string{`"\"`},
   191  		},
   192  	}
   193  
   194  	ci.Parallel(t)
   195  	_, _, url := testServer(t, false, nil)
   196  
   197  	for _, tc := range tcs {
   198  		t.Run(tc.name, func(t *testing.T) {
   199  			tc := tc       // capture test case
   200  			ci.Parallel(t) // make the subtests parallel
   201  
   202  			keys := append(tc.goodKeys, tc.badKeys...) // combine keys into a single slice
   203  			for i, k := range keys {
   204  				keys[i] = k + "=value" // Make each key into a k=v pair; value is not part of test
   205  			}
   206  
   207  			ui := cli.NewMockUi()
   208  			cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
   209  			args := append([]string{"-address=" + url, "-force", "-out=json", "test/var"}, keys...)
   210  			code := cmd.Run(args)
   211  			errOut := ui.ErrorWriter.String()
   212  
   213  			must.Eq(t, 0, code) // the command should always succeed
   214  
   215  			badKeysLen := len(tc.badKeys)
   216  			switch badKeysLen {
   217  			case 0:
   218  				must.Eq(t, "", errOut) // cases with no bad keys shouldn't put anything to stderr
   219  				return
   220  			case 1:
   221  				must.StrContains(t, errOut, "1 warning:") // header should be singular
   222  			default:
   223  				must.StrContains(t, errOut, fmt.Sprintf("%d warnings:", badKeysLen)) // header should be plural
   224  			}
   225  
   226  			for _, k := range tc.badKeys {
   227  				must.StrContains(t, errOut, k) // every bad key should appear in the warning output
   228  			}
   229  
   230  			if len(tc.badChars) > 0 {
   231  				invalid := r.FindAllStringSubmatch(errOut, -1)
   232  				for i, k := range tc.badChars {
   233  					must.Eq(t, invalid[i][1], k) // every bad char should appear in the warning output
   234  				}
   235  			}
   236  
   237  			for _, k := range tc.goodKeys {
   238  				must.StrNotContains(t, errOut, k) // good keys should not be emitted
   239  			}
   240  		})
   241  	}
   242  }